someone-oa/pc/tool-output-calculator.html
2025-12-11 18:08:46 +08:00

594 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>产值计算器 - OA系统</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
background: #f0f2f5;
overflow: hidden;
}
.layout-container {
height: 100vh;
display: flex;
flex-direction: column;
}
.navbar {
height: 50px;
background: #fff;
border-bottom: 1px solid #e6e6e6;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
}
.navbar-left {
display: flex;
align-items: center;
}
.logo {
font-size: 20px;
font-weight: bold;
color: #409EFF;
cursor: pointer;
}
.nav-menu { display: flex; gap: 8px; margin-left: 16px; }
.nav-item { padding: 8px 16px; cursor: pointer; border-radius: 4px; color: #303133; }
.nav-item:hover { background: #ecf5ff; color: #409EFF; }
.nav-item.active { background: #409EFF; color: #fff; }
.navbar-right {
display: flex;
align-items: center;
gap: 20px;
}
.main-container {
flex: 1;
display: flex;
overflow: hidden;
}
.app-main {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
background: #f0f2f5;
}
.content {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #fff;
margin: 10px;
border-radius: 4px;
}
.page-header {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.page-title {
font-size: 20px;
font-weight: 500;
color: #303133;
}
.page-desc {
font-size: 14px;
color: #909399;
margin-top: 8px;
}
.form-card {
background: white;
border-radius: 4px;
padding: 24px;
margin-bottom: 20px;
}
.form-section {
margin-bottom: 24px;
}
.form-section-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.form-row {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.form-item {
flex: 1;
}
.form-label {
display: block;
margin-bottom: 8px;
color: #333;
font-size: 14px;
}
.form-label .required {
color: #ff4d4f;
}
.form-input, .form-select {
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
}
.form-input:focus, .form-select:focus {
outline: none;
border-color: #409EFF;
}
.work-content-list {
margin-top: 16px;
}
.work-content-item {
display: flex;
gap: 12px;
align-items: center;
padding: 12px;
background: #f5f7fa;
border-radius: 4px;
margin-bottom: 12px;
}
.work-content-item .form-item {
flex: 1;
}
.work-content-item .form-item:first-child {
flex: 0 0 200px;
}
.work-content-item .form-item:last-child {
flex: 0 0 150px;
}
.result-table {
width: 100%;
border-collapse: collapse;
margin-top: 16px;
}
.result-table th,
.result-table td {
padding: 12px;
text-align: left;
border: 1px solid #f0f0f0;
}
.result-table th {
background: #fafafa;
font-weight: 500;
}
.result-table .total-row {
background: #f0f5ff;
font-weight: 500;
}
.result-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 24px;
border-radius: 8px;
margin-top: 24px;
}
.result-label {
font-size: 14px;
opacity: 0.9;
margin-bottom: 8px;
}
.result-value {
font-size: 32px;
font-weight: bold;
}
.btn {
padding: 8px 24px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #409EFF;
color: white;
}
.btn-primary:hover {
background: #66b1ff;
}
.btn-default {
background: white;
color: #333;
border: 1px solid #d9d9d9;
}
.btn-default:hover {
border-color: #409EFF;
color: #409EFF;
}
.btn-danger {
background: #ff4d4f;
color: white;
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #f0f0f0;
}
.help-card {
background: #f0f5ff;
border: 1px solid #adc6ff;
border-radius: 4px;
padding: 16px;
margin-top: 20px;
}
.help-title {
font-weight: 500;
color: #1890ff;
margin-bottom: 12px;
}
.help-item {
font-size: 13px;
color: #666;
margin-bottom: 8px;
line-height: 1.6;
}
</style>
<!-- Vue.js -->
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<!-- Element UI JS -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<div id="app">
<div class="layout-container">
<div class="navbar">
<div class="navbar-left">
<div class="logo" onclick="window.location.href='dashboard.html'">OA系统</div>
<div class="nav-menu">
<div class="nav-item" onclick="window.location.href='dashboard.html'">首页</div>
<div class="nav-item" onclick="window.location.href='project-initiation.html'">商机管理</div>
<div class="nav-item" onclick="window.location.href='project-start.html'">过程管理</div>
<div class="nav-item" onclick="window.location.href='finance-invoice.html'">财务管理</div>
<div class="nav-item" onclick="window.location.href='report-project-detail.html'">报表管理</div>
<div class="nav-item active">工具</div>
</div>
</div>
<div class="navbar-right">
<span style="color: #303133;">张三</span><span style="color: #909399; margin: 0 8px;">|</span>
<a href="profile.html" style="color: #409EFF; text-decoration: none;">个人中心</a>
<span>|</span>
<a href="login.html" style="color: #409EFF; text-decoration: none;">退出</a>
</div>
</div>
<div class="main-container">
<div class="app-main">
<div class="content">
<div class="page-header">
<div>
<div class="page-title">产值计算器</div>
<div class="page-desc">根据服务类型和工作内容,快速计算项目产值</div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-default" onclick="window.location.href='tool-center.html'" style="font-size: 13px;">工具中心</button>
<button class="btn btn-default" onclick="window.location.href='tool-fee-calculator.html'" style="font-size: 13px;">切换到费用计算器</button>
</div>
</div>
<div class="form-card">
<div class="form-section">
<div class="form-section-title">计算参数</div>
<div class="form-row">
<div class="form-item">
<label class="form-label">
<span class="required">*</span>服务类型
</label>
<select id="serviceType" class="form-select" onchange="onServiceTypeChange()">
<option value="">请选择服务类型</option>
<option value="结算审计、概算编制及审核、清单控制价编制及审核">结算审计、概算编制及审核、清单控制价编制及审核</option>
<option value="工程咨询">工程咨询</option>
<option value="跟踪审计">跟踪审计</option>
<option value="项目管理">项目管理</option>
<option value="工程监理">工程监理</option>
</select>
</div>
<div class="form-item">
<label class="form-label">
<span class="required">*</span>成果金额(元)
</label>
<input type="number" id="outputAmount" class="form-input" placeholder="请输入成果金额" oninput="calculateOutput()">
</div>
</div>
</div>
<div class="form-section">
<div class="form-section-title">工作内容产值计算</div>
<div id="workContentList" class="work-content-list">
<!-- 工作内容列表将动态生成 -->
</div>
<button class="btn btn-default" onclick="addWorkContent()" style="margin-top: 12px;">+ 添加工作内容</button>
</div>
<div class="result-box">
<div class="result-label">总产值</div>
<div class="result-value" id="totalOutput">¥0.00</div>
</div>
<div id="resultTable" style="display: none; margin-top: 24px;">
<table class="result-table">
<thead>
<tr>
<th>工作内容</th>
<th>完成比例</th>
<th>成果金额</th>
<th>产值金额</th>
</tr>
</thead>
<tbody id="resultTableBody">
<!-- 结果表格将动态生成 -->
</tbody>
</table>
</div>
</div>
<div class="help-card">
<div class="help-title">📊 产值计算公式说明</div>
<div class="help-item">• 结算审计、概算编制及审核、清单控制价编制及审核计量30%计价30%对账10%出报告30%</div>
<div class="help-item">• 工程咨询文本30%估算20%出报告50%</div>
<div class="help-item">• 跟踪审计现场跟踪60%进度款审核40%</div>
<div class="help-item">• 项目管理项目协助甲方报批报建40%项目实施过程中管控60%</div>
<div class="help-item">• 工程监理现场跟踪60%进度审核40%</div>
<div style="margin-top: 12px; color: #1890ff; font-weight: 500;">💡 产值金额 = 成果金额 × 对应工作内容的完成比例</div>
</div>
<div class="form-actions">
<button class="btn btn-default" onclick="resetForm()">重置</button>
<button class="btn btn-primary" onclick="exportResult()">导出结果</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="common.js"></script>
<script>
// 工作内容与比例映射
const workContentRatioMap = {
'结算审计、概算编制及审核、清单控制价编制及审核': {
'计量': 0.3,
'计价': 0.3,
'对账': 0.1,
'出报告': 0.3
},
'工程咨询': {
'文本': 0.3,
'估算': 0.2,
'出报告': 0.5
},
'跟踪审计': {
'现场跟踪': 0.6,
'进度款审核': 0.4
},
'项目管理': {
'项目协助甲方报批报建': 0.4,
'项目实施过程中管控': 0.6
},
'工程监理': {
'现场跟踪': 0.6,
'进度审核': 0.4
}
};
let workContentCount = 0;
// 服务类型变化事件
function onServiceTypeChange() {
const serviceType = document.getElementById('serviceType').value;
const workContentList = document.getElementById('workContentList');
workContentList.innerHTML = '';
workContentCount = 0;
if (serviceType && workContentRatioMap[serviceType]) {
const workContents = Object.keys(workContentRatioMap[serviceType]);
workContents.forEach(content => {
addWorkContentItem(content, workContentRatioMap[serviceType][content]);
});
}
calculateOutput();
}
// 添加工作内容项
function addWorkContent(content = null, ratio = null) {
const serviceType = document.getElementById('serviceType').value;
if (!serviceType) {
CommonUtils.showMessage('请先选择服务类型', 'warning');
return;
}
if (content && ratio !== null) {
addWorkContentItem(content, ratio);
} else {
const workContents = Object.keys(workContentRatioMap[serviceType]);
if (workContents.length > 0) {
addWorkContentItem(workContents[0], workContentRatioMap[serviceType][workContents[0]]);
}
}
}
// 添加工作内容项(内部方法)
function addWorkContentItem(content, ratio) {
const workContentList = document.getElementById('workContentList');
const serviceType = document.getElementById('serviceType').value;
const workContents = serviceType ? Object.keys(workContentRatioMap[serviceType]) : [];
const item = document.createElement('div');
item.className = 'work-content-item';
item.id = 'workContent_' + workContentCount;
let optionsHtml = '';
if (serviceType && workContentRatioMap[serviceType]) {
Object.keys(workContentRatioMap[serviceType]).forEach(key => {
const selected = key === content ? 'selected' : '';
optionsHtml += `<option value="${key}" ${selected}>${key}</option>`;
});
}
item.innerHTML = `
<div class="form-item">
<select class="form-select work-content-select" onchange="onWorkContentChange(${workContentCount})">
${optionsHtml || '<option>请先选择服务类型</option>'}
</select>
</div>
<div class="form-item">
<input type="text" class="form-input" value="${(ratio * 100).toFixed(0)}%" readonly style="background: #f5f5f5;">
</div>
<div class="form-item">
<input type="text" class="form-input output-amount" value="0" readonly style="background: #f5f5f5;">
</div>
<button class="btn btn-danger" onclick="removeWorkContent(${workContentCount})">删除</button>
`;
workContentList.appendChild(item);
workContentCount++;
calculateOutput();
}
// 工作内容变化事件
function onWorkContentChange(index) {
calculateOutput();
}
// 删除工作内容
function removeWorkContent(index) {
const item = document.getElementById('workContent_' + index);
if (item) {
item.remove();
calculateOutput();
}
}
// 计算产值
function calculateOutput() {
const outputAmount = parseFloat(document.getElementById('outputAmount').value) || 0;
const serviceType = document.getElementById('serviceType').value;
const resultTable = document.getElementById('resultTable');
const resultTableBody = document.getElementById('resultTableBody');
const totalOutputEl = document.getElementById('totalOutput');
if (!serviceType || outputAmount === 0) {
resultTable.style.display = 'none';
totalOutputEl.textContent = '¥0.00';
return;
}
const workContentItems = document.querySelectorAll('.work-content-item');
let totalOutput = 0;
let tableRows = '';
workContentItems.forEach((item, index) => {
const select = item.querySelector('.work-content-select');
const workContent = select.value;
const ratio = workContentRatioMap[serviceType] && workContentRatioMap[serviceType][workContent] ? workContentRatioMap[serviceType][workContent] : 0;
const output = outputAmount * ratio;
totalOutput += output;
// 更新显示
const ratioInput = item.querySelectorAll('.form-input')[0];
const outputInput = item.querySelectorAll('.form-input')[1];
ratioInput.value = (ratio * 100).toFixed(0) + '%';
outputInput.value = '¥' + output.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
// 表格行
tableRows += `
<tr>
<td>${workContent}</td>
<td>${(ratio * 100).toFixed(0)}%</td>
<td>¥${outputAmount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
<td>¥${output.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
</tr>
`;
});
// 添加总计行
tableRows += `
<tr class="total-row">
<td colspan="3"><strong>总计</strong></td>
<td><strong>¥${totalOutput.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</strong></td>
</tr>
`;
resultTableBody.innerHTML = tableRows;
resultTable.style.display = 'block';
totalOutputEl.textContent = '¥' + totalOutput.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
// 重置表单
function resetForm() {
CommonUtils.confirm('确认重置表单?', () => {
document.getElementById('serviceType').value = '';
document.getElementById('outputAmount').value = '';
document.getElementById('workContentList').innerHTML = '';
document.getElementById('resultTable').style.display = 'none';
document.getElementById('totalOutput').textContent = '¥0.00';
workContentCount = 0;
});
}
// 导出结果
function exportResult() {
const serviceType = document.getElementById('serviceType').value;
const outputAmount = document.getElementById('outputAmount').value;
const totalOutput = document.getElementById('totalOutput').textContent;
if (!serviceType || !outputAmount) {
CommonUtils.showMessage('请先完成计算', 'warning');
return;
}
const resultTableBody = document.getElementById('resultTableBody');
const rows = resultTableBody.querySelectorAll('tr');
let text = '产值计算结果\n';
text += '='.repeat(40) + '\n';
text += '服务类型:' + serviceType + '\n';
text += '成果金额:¥' + parseFloat(outputAmount).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '\n';
text += '总产值:' + totalOutput + '\n';
text += '\n详细明细\n';
text += '-'.repeat(40) + '\n';
rows.forEach(row => {
const cells = row.querySelectorAll('td');
if (cells.length >= 4) {
text += cells[0].textContent + ' | ';
text += cells[1].textContent + ' | ';
text += cells[2].textContent + ' | ';
text += cells[3].textContent + '\n';
}
});
text += '\n计算时间' + new Date().toLocaleString('zh-CN') + '\n';
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '产值计算结果_' + new Date().getTime() + '.txt';
a.click();
URL.revokeObjectURL(url);
CommonUtils.showMessage('结果已导出');
}
</script>
</body>
</html>