first commit

This commit is contained in:
yanpeng200212
2026-10-08 10:43:59 +08:00
commit 8430ba0452
288 changed files with 29595 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import request from '../../../utils/request';
import { ElMessage } from 'element-plus';
// ==================== 列表 ====================
const list = ref([]);
const total = ref(0);
const loading = ref(false);
const deptOptions = ref([]);
const query = reactive({ pageIndex: 1, pageSize: 20, date: '', deptId: null });
async function loadDepts() {
try {
const res = await request.get('/sys/department');
deptOptions.value = (res.data || []).filter((d) => d.name.includes('前缫'));
} catch (e) {}
}
async function load() {
loading.value = true;
try {
const params = { pageIndex: query.pageIndex, pageSize: query.pageSize };
if (query.date) params.date = query.date;
if (query.deptId) params.deptId = query.deptId;
const res = await request.get('/hbjy', { params });
list.value = res.data.items;
total.value = res.data.total;
} finally {
loading.value = false;
}
}
function handlePageChange(p) { query.pageIndex = p; load(); }
// ==================== 编辑对话框 ====================
const dialogVisible = ref(false);
const editLoading = ref(false);
const saving = ref(false);
const editData = ref(null);
// 一度/二度/三度
const yiduColumns = [
{ key: 'ydYidu', label: '一度' },
{ key: 'ydErdu', label: '二度' },
{ key: 'ydSandu', label: '三度' },
];
// 洁净等级
const jjColumns = [
{ key: 'jj100', label: '100' },
{ key: 'jj95', label: '95' },
{ key: 'jj90', label: '90' },
{ key: 'jj85', label: '85' },
{ key: 'jj80', label: '80' },
{ key: 'jj75', label: '75' },
{ key: 'jj70', label: '70' },
{ key: 'jj65', label: '65' },
{ key: 'jj60', label: '60' },
];
// 清洁疵点
const qjColumns = [
{ key: 'qjZycdTdc', label: '特大糙' },
{ key: 'qjCycdFs', label: '废丝' },
{ key: 'qjCycdDc', label: '大糙' },
{ key: 'qjCycdNfc', label: '粘付糙' },
{ key: 'qjCycdDcj', label: '大长结' },
{ key: 'qjCycdZlx', label: '重螺旋' },
{ key: 'qjPtcdXc', label: '小糙' },
{ key: 'qjPtcdCj', label: '长结' },
{ key: 'qjPtcdLx', label: '螺旋' },
{ key: 'qjPtcdHjls', label: '环结及裂丝' },
];
function num(v) {
if (typeof v === 'number') return isFinite(v) ? v : 0;
if (v === null || v === undefined || v === '') return 0;
const n = Number(v);
return isFinite(n) ? n : 0;
}
function round2(v) { return Math.round(v * 100) / 100; }
// 片数 = 洁净等级各列之和
function calcPianshu(row) {
return jjColumns.reduce((s, c) => s + num(row[c.key]), 0);
}
function calcRow(row) {
row.pianshu = calcPianshu(row);
}
function onInput(row, key, v) {
row[key] = num(v);
calcRow(row);
}
function selectAll(e) {
const el = e?.target;
if (el && typeof el.select === 'function') setTimeout(() => el.select(), 0);
}
// 汇总
const summary = computed(() => {
const rows = editData.value?.records || [];
const pianshu = rows.reduce((s, r) => s + (r.pianshu || 0), 0);
// 清洁扣分
let totalDeduction = 0;
rows.forEach((r) => {
totalDeduction += num(r.qjZycdTdc) * 1.0;
totalDeduction += (num(r.qjCycdFs) + num(r.qjCycdDc) + num(r.qjCycdNfc) + num(r.qjCycdDcj) + num(r.qjCycdZlx)) * 0.4;
totalDeduction += (num(r.qjPtcdXc) + num(r.qjPtcdCj) + num(r.qjPtcdLx) + num(r.qjPtcdHjls)) * 0.1;
});
const qingjie = pianshu === 0 ? 0 : round2((pianshu - totalDeduction) * 100 / pianshu);
// 洁净扣分
let jjDeduction = 0;
jjDeduction += 1 * rows.reduce((s, r) => s + num(r.jj95), 0);
jjDeduction += 2 * rows.reduce((s, r) => s + num(r.jj90), 0);
jjDeduction += 3 * rows.reduce((s, r) => s + num(r.jj85), 0);
jjDeduction += 4 * rows.reduce((s, r) => s + num(r.jj80), 0);
jjDeduction += 5 * rows.reduce((s, r) => s + num(r.jj75), 0);
jjDeduction += 6 * rows.reduce((s, r) => s + num(r.jj70), 0);
jjDeduction += 7 * rows.reduce((s, r) => s + num(r.jj65), 0);
jjDeduction += 8 * rows.reduce((s, r) => s + num(r.jj60), 0);
const jiejing = pianshu === 0 ? 0 : round2((pianshu * 100 - jjDeduction * 5) / pianshu);
return { pianshu, qingjie, jiejing };
});
async function openEdit(row) {
editLoading.value = true;
dialogVisible.value = true;
try {
const res = await request.get(`/hbjy/${row.lsjlId}`);
editData.value = res.data;
editData.value.records.forEach(calcRow);
} catch (e) {
dialogVisible.value = false;
} finally {
editLoading.value = false;
}
}
async function save() {
if (!editData.value) return;
if (summary.value.pianshu === 0) { ElMessage.warning('请至少录入一个洁净等级片数'); return; }
saving.value = true;
try {
await request.post('/hbjy', {
lsjlId: editData.value.lsjlId,
dengji: editData.value.dengji || '',
records: editData.value.records.map((r) => ({
chetaihao: r.chetaihao,
ydYidu: num(r.ydYidu), ydErdu: num(r.ydErdu), ydSandu: num(r.ydSandu),
jj100: num(r.jj100), jj95: num(r.jj95), jj90: num(r.jj90), jj85: num(r.jj85),
jj80: num(r.jj80), jj75: num(r.jj75), jj70: num(r.jj70), jj65: num(r.jj65), jj60: num(r.jj60),
qjZycdTdc: num(r.qjZycdTdc),
qjCycdFs: num(r.qjCycdFs), qjCycdDc: num(r.qjCycdDc), qjCycdNfc: num(r.qjCycdNfc),
qjCycdDcj: num(r.qjCycdDcj), qjCycdZlx: num(r.qjCycdZlx),
qjPtcdXc: num(r.qjPtcdXc), qjPtcdCj: num(r.qjPtcdCj), qjPtcdLx: num(r.qjPtcdLx), qjPtcdHjls: num(r.qjPtcdHjls)
}))
});
ElMessage.success('保存成功');
dialogVisible.value = false;
load();
} catch (e) {} finally {
saving.value = false;
}
}
onMounted(() => { load(); loadDepts(); });
</script>
<template>
<el-card shadow="never">
<template #header>
<div class="card-header"><span>黑板检验</span></div>
</template>
<div class="search-bar">
<el-date-picker v-model="query.date" type="date" placeholder="日期" value-format="YYYY-MM-DD" style="width: 160px" />
<el-select v-model="query.deptId" clearable placeholder="车间" style="width: 180px">
<el-option v-for="d in deptOptions" :key="d.id" :label="d.name" :value="d.id" />
</el-select>
<el-button type="primary" @click="query.pageIndex=1;load()">搜索</el-button>
<el-button @click="Object.assign(query,{date:'',deptId:null,pageIndex:1});load()">重置</el-button>
</div>
<el-table v-loading="loading" :data="list" border stripe>
<el-table-column type="index" label="序号" width="55" />
<el-table-column label="日期" width="100">
<template #default="{ row }">{{ row.workDate?.slice(0, 10) }}</template>
</el-table-column>
<el-table-column prop="deptName" label="车间" width="110" />
<el-table-column prop="jizu" label="机组" width="60" />
<el-table-column prop="huishu" label="回数" width="60" />
<el-table-column label="操作" width="80" fixed="right">
<template #default="{ row }">
<el-button size="small" :type="row.isChecked ? 'warning' : 'primary'" @click="openEdit(row)">
{{ row.isChecked ? '重新录入' : '录入' }}
</el-button>
</template>
</el-table-column>
<el-table-column prop="guige" label="规格" width="75" />
<el-table-column prop="zhuangKouName" label="庄口" min-width="160" show-overflow-tooltip />
<el-table-column prop="yanse" label="颜色" width="75" />
<el-table-column prop="isbancheName" label="半车" width="65" />
<el-table-column prop="luosiTime" label="落丝时间" width="90" />
<el-table-column prop="chetaishu" label="车台数" width="70" />
<el-table-column prop="yingluPianshu" label="应录片数" width="80" />
<el-table-column label="已录数" width="70" align="center">
<template #default="{ row }">
<el-tag :type="row.yingluPianshu > row.pianshu ? 'warning' : 'success'" size="small" effect="light">{{ row.pianshu }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="qingjie" label="清洁" width="70" />
<el-table-column prop="jiejing" label="洁净" width="70" />
<el-table-column prop="dengji" label="等级" width="65" />
</el-table>
<el-pagination class="pagination" :current-page="query.pageIndex" :page-size="query.pageSize" :total="total" layout="total, prev, pager, next" @current-change="handlePageChange" />
<!-- 编辑对话框 -->
<el-dialog v-model="dialogVisible" title="黑板检验" width="95%" top="2vh" destroy-on-close>
<div v-loading="editLoading">
<template v-if="editData">
<!-- 落丝信息 -->
<el-descriptions :column="7" border size="small" class="info-block">
<el-descriptions-item label="车间">{{ editData.deptName }}</el-descriptions-item>
<el-descriptions-item label="机组">{{ editData.jizu }}</el-descriptions-item>
<el-descriptions-item label="回数">{{ editData.huishu }}</el-descriptions-item>
<el-descriptions-item label="庄口">{{ editData.zhuangKouName }}</el-descriptions-item>
<el-descriptions-item label="颜色">{{ editData.yanse }}</el-descriptions-item>
<el-descriptions-item label="半车">{{ editData.isbancheName }}</el-descriptions-item>
<el-descriptions-item label="规格">{{ editData.guige }}</el-descriptions-item>
</el-descriptions>
<!-- 车台明细 -->
<el-table :data="editData.records" border size="small" max-height="420" class="hbjy-table">
<el-table-column prop="chetaihao" label="车台" width="70" align="center" fixed />
<el-table-column v-for="c in yiduColumns" :key="c.key" :label="c.label" width="70" align="center">
<template #default="{ row }">
<el-input-number v-model="row[c.key]" :min="0" :controls="false" class="hb-input" @focus="selectAll" @input="(v) => onInput(row, c.key, v)" />
</template>
</el-table-column>
<el-table-column v-for="c in jjColumns" :key="c.key" :label="c.label" width="70" align="center">
<template #default="{ row }">
<el-input-number v-model="row[c.key]" :min="0" :controls="false" class="hb-input" @focus="selectAll" @input="(v) => onInput(row, c.key, v)" />
</template>
</el-table-column>
<el-table-column v-for="c in qjColumns" :key="c.key" :label="c.label" width="80" align="center">
<template #default="{ row }">
<el-input-number v-model="row[c.key]" :min="0" :controls="false" class="hb-input" @focus="selectAll" @input="(v) => onInput(row, c.key, v)" />
</template>
</el-table-column>
<el-table-column prop="pianshu" label="片数" width="70" align="center" fixed="right" />
</el-table>
<!-- 汇总 -->
<div class="summary-bar">
<div class="stat"><span class="lbl">已录片数</span><b>{{ summary.pianshu }}</b></div>
<div class="stat"><span class="lbl">应录片数</span><b class="c-gray">{{ editData.yingluPianshu }}</b></div>
<div class="stat"><span class="lbl">清洁分</span><b class="c-blue">{{ summary.qingjie }}</b></div>
<div class="stat"><span class="lbl">洁净分</span><b class="c-green">{{ summary.jiejing }}</b></div>
<div class="stat level">
<span class="lbl">等级</span>
<el-input v-model="editData.dengji" placeholder="等级" style="width: 90px" />
</div>
</div>
</template>
</div>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
</template>
</el-dialog>
</el-card>
</template>
<style scoped>
.card-header { display: flex; justify-content: space-between; align-items: center; }
.search-bar { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.pagination { margin-top: 16px; justify-content: flex-end; }
.info-block { margin-bottom: 12px; }
.hb-input :deep(.el-input__wrapper) { padding: 0 4px; }
.hb-input :deep(.el-input__inner) { text-align: center; }
.summary-bar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 16px; padding: 14px 16px; background: #f5f7fa; border-radius: 8px; }
.stat { display: flex; flex-direction: column; align-items: center; min-width: 70px; padding: 6px 14px; background: #fff; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,.06); }
.stat .lbl { font-size: 12px; color: #909399; }
.stat b { font-size: 18px; color: #303133; }
.stat .c-blue { color: #409eff; }
.stat .c-green { color: #67c23a; }
.stat .c-gray { color: #909399; }
.stat.level { flex-direction: row; gap: 6px; align-items: center; }
</style>
+283
View File
@@ -0,0 +1,283 @@
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import request from '../../../utils/request';
import { ElMessage, ElMessageBox } from 'element-plus';
const list = ref([]);
const total = ref(0);
const loading = ref(false);
const deptOptions = ref([]); // 前缫车间
const zhuangkouOptions = ref([]); // 工艺庄口
const guigeOptions = ref([]); // 规格
const query = reactive({ pageIndex: 1, pageSize: 20, date: '', deptId: null, keyword: '' });
// 颜色字典(写死)
const yanseOptions = ['蓝色', '绿色', '乌红', '深红', '桔红', '蓝方', '黄方', '绿方', '紫方', '红方', '金方', '红圆', '桔圆', '绿圆', '黄圆', '粉圆', '蓝圆', '嫩绿'];
const jizuOptions = [1, 2, 3, 4, 5, 6];
const dialogVisible = ref(false);
const dialogTitle = ref('新增落丝记录');
const isEdit = ref(false);
const editId = ref(null);
const form = reactive({
workDate: new Date(), deptId: null, gyzkId: null, jizu: 1,
luosiTime: '', yanse: '', huishu: 0, chetaiStart: 0, chetaiEnd: 0,
chetaishu: 0, isbanche: false, hours: 0, guige: '', chesu: 0
});
async function load() {
loading.value = true;
try {
const params = { pageIndex: query.pageIndex, pageSize: query.pageSize, keyword: query.keyword };
if (query.date) params.date = query.date;
if (query.deptId) params.deptId = query.deptId;
const res = await request.get('/lsjl', { params });
list.value = res.data.items;
total.value = res.data.total;
} finally {
loading.value = false;
}
}
// 加载前缫车间(名称含"前缫"的部门)
async function loadDepts() {
const res = await request.get('/sys/department');
deptOptions.value = (res.data || []).filter((d) => d.name.includes('前缫'));
}
async function loadZhuangkou() {
const res = await request.get('/craftzhuangkou', { params: { pageIndex: 1, pageSize: 1000 } });
zhuangkouOptions.value = res.data.items;
}
async function loadGuige() {
const res = await request.get('/shengsiguige/all');
guigeOptions.value = res.data;
}
// 车间+机组 → 自动带出车台号
async function loadChetai() {
if (!form.deptId || !form.jizu) return;
try {
const res = await request.get('/lsjl/chetai', { params: { deptId: form.deptId, jizu: form.jizu } });
if (res.data) {
form.chetaiStart = res.data.chetaiStart;
form.chetaiEnd = res.data.chetaiEnd;
calcChetaishu();
}
} catch (e) {}
}
function calcChetaishu() {
if (form.chetaiStart > 0 && form.chetaiEnd > form.chetaiStart) {
form.chetaishu = form.chetaiEnd - form.chetaiStart + 1;
}
}
function openCreate() {
isEdit.value = false;
dialogTitle.value = '新增落丝记录';
Object.assign(form, {
workDate: new Date(), deptId: deptOptions.value[0]?.id || null, gyzkId: null, jizu: 1,
luosiTime: '', yanse: yanseOptions[0] || '', huishu: 0, chetaiStart: 0, chetaiEnd: 0,
chetaishu: 0, isbanche: false, hours: 0, guige: guigeOptions.value[0] || '', chesu: 0
});
dialogVisible.value = true;
}
function openEdit(row) {
isEdit.value = true;
editId.value = row.id;
dialogTitle.value = '修改落丝记录';
Object.assign(form, {
workDate: row.workDate, deptId: row.deptId, gyzkId: row.gyzkId, jizu: row.jizu,
luosiTime: row.luosiTime, yanse: row.yanse, huishu: row.huishu,
chetaiStart: row.chetaiStart, chetaiEnd: row.chetaiEnd, chetaishu: row.chetaishu,
isbanche: row.isbanche, hours: row.hours, guige: row.guige, chesu: row.chesu
});
dialogVisible.value = true;
}
async function save() {
if (!form.deptId) { ElMessage.warning('请选择车间'); return; }
if (!form.gyzkId) { ElMessage.warning('请选择庄口'); return; }
try {
if (isEdit.value) {
await request.put(`/lsjl/${editId.value}`, form);
ElMessage.success('更新成功');
} else {
await request.post('/lsjl', form);
ElMessage.success('创建成功');
}
dialogVisible.value = false;
load();
} catch (e) {}
}
async function del(row) {
await ElMessageBox.confirm('确定删除这条落丝记录吗?', '提示', { type: 'warning' });
try {
await request.delete(`/lsjl/${row.id}`);
ElMessage.success('删除成功');
load();
} catch (e) {}
}
function handlePageChange(p) { query.pageIndex = p; load(); }
onMounted(() => { load(); loadDepts(); loadZhuangkou(); loadGuige(); });
</script>
<template>
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>落丝记录</span>
<el-button type="primary" @click="openCreate">新增落丝记录</el-button>
</div>
</template>
<div class="search-bar">
<el-date-picker v-model="query.date" type="date" placeholder="日期" value-format="YYYY-MM-DD" style="width: 160px" />
<el-select v-model="query.deptId" clearable placeholder="车间" style="width: 160px">
<el-option v-for="d in deptOptions" :key="d.id" :label="d.name" :value="d.id" />
</el-select>
<el-input v-model="query.keyword" placeholder="庄口/规格" clearable style="width: 180px" @keyup.enter="query.pageIndex=1;load()" />
<el-button type="primary" @click="query.pageIndex=1;load()">搜索</el-button>
<el-button @click="Object.assign(query,{date:'',deptId:null,keyword:'',pageIndex:1});load()">重置</el-button>
</div>
<el-table v-loading="loading" :data="list" border stripe>
<el-table-column label="日期" width="100">
<template #default="{ row }">{{ row.workDate?.slice(0, 10) }}</template>
</el-table-column>
<el-table-column prop="deptName" label="车间" width="120" />
<el-table-column prop="jizu" label="机组" width="70" />
<el-table-column prop="guige" label="规格" width="80" />
<el-table-column prop="zhuangKouName" label="庄口" min-width="180" />
<el-table-column prop="huishu" label="回数" width="70" />
<el-table-column prop="yanse" label="颜色" width="80" />
<el-table-column prop="isbancheName" label="半车" width="70" />
<el-table-column prop="luosiTime" label="落丝时间" width="90" />
<el-table-column prop="hours" label="时长" width="70" />
<el-table-column prop="chetaishu" label="车台数" width="80" />
<el-table-column prop="chetaiStart" label="开始车台" width="90" />
<el-table-column prop="chetaiEnd" label="结束车台" width="90" />
<el-table-column prop="chesu" label="车速" width="80" />
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button size="small" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="del(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination class="pagination" :current-page="query.pageIndex" :page-size="query.pageSize" :total="total" layout="total, prev, pager, next" @current-change="handlePageChange" />
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="640px">
<el-form :model="form" label-width="100px">
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="日期">
<el-date-picker v-model="form.workDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="车间">
<el-select v-model="form.deptId" style="width: 100%" @change="loadChetai">
<el-option v-for="d in deptOptions" :key="d.id" :label="d.name" :value="d.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="机组">
<el-select v-model="form.jizu" style="width: 100%" @change="loadChetai">
<el-option v-for="j in jizuOptions" :key="j" :label="j" :value="j" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="庄口">
<el-select v-model="form.gyzkId" filterable style="width: 100%">
<el-option v-for="z in zhuangkouOptions" :key="z.id" :label="z.zhuangKouName" :value="z.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="颜色">
<el-select v-model="form.yanse" style="width: 100%">
<el-option v-for="y in yanseOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="回数">
<el-input-number v-model="form.huishu" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="落丝时间">
<el-time-picker v-model="form.luosiTime" value-format="HH:mm:ss" placeholder="落丝时间" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="缫丝时长">
<el-input-number v-model="form.hours" :min="0" :precision="1" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="8">
<el-form-item label="开始车台">
<el-input-number v-model="form.chetaiStart" :min="0" style="width: 100%" @change="calcChetaishu" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结束车台">
<el-input-number v-model="form.chetaiEnd" :min="0" style="width: 100%" @change="calcChetaishu" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="车台数">
<el-input-number v-model="form.chetaishu" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="规格">
<el-select v-model="form.guige" style="width: 100%">
<el-option v-for="g in guigeOptions" :key="g" :label="g" :value="g" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="车速">
<el-input-number v-model="form.chesu" :min="0" :precision="1" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="半车">
<el-switch v-model="form.isbanche" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="save">确定</el-button>
</template>
</el-dialog>
</el-card>
</template>
<style scoped>
.card-header { display: flex; justify-content: space-between; align-items: center; }
.search-bar { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.pagination { margin-top: 16px; justify-content: flex-end; }
</style>
+374
View File
@@ -0,0 +1,374 @@
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import request from '../../../utils/request';
import { ElMessage } from 'element-plus';
import { CircleCloseFilled, WarningFilled, InfoFilled } from '@element-plus/icons-vue';
// ==================== 列表 ====================
const list = ref([]);
const total = ref(0);
const loading = ref(false);
const deptOptions = ref([]);
const query = reactive({ pageIndex: 1, pageSize: 20, date: '', deptId: null });
async function loadDepts() {
try {
const res = await request.get('/sys/department');
deptOptions.value = (res.data || []).filter((d) => d.name.includes('前缫'));
} catch (e) {}
}
async function load() {
loading.value = true;
try {
const params = { pageIndex: query.pageIndex, pageSize: query.pageSize };
if (query.date) params.date = query.date;
if (query.deptId) params.deptId = query.deptId;
const res = await request.get('/xdjy', { params });
list.value = res.data.items;
total.value = res.data.total;
} finally {
loading.value = false;
}
}
function handlePageChange(p) { query.pageIndex = p; load(); }
// ==================== 编辑对话框 ====================
const dialogVisible = ref(false);
const editLoading = ref(false);
const saving = ref(false);
const editData = ref(null);
const gg = computed(() => editData.value?.guigeConfig || null);
// 纤度列配置
const xdColumns = [
{ key: 'xd1', label: '纤度1' },
{ key: 'xd2', label: '纤度2' },
{ key: 'xd3', label: '纤度3' },
{ key: 'xd4', label: '纤度4' },
];
function num(v) {
if (typeof v === 'number') return isFinite(v) ? v : 0;
if (v === null || v === undefined || v === '') return 0;
const n = Number(v);
return isFinite(n) ? n : 0;
}
function round2(v) { return Math.round(v * 100) / 100; }
// 野纤等级:teye(特野) / daye(大野) / xiaoye(小野) / null(正常)
function xdLevel(xd) {
const g = gg.value;
const v = num(xd);
if (!g || v <= 0) return null;
if (v <= g.teyeLolimit || v >= g.teyeUplimit) return 'teye';
if (v <= g.dayeLolimit || v >= g.dayeUplimit) return 'daye';
if (v <= g.xiaoyeLolimit || v >= g.xiaoyeUplimit) return 'xiaoye';
return null;
}
// 范围(在中心范围内)
function isInRange(xd) {
const g = gg.value;
const v = num(xd);
return !!g && v > 0 && g.centerrangeLolimit <= v && v <= g.centerrangeUplimit;
}
// 中心(等于中心纤度)
function isCenter(xd) {
const g = gg.value;
const v = num(xd);
return !!g && v > 0 && g.centerXiandu === v;
}
// 计算单行指标
function calcRow(row) {
const xds = [num(row.xd1), num(row.xd2), num(row.xd3), num(row.xd4)];
const valid = xds.filter((v) => v > 0);
row.xdNum = valid.length;
if (valid.length > 0) {
row.xdAvg = round2(xds.reduce((a, b) => a + b, 0) / valid.length);
row.fanweiNum = xds.filter(isInRange).length;
row.xiaoyeNum = xds.filter((v) => xdLevel(v) === 'xiaoye').length;
row.dayeNum = xds.filter((v) => xdLevel(v) === 'daye').length;
row.teyeNum = xds.filter((v) => xdLevel(v) === 'teye').length;
row.centerNum = xds.filter(isCenter).length;
} else {
row.xdAvg = 0; row.fanweiNum = 0; row.xiaoyeNum = 0; row.dayeNum = 0; row.teyeNum = 0; row.centerNum = 0;
}
}
// 汇总指标
const summary = computed(() => {
const items = editData.value?.xdjy || [];
let jiaoshu = 0, sum = 0, fanweiNum = 0, xiaoyeNum = 0, dayeNum = 0, teyeNum = 0, centerNum = 0;
const xds = [];
items.forEach((r) => {
[num(r.xd1), num(r.xd2), num(r.xd3), num(r.xd4)].forEach((xd) => {
if (xd > 0) { jiaoshu++; sum += xd; xds.push(xd); }
});
fanweiNum += r.fanweiNum || 0; xiaoyeNum += r.xiaoyeNum || 0; dayeNum += r.dayeNum || 0;
teyeNum += r.teyeNum || 0; centerNum += r.centerNum || 0;
});
const avg = jiaoshu > 0 ? sum / jiaoshu : 0;
let junfangcha = 0;
if (jiaoshu > 0) {
let diff = 0;
xds.forEach((x) => { const d = x - avg; diff += d * d; });
junfangcha = round2(Math.sqrt(round2(diff / jiaoshu)));
}
let zongcha = 0;
if (jiaoshu > 0) {
xds.sort((a, b) => a - b);
const n = Math.max(1, Math.ceil(xds.length * 0.02));
const minAvg = xds.slice(0, n).reduce((a, b) => a + b, 0) / n;
const maxAvg = xds.slice(xds.length - n).reduce((a, b) => a + b, 0) / n;
zongcha = round2(Math.max(Math.abs(minAvg - round2(avg)), Math.abs(maxAvg - round2(avg))));
}
const fanxianLv = jiaoshu > 0 ? Math.round(fanweiNum * 100 / jiaoshu) : 0;
return { jiaoshu, avgxiandu: round2(avg), junfangcha, zongcha, fanxianLv, fanweiNum, xiaoyeNum, dayeNum, teyeNum, centerNum };
});
// 纤度输入:显式赋值 + 重算,避免事件时序问题
function onXdInput(row, key, v) {
row[key] = num(v);
calcRow(row);
}
// 单击选中整个单元格(聚焦时全选,方便直接覆盖输入)
function selectAll(e) {
const el = e?.target;
if (el && typeof el.select === 'function') {
setTimeout(() => el.select(), 0);
}
}
async function openEdit(row) {
editLoading.value = true;
dialogVisible.value = true;
try {
const res = await request.get(`/xdjy/${row.lsjlId}`);
editData.value = res.data;
editData.value.xdjy.forEach(calcRow);
} catch (e) {
dialogVisible.value = false;
} finally {
editLoading.value = false;
}
}
async function save() {
if (!editData.value) return;
if (summary.value.jiaoshu === 0) { ElMessage.warning('请至少录入一个纤度值'); return; }
saving.value = true;
try {
await request.post('/xdjy', {
lsjlId: editData.value.lsjlId,
dengji: editData.value.dengji || '',
items: editData.value.xdjy.map((i) => ({
chetaihao: i.chetaihao,
xd1: num(i.xd1), xd2: num(i.xd2), xd3: num(i.xd3), xd4: num(i.xd4)
}))
});
ElMessage.success('保存成功');
dialogVisible.value = false;
load();
} catch (e) {} finally {
saving.value = false;
}
}
onMounted(() => { load(); loadDepts(); });
</script>
<template>
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>纤度检验</span>
</div>
</template>
<div class="search-bar">
<el-date-picker v-model="query.date" type="date" placeholder="日期" value-format="YYYY-MM-DD" style="width: 160px" />
<el-select v-model="query.deptId" clearable placeholder="车间" style="width: 180px">
<el-option v-for="d in deptOptions" :key="d.id" :label="d.name" :value="d.id" />
</el-select>
<el-button type="primary" @click="query.pageIndex=1;load()">搜索</el-button>
<el-button @click="Object.assign(query,{date:'',deptId:null,pageIndex:1});load()">重置</el-button>
</div>
<el-table v-loading="loading" :data="list" border stripe>
<el-table-column type="index" label="序号" width="55" />
<el-table-column label="日期" width="100">
<template #default="{ row }">{{ row.workDate?.slice(0, 10) }}</template>
</el-table-column>
<el-table-column prop="deptName" label="车间" width="110" />
<el-table-column prop="jizu" label="机组" width="60" />
<el-table-column prop="huishu" label="回数" width="60" />
<el-table-column prop="guige" label="规格" width="75" />
<el-table-column prop="zhuangKouName" label="庄口" min-width="160" show-overflow-tooltip />
<el-table-column prop="yanse" label="颜色" width="75" />
<el-table-column prop="isbancheName" label="半车" width="65" />
<el-table-column prop="chetaishu" label="车台数" width="70" />
<el-table-column prop="luosiTime" label="落丝时间" width="90" />
<el-table-column prop="yingchengnum" label="应秤数" width="70" />
<el-table-column label="已秤数" width="70" align="center">
<template #default="{ row }">
<el-tag :type="row.yingchengnum > row.jiaoshu ? 'warning' : 'success'" size="small" effect="light">{{ row.jiaoshu }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="avgxiandu" label="平均纤度" width="85" />
<el-table-column prop="junfangcha" label="均方差" width="75" />
<el-table-column prop="zongcha" label="总差" width="65" />
<el-table-column prop="fanxianLv" label="范纤率" width="70" />
<el-table-column prop="centerNum" label="中心" width="60" />
<el-table-column prop="xiaoyeNum" label="小野" width="60" />
<el-table-column prop="dayeNum" label="大野" width="60" />
<el-table-column prop="teyeNum" label="特野" width="60" />
<el-table-column prop="dengji" label="等级" width="65" />
<el-table-column label="操作" width="90" fixed="right">
<template #default="{ row }">
<el-button size="small" :type="row.isChecked ? 'warning' : 'primary'" @click="openEdit(row)">
{{ row.isChecked ? '重新检验' : '检验' }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination class="pagination" :current-page="query.pageIndex" :page-size="query.pageSize" :total="total" layout="total, prev, pager, next" @current-change="handlePageChange" />
<!-- 检验对话框 -->
<el-dialog v-model="dialogVisible" title="纤度检验" width="94%" top="2vh" destroy-on-close>
<div v-loading="editLoading">
<template v-if="editData">
<!-- 落丝信息 -->
<el-descriptions :column="6" border size="small" class="info-block">
<el-descriptions-item label="车间">{{ editData.deptName }}</el-descriptions-item>
<el-descriptions-item label="机组">{{ editData.jizu }}</el-descriptions-item>
<el-descriptions-item label="回数">{{ editData.huishu }}</el-descriptions-item>
<el-descriptions-item label="庄口">{{ editData.zhuangKouName }}</el-descriptions-item>
<el-descriptions-item label="颜色">{{ editData.yanse }}</el-descriptions-item>
<el-descriptions-item label="半车">{{ editData.isbancheName }}</el-descriptions-item>
</el-descriptions>
<!-- 规格范围 -->
<el-descriptions v-if="gg" :column="6" border size="small" class="info-block">
<el-descriptions-item label="规格">{{ gg.guige }}</el-descriptions-item>
<el-descriptions-item label="中心范围">[{{ gg.centerrangeLolimit }} - {{ gg.centerrangeUplimit }}]</el-descriptions-item>
<el-descriptions-item label="中心纤度">{{ gg.centerXiandu }}</el-descriptions-item>
<el-descriptions-item label="小野范围" class="d-xiaoye">≤{{ gg.xiaoyeLolimit }} / ≥{{ gg.xiaoyeUplimit }}</el-descriptions-item>
<el-descriptions-item label="大野范围" class="d-daye">≤{{ gg.dayeLolimit }} / ≥{{ gg.dayeUplimit }}</el-descriptions-item>
<el-descriptions-item label="特野范围" class="d-teye">≤{{ gg.teyeLolimit }} / ≥{{ gg.teyeUplimit }}</el-descriptions-item>
</el-descriptions>
<!-- 车台明细 -->
<el-table :data="editData.xdjy" border size="small" max-height="440">
<el-table-column prop="chetaihao" label="车台号" width="70" align="center" fixed />
<el-table-column v-for="c in xdColumns" :key="c.key" :label="c.label" width="118" align="center">
<template #default="{ row }">
<el-input-number
v-model="row[c.key]"
:min="0"
:precision="1"
:step="0.5"
:controls="false"
:class="['xd-input', 'xd-' + (xdLevel(row[c.key]) || 'normal')]"
@focus="selectAll"
@input="(v) => onXdInput(row, c.key, v)"
>
<template #suffix>
<el-icon v-if="xdLevel(row[c.key]) === 'teye'" class="xd-icon icon-teye"><CircleCloseFilled /></el-icon>
<el-icon v-else-if="xdLevel(row[c.key]) === 'daye'" class="xd-icon icon-daye"><WarningFilled /></el-icon>
<el-icon v-else-if="xdLevel(row[c.key]) === 'xiaoye'" class="xd-icon icon-xiaoye"><InfoFilled /></el-icon>
</template>
</el-input-number>
</template>
</el-table-column>
<el-table-column prop="xdNum" label="已称绞数" width="80" align="center" />
<el-table-column prop="xdAvg" label="平均纤度" width="85" align="center" />
<el-table-column prop="fanweiNum" label="范围" width="60" align="center" />
<el-table-column prop="xiaoyeNum" label="小野" width="60" align="center" />
<el-table-column prop="dayeNum" label="大野" width="60" align="center" />
<el-table-column prop="teyeNum" label="特野" width="60" align="center" />
<el-table-column prop="centerNum" label="中心" width="60" align="center" />
</el-table>
<!-- 汇总 -->
<div class="summary-bar">
<div class="stat"><span class="lbl">已称绞数</span><b>{{ summary.jiaoshu }}</b></div>
<div class="stat"><span class="lbl">平均纤度</span><b class="c-primary">{{ summary.avgxiandu }}</b></div>
<div class="stat"><span class="lbl">均方差</span><b>{{ summary.junfangcha }}</b></div>
<div class="stat"><span class="lbl">总差</span><b>{{ summary.zongcha }}</b></div>
<div class="stat"><span class="lbl">范纤率</span><b class="c-primary">{{ summary.fanxianLv }}%</b></div>
<div class="stat"><span class="lbl">范围</span><b class="c-blue">{{ summary.fanweiNum }}</b></div>
<div class="stat tag-xiaoye"><span class="lbl">小野</span><b>{{ summary.xiaoyeNum }}</b></div>
<div class="stat tag-daye"><span class="lbl">大野</span><b>{{ summary.dayeNum }}</b></div>
<div class="stat tag-teye"><span class="lbl">特野</span><b>{{ summary.teyeNum }}</b></div>
<div class="stat tag-center"><span class="lbl">中心</span><b>{{ summary.centerNum }}</b></div>
<div class="stat level">
<span class="lbl">等级</span>
<el-input v-model="editData.dengji" placeholder="等级" style="width: 90px" />
</div>
</div>
<!-- 图例 -->
<div class="legend">
<span class="lg lg-teye"><el-icon><CircleCloseFilled /></el-icon> 特野</span>
<span class="lg lg-daye"><el-icon><WarningFilled /></el-icon> 大野</span>
<span class="lg lg-xiaoye"><el-icon><InfoFilled /></el-icon> 小野</span>
</div>
</template>
</div>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
</template>
</el-dialog>
</el-card>
</template>
<style scoped>
.card-header { display: flex; justify-content: space-between; align-items: center; }
.search-bar { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.pagination { margin-top: 16px; justify-content: flex-end; }
.info-block { margin-bottom: 12px; }
/* 规格范围描述着色 */
.d-xiaoye :deep(.el-descriptions__content) { color: #b88230; font-weight: 600; }
.d-daye :deep(.el-descriptions__content) { color: #e6a23c; font-weight: 600; }
.d-teye :deep(.el-descriptions__content) { color: #f56c6c; font-weight: 600; }
/* 纤度输入框:浅色背景 + 图标 */
.xd-input :deep(.el-input__wrapper) { padding: 0 6px; }
.xd-input.xd-teye :deep(.el-input__wrapper) { background: #fef0f0; box-shadow: 0 0 0 1px #f56c6c inset; }
.xd-input.xd-daye :deep(.el-input__wrapper) { background: #fdf6ec; box-shadow: 0 0 0 1px #e6a23c inset; }
.xd-input.xd-xiaoye :deep(.el-input__wrapper) { background: #fef9e7; box-shadow: 0 0 0 1px #e6b23c inset; }
.xd-icon { font-size: 14px; }
.icon-teye { color: #f56c6c; }
.icon-daye { color: #e6a23c; }
.icon-xiaoye { color: #e6b23c; }
/* 汇总栏 */
.summary-bar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 16px; padding: 14px 16px; background: #f5f7fa; border-radius: 8px; }
.stat { display: flex; flex-direction: column; align-items: center; min-width: 64px; padding: 6px 12px; background: #fff; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,.06); }
.stat .lbl { font-size: 12px; color: #909399; }
.stat b { font-size: 18px; color: #303133; }
.stat .c-primary { color: #409eff; }
.stat .c-blue { color: #409eff; }
.stat.tag-xiaoye b { color: #b88230; }
.stat.tag-daye b { color: #e6a23c; }
.stat.tag-teye b { color: #f56c6c; }
.stat.tag-center b { color: #67c23a; }
.stat.level { flex-direction: row; gap: 6px; align-items: center; }
/* 图例 */
.legend { display: flex; gap: 18px; margin-top: 12px; color: #606266; font-size: 13px; }
.legend .lg { display: flex; align-items: center; gap: 4px; }
.lg-teye { color: #f56c6c; }
.lg-daye { color: #e6a23c; }
.lg-xiaoye { color: #b88230; }
</style>