594 lines
24 KiB
HTML
594 lines
24 KiB
HTML
<!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>
|
||
|