<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生产计划排程表</title>
<style>
/* ======= 天空浅蓝主题 ======= */
:root{
--bg:#f5faff;
--panel:#fff;
--border:#cce7ff;
--primary:#3ca9ff;
--primary-dark:#0077e6;
--primary-light:#e6f4ff;
--text:#003366;
--text-light:#0077e6;
--shadow:rgba(60,169,255,.12);
}
body{margin:0;background:var(--bg);font-family:"Segoe UI",Arial,"PingFang SC","Microsoft YaHei",sans-serif}
.status-container{max-width:98%;margin:22px auto;background:var(--panel);border:1px solid var(--border);padding:22px 18px 30px 18px;border-radius:12px;box-shadow:0 6px 30px var(--shadow);position:relative}
.status-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px;background:var(--bg)}
.status-header h3{color:var(--text-light);letter-spacing:2px}
.status-header .btns{display:flex;gap:12px}
.btn{padding:7px 18px;border:none;border-radius:4px;font-size:15px;cursor:pointer;background:var(--primary);color:#fff;transition:background .2s}
.btn:hover{background:var(--primary-dark)}
.btn.modify{background:var(--primary-light);color:var(--text)}
.btn.modify:hover{background:var(--primary-dark);color:#fff}
.search-box{display:flex;align-items:center;gap:8px}
.search-box input{height:32px;border:1px solid var(--primary);border-radius:4px;padding:0 10px;font-size:14px;background:#fff;color:var(--text)}
table{width:100%;border-collapse:collapse;font-size:14px;color:var(--text)}
thead{background-color:var(--primary-light);color:var(--text)}
th,td{border:1px solid var(--border);padding:9px 7px;text-align:center}
th{background:var(--primary-light);color:var(--text)}
.tag{border-radius:10px;padding:2px 10px;font-size:13px;display:inline-block}
.tag-success{color:#21b97a}.tag-danger{color:#f56c6c}
/* 弹窗共用 */
.modal-mask{position:fixed;inset:0;background:rgba(0,119,230,.2);z-index:999;display:none}
.modal{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);background:#fff;color:var(--text);border-radius:8px;padding:22px;min-width:360px;z-index:1000;display:none}
.modal-title{font-weight:bold;font-size:1.18rem;color:var(--primary-dark);margin-bottom:19px}
.modal-form-row{display:flex;align-items:center;margin-bottom:13px}
.modal-form-row label{width:86px;color:var(--text-light);font-size:14px}
.modal-form-row input,.modal-form-row select{flex:1;height:28px;padding:0 7px;border:1px solid var(--primary);border-radius:4px;background:var(--bg);color:var(--text)}
.modal-btns{text-align:right;margin-top:18px}
.modal-btns .btn{margin-left:12px;background:var(--primary);color:#fff}
/* 详情弹窗专属 */
.detail-modal{min-width:800px;max-width:95vw;max-height:90vh;overflow:auto}
.detail-modal table th,.detail-modal table td{color:var(--text);border:1px solid var(--border);padding:6px 4px;min-width:90px;background:#fff}
.detail-modal input[type=number]{width:100%;border:1px solid var(--border);border-radius:3px;padding:4px;background:#fff;color:var(--text)}
.detail-btns{margin-top:15px;text-align:right}
/* 分页 */
.pager-box{display:flex;align-items:center;gap:8px;position:absolute;right:22px;bottom:-80px;background:var(--primary-light);padding:6px 10px;border-radius:6px;box-shadow:0 2px 8px var(--shadow);font-size:14px;color:var(--text-light)}
.pager-label input{width:46px;height:24px;text-align:center;border:1px solid var(--primary);border-radius:4px;margin:0 4px;background:#fff;color:var(--text-light)}
</style>
</head>
<body>
<div class="status-container">
<div class="status-header">
<h3>生产计划排程表</h3>
<div class="btns">
<div class="search-box">
<input type="text" id="searchInput" placeholder="工序、负责人、产品名称...关键词查询">
<button class="btn" onclick="searchPlans()">查询</button>
</div>
<button class="btn" onclick="openAddModal()">增加</button>
</div>
</div>
<table>
<thead>
<tr>
<th>序号</th><th>工序</th><th>接单日期</th><th>负责人</th><th>订单跟踪号</th><th>产品代码</th>
<th>产品名称</th><th>订单量</th><th>GTM出货日期</th><th>工厂入库日期</th><th>是否延误</th>
<th>累计交付</th><th>待交付</th><th>结单情况</th><th>排程日期</th><th>备注</th><th>操作</th>
</tr>
</thead>
<tbody id="planTableBody"><tr><td colspan="17">加载中...</td></tbody>
</table>
<div id="pagerBox" class="pager-box">
<button class="btn" id="btnPrev" onclick="prevPage()">上一页</button>
<label class="pager-label">
第<input type="number" id="pageInput" min="1" max="1" value="1" onkeydown="if(event.key==='Enter') jumpToPage()">页 / 共<span id="totalPages">1</span>页
</label>
<button class="btn" id="btnNext" onclick="nextPage()">下一页</button>
<button class="btn modify" onclick="jumpToPage()">跳转</button>
</div>
</div>
<!-- 原有增删改弹窗 -->
<div class="modal-mask" id="modalMask"></div>
<div class="modal" id="modalBox">
<div class="modal-title" id="modalTitle">新增排程</div>
<form id="modalForm">
<div class="modal-form-row"><label>工序</label><input type="text" name="processName" required></div>
<div class="modal-form-row"><label>接单日期</label><input type="date" name="startDate"></div>
<div class="modal-form-row"><label>负责人</label><input type="text" name="leader" required></div>
<div class="modal-form-row"><label>订单跟踪号</label><input type="text" name="orderTrackingNo" required></div>
<div class="modal-form-row"><label>产品代码</label><input type="text" name="productCode" required></div>
<div class="modal-form-row"><label>产品名称</label><input type="text" name="productName" required></div>
<div class="modal-form-row"><label>订单量</label><input type="number" name="orderQuantity" required></div>
<div class="modal-form-row"><label>GTM出货日期</label><input type="date" name="shipmentDate"></div>
<div class="modal-form-row"><label>工厂入库日期</label><input type="date" name="finishDate"></div>
<div class="modal-form-row"><label>是否延误</label>
<select name="isDelayed"><option value="1">是</option><option value="0">否</option></select></div>
<div class="modal-form-row"><label>累计交付</label><input type="number" name="deliveredQty"></div>
<div class="modal-form-row"><label>待交付</label><input type="number" name="pendingQty"></div>
<div class="modal-form-row"><label>结单情况</label><input type="text" name="receiveStatus"></div>
<div class="modal-form-row"><label>排程日期</label><input type="date" name="scheduleDate"></div>
<div class="modal-form-row"><label>备注</label><input type="text" name="remark"></div>
<div class="modal-btns">
<button type="button" class="btn" onclick="closeModal()">取消</button>
<button type="submit" class="btn modify" id="modalSubmitBtn">保存</button>
</div>
</form>
</div>
<!-- ===== 新增:详情弹窗 ===== -->
<div class="modal-mask" id="detailMask" style="display:none"></div>
<div class="modal detail-modal" id="detailWrap" style="display:none">
<div class="modal-title">排程详情</div>
<p>订单跟踪号:<span id="detailOrderNo"></span> 产品名称:<span id="detailProductName"></span></p>
<div style="overflow-x:auto">
<table id="detailTable">
<thead><tr id="detailHeadRow"></tr></thead>
<tbody>
<tr id="plannedRow"><td><b>计划交付</b></td></tr>
<tr id="actualRow"><td><b>实际交付</b></td></tr>
</tbody>
</table>
</div>
<div class="modal-btns">
<button class="btn modify" onclick="saveDetail()">保存</button>
<button class="btn" onclick="closeDetail()">关闭</button>
</div>
</div>
<script>
let editingId=null,allPlans=[],currentPage=1,totalPages=1;
async function loadPlans(k,p){const t=document.getElementById('planTableBody');t.innerHTML='<tr><td colspan="17">加载中...</td></tr>';try{let u=`http://localhost:8080/api/production_plans?page=${p}&size=10`;if(k)u+=`&keyword=${encodeURIComponent(k)}`;const r=await fetch(u),d=await r.json();allPlans=d.records;renderPlans(d.records);toggleOperationColumn();currentPage=d.current;totalPages=d.pages;updatePager();}catch{t.innerHTML='<tr><td colspan="17">加载失败</td></tr>'}}
function renderPlans(d){const t=document.getElementById('planTableBody');if(!Array.isArray(d)||d.length===0){t.innerHTML='<tr><td colspan="17">暂无数据</td></tr>';return;}t.innerHTML=d.map(r=>`<tr>
<td>${r.id}</td><td>${r.processName||'-'}</td><td>${r.startDate||'-'}</td><td>${r.leader||'-'}</td><td>${r.orderTrackingNo||'-'}</td>
<td>${r.productCode||'-'}</td><td>${r.productName||'-'}</td><td>${r.orderQuantity??'-'}</td><td>${r.shipmentDate||'-'}</td>
<td>${r.finishDate||'-'}</td><td><span class="tag ${r.isDelayed>0?'tag-danger':'tag-success'}">${r.isDelayed>0?'是':'否'}</span></td>
<td>${r.deliveredQty??'-'}</td><td>${r.pendingQty??'-'}</td><td>${r.receiveStatus||'-'}</td>
<td><button class="btn modify" onclick="openDetail(${r.id})">查看详情</button></td>
<td>${r.remark||'-'}</td>
<td><button class="btn modify" onclick="openEditModal(${r.id})">修改</button><button class="btn" style="margin-left:6px;background:#f56c6c" onclick="deletePlan(${r.id})">删除</button></td>
</tr>`).join('')}
function toggleOperationColumn(){const role=localStorage.getItem('role');const head=document.querySelector('th:last-child'),cells=document.querySelectorAll('td:last-child');if(role==='ADMIN'){head.style.display='';cells.forEach(c=>c.style.display='')}else{head.style.display='none';cells.forEach(c=>c.style.display='none')}}
function searchPlans(){loadPlans(document.getElementById('searchInput').value.trim(),1)}
function updatePager(){const i=document.getElementById('pageInput');i.max=totalPages;i.value=currentPage;document.getElementById('totalPages').textContent=totalPages;document.getElementById('btnPrev').disabled=currentPage<=1;document.getElementById('btnNext').disabled=currentPage>=totalPages}
function prevPage(){if(currentPage>1)loadPlans(document.getElementById('searchInput').value.trim(),currentPage-1)}
function nextPage(){if(currentPage<totalPages)loadPlans(document.getElementById('searchInput').value.trim(),currentPage+1)}
function jumpToPage(){const v=parseInt(document.getElementById('pageInput').value,10);if(isNaN(v)||v<1||v>totalPages)return;loadPlans(document.getElementById('searchInput').value.trim(),v)}
function openAddModal(){editingId=null;resetModal('新增排程')}
function openEditModal(id){editingId=id;const r=allPlans.find(p=>p.id===id);if(!r)return;resetModal('修改排程',r)}
function resetModal(title,data={}){document.getElementById('modalTitle').textContent=title;const f=document.getElementById('modalForm');f.reset();for(const k in data)if(f[k])f[k].value=data[k];document.getElementById('modalMask').style.display='block';document.getElementById('modalBox').style.display='block'}
function closeModal(){document.getElementById('modalMask').style.display='none';document.getElementById('modalBox').style.display='none'}
document.getElementById('modalMask').onclick=closeModal;
document.getElementById('modalForm').onsubmit=async e=>{e.preventDefault();const fd=new FormData(e.target),data=Object.fromEntries(fd);data.isDelayed=data.isDelayed==='1';['orderQuantity','deliveredQty','pendingQty'].forEach(k=>{if(data[k])data[k]=Number(data[k])});try{const url=editingId?`/api/production_plans/${editingId}`:'/api/production_plans',meth=editingId?'PUT':'POST';const res=await fetch(url,{method:meth,headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});if(res.ok){closeModal();loadPlans('',currentPage)}else alert('保存失败')}catch{alert('网络异常')}}
async function deletePlan(id){if(!confirm(`确定删除排程 ${id} 吗?`))return;try{const res=await fetch(`/api/production_plans/${id}`,{method:'DELETE'});if(res.ok){loadPlans(document.getElementById('searchInput').value.trim(),currentPage)}else alert('删除失败')}catch{alert('网络异常')}}
/* ================= 新增详情弹窗逻辑 ================= */
let curDetailPlanId = null;
/* ================= 新增详情弹窗逻辑(含星期几行) ================= */
async function openDetail(planId) {
curDetailPlanId = planId;
const plan = allPlans.find(p => p.id === planId);
if (!plan) return;
document.getElementById('detailOrderNo').textContent = plan.orderTrackingNo;
document.getElementById('detailProductName').textContent = plan.productName;
/* 1. 生成最近 30 天日期 */
const dates = [];
const today = new Date();
for (let i = 0; i < 30; i++) {
const d = new Date(today);
d.setDate(today.getDate() + i);
dates.push(d.toISOString().slice(0, 10));
}
/* 2. 读取已保存数据 */
let saved = {};
try {
const res = await fetch(`/api/production_plans/${planId}/details`);
if (res.ok) (await res.json()).forEach(it => (saved[it.date] = it));
} catch (e) { /* ignore */ }
/* 3. 计算星期几(0=周日 … 6=周六) */
const weekMap = ['日', '一', '二', '三', '四', '五', '六'];
const weekRowHTML = dates
.map(d => `<td>周${weekMap[new Date(d + 'T00:00:00').getDay()]}</td>`)
.join('');
/* 4. 渲染表头 */
const headRow = document.getElementById('detailHeadRow');
headRow.innerHTML = '<th></th>' + dates.map(d => `<th>${d}</th>`).join('');
/* 5. 渲染星期、计划、实际三行 */
const tbody = document.querySelector('#detailTable tbody');
tbody.innerHTML =
`<tr id="weekRow"><td><b>星期</b></td>${weekRowHTML}</tr>` +
`<tr id="plannedRow"><td><b>计划交付</b></td>${dates
.map(
d =>
`<td><input type="number" min="0" value="${saved[d]?.plannedQty || 0}" data-date="${d}" data-type="planned"></td>`
)
.join('')}</tr>` +
`<tr id="actualRow"><td><b>实际交付</b></td>${dates
.map(
d =>
`<td><input type="number" min="0" value="${saved[d]?.actualQty || 0}" data-date="${d}" data-type="actual"></td>`
)
.join('')}</tr>`;
/* 6. 显示弹窗 */
document.getElementById('detailMask').style.display = 'block';
document.getElementById('detailWrap').style.display = 'block';
}
function closeDetail(){
document.getElementById('detailMask').style.display='none';
document.getElementById('detailWrap').style.display='none';
}
async function saveDetail(){
const inputs = document.querySelectorAll('#detailTable input');
const payload = {};
inputs.forEach(inp=>{
const date = inp.dataset.date;
const type = inp.dataset.type;
if(!payload[date]) payload[date] = {date, plannedQty:0, actualQty:0};
payload[date][type] = parseInt(inp.value) || 0;
});
await fetch(`/api/production_plans/${curDetailPlanId}/details`,{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify(Object.values(payload))
});
alert('已保存');
closeDetail();
}
/* ================= 初始化 ================= */
loadPlans('',1);
</script>
</body>
</html>这是我的前端界面,请帮我实现点击查看详情按钮后的后端接口,要求当录入数据并保存后上面的日期就不会随着时间变更
最新发布