/** * 项目管理系统前端(依据产品文档、API 文档、交互文档) * 对接 http://127.0.0.1:8000,登录 / 列表 / 详情 / 新建 / 保存 / 操作日志 */ (function () { 'use strict'; var PAGE_SIZE = 20; var currentPage = 1; var totalCount = 0; var currentProjectId = null; var viewLogin = document.getElementById('view-login'); var appMain = document.getElementById('appMain'); var loginForm = document.getElementById('loginForm'); var loginError = document.getElementById('loginError'); var btnLoginSubmit = document.getElementById('btnLoginSubmit'); var btnLogout = document.getElementById('btnLogout'); var userInfo = document.getElementById('userInfo'); var btnNewProject = document.getElementById('btnNewProject'); var navList = document.getElementById('navList'); var viewList = document.getElementById('view-list'); var viewDetail = document.getElementById('view-detail'); var searchType = document.getElementById('searchType'); var searchKeyword = document.getElementById('searchKeyword'); var btnSearch = document.getElementById('btnSearch'); var filterProgress = document.getElementById('filterProgress'); var filterCost = document.getElementById('filterCost'); var dateFilterType = document.getElementById('dateFilterType'); var dateFrom = document.getElementById('dateFrom'); var dateTo = document.getElementById('dateTo'); var dateAbnormal = document.getElementById('dateAbnormal'); var listLoading = document.getElementById('listLoading'); var listEmpty = document.getElementById('listEmpty'); var projectTable = document.getElementById('projectTable'); var projectTableBody = document.getElementById('projectTableBody'); var paginationInfo = document.getElementById('paginationInfo'); var btnPrev = document.getElementById('btnPrev'); var btnNext = document.getElementById('btnNext'); var btnBack = document.getElementById('btnBack'); var detailTitle = document.getElementById('detailTitle'); var detailLoading = document.getElementById('detailLoading'); var detailContent = document.getElementById('detailContent'); var btnViewLog = document.getElementById('btnViewLog'); var btnSave = document.getElementById('btnSave'); var logOverlay = document.getElementById('logOverlay'); var logDrawer = document.getElementById('logDrawer'); var logDrawerClose = document.getElementById('logDrawerClose'); var logLoading = document.getElementById('logLoading'); var logEmpty = document.getElementById('logEmpty'); var logList = document.getElementById('logList'); var toast = document.getElementById('toast'); function showLogin() { if (viewLogin) viewLogin.classList.remove('hidden'); if (appMain) appMain.style.display = 'none'; } function showApp() { if (viewLogin) viewLogin.classList.add('hidden'); if (appMain) appMain.style.display = 'flex'; var u = window.API && window.API.getUser ? window.API.getUser() : null; if (userInfo && u) userInfo.textContent = (u.displayName || u.username || '') + ' · ' + (u.role || ''); updateNewProjectVisibility(); loadList(); } window.onAuthRequired = function () { showLogin(); }; function isMarketRole() { var u = window.API && window.API.getUser ? window.API.getUser() : null; return u && u.role === '市场部'; } function updateNewProjectVisibility() { if (btnNewProject) btnNewProject.style.display = isMarketRole() ? '' : 'none'; } function showToast(message, type) { if (!toast) return; toast.textContent = message; toast.className = 'toast visible' + (type ? ' ' + type : ''); clearTimeout(toast._tid); toast._tid = setTimeout(function () { toast.classList.remove('visible'); }, 2500); } function showList() { if (viewList) viewList.classList.add('active'); if (viewDetail) viewDetail.classList.remove('active'); if (navList) navList.classList.add('active'); document.querySelectorAll('.nav-item').forEach(function (n) { n.classList.remove('active'); }); if (navList) navList.classList.add('active'); } function showDetail(projectId) { currentProjectId = projectId; if (viewList) viewList.classList.remove('active'); if (viewDetail) viewDetail.classList.add('active'); document.querySelectorAll('.nav-item').forEach(function (n) { n.classList.remove('active'); }); if (detailTitle) detailTitle.textContent = projectId ? '项目详情' : '新建项目'; if (detailLoading) detailLoading.style.display = 'block'; if (detailContent) detailContent.style.display = 'none'; if (projectId) { window.API.post('/api/projects/detail', { id: projectId }).then(function (res) { if (detailLoading) detailLoading.style.display = 'none'; if (detailContent) detailContent.style.display = 'block'; if (res.ok && res.data) { fillDetailForm(res.data); } else { showToast(res.message || '加载失败', 'error'); } }); } else { if (detailLoading) detailLoading.style.display = 'none'; if (detailContent) detailContent.style.display = 'block'; clearDetailForm(); } } function getFormDataBySection(sectionName) { var panel = document.querySelector('.tab-panel[id="panel-' + sectionName + '"]'); if (!panel) return {}; var grid = panel.querySelector('[data-section="' + sectionName + '"]'); if (!grid) return {}; var obj = {}; grid.querySelectorAll('[data-field]').forEach(function (el) { var key = el.getAttribute('data-field'); if (!key) return; var val = el.value; if (el.type === 'number' && val !== '') val = Number(val); if (val !== '' && val !== undefined) obj[key] = val; }); return obj; } function fillDetailForm(project) { function setSection(sectionName, data) { if (!data) return; var panel = document.querySelector('.tab-panel[id="panel-' + sectionName + '"]'); if (!panel) return; var grid = panel.querySelector('[data-section="' + sectionName + '"]'); if (!grid) return; grid.querySelectorAll('[data-field]').forEach(function (el) { var key = el.getAttribute('data-field'); if (!key || data[key] === undefined) return; var v = data[key]; if (v !== null && v !== undefined) el.value = v; else el.value = ''; }); } setSection('contract', project.contract); setSection('costControl', project.costControl); setSection('receivable', project.receivable); setSection('payable', project.payable); setSection('other', project.other); } function clearDetailForm() { ['contract', 'costControl', 'receivable', 'payable', 'other'].forEach(function (sectionName) { var panel = document.querySelector('.tab-panel[id="panel-' + sectionName + '"]'); if (!panel) return; var grid = panel.querySelector('[data-section="' + sectionName + '"]'); if (!grid) return; grid.querySelectorAll('input, select, textarea').forEach(function (el) { if (el.type === 'checkbox' || el.type === 'radio') el.checked = false; else el.value = ''; }); }); } function collectPayload() { var contract = getFormDataBySection('contract'); var costControl = getFormDataBySection('costControl'); var receivable = getFormDataBySection('receivable'); var payable = getFormDataBySection('payable'); var other = getFormDataBySection('other'); return { contract: contract, costControl: costControl, receivable: receivable, payable: payable, other: other }; } function loadList() { if (listLoading) listLoading.style.display = 'block'; if (listEmpty) listEmpty.style.display = 'none'; if (projectTable) projectTable.style.visibility = 'hidden'; var body = { page: currentPage, pageSize: PAGE_SIZE, searchType: searchType && searchType.value ? searchType.value : undefined, keyword: searchKeyword && searchKeyword.value ? searchKeyword.value.trim() : undefined, progress: filterProgress && filterProgress.value ? filterProgress.value : undefined, cost: filterCost && filterCost.value ? filterCost.value : undefined, dateFilterType: dateFilterType && dateFilterType.value ? dateFilterType.value : undefined, dateFrom: dateFrom && dateFrom.value ? dateFrom.value : undefined, dateTo: dateTo && dateTo.value ? dateTo.value : undefined, dateAbnormal: dateAbnormal && dateAbnormal.checked ? true : undefined, }; if (!body.keyword) body.searchType = undefined; if (!body.dateFilterType) body.dateFrom = body.dateTo = undefined; window.API.post('/api/projects/list', body).then(function (res) { if (listLoading) listLoading.style.display = 'none'; if (!res.ok) { showToast(res.message || '加载失败', 'error'); if (listEmpty) listEmpty.style.display = 'flex'; if (listEmpty) listEmpty.querySelector('p').textContent = res.message || '加载失败'; return; } var list = (res.data && res.data.list) || []; totalCount = (res.data && res.data.total) || 0; if (list.length === 0) { if (listEmpty) listEmpty.style.display = 'flex'; if (listEmpty) listEmpty.querySelector('p').textContent = '暂无项目数据'; if (projectTable) projectTable.style.visibility = 'hidden'; } else { if (listEmpty) listEmpty.style.display = 'none'; if (projectTable) projectTable.style.visibility = 'visible'; var html = list .map(function (p) { var updatedAt = p.updatedAt ? p.updatedAt.slice(0, 10) : '—'; return ( '