someone-oa/pc/project-board.html
2025-12-11 19:04:46 +08:00

521 lines
20 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">
<!-- 统一布局样式 -->
<link rel="stylesheet" href="unified-layout.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: center;
}
.page-title {
font-size: 20px;
font-weight: 500;
color: #303133;
}
.view-switch {
display: flex;
gap: 8px;
}
.view-btn {
padding: 6px 16px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background: white;
color: #606266;
cursor: pointer;
font-size: 14px;
}
.view-btn.active {
background: #409EFF;
color: white;
border-color: #409EFF;
}
.board-container {
display: flex;
gap: 16px;
overflow-x: auto;
padding-bottom: 20px;
}
.board-column {
flex: 1;
min-width: 280px;
background: #f5f7fa;
border-radius: 8px;
padding: 16px;
}
.column-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #e4e7ed;
}
.column-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
.column-count {
background: #409EFF;
color: white;
border-radius: 12px;
padding: 2px 8px;
font-size: 12px;
}
.project-card {
background: white;
border-radius: 6px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
cursor: move;
transition: all 0.3s;
}
.project-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
transform: translateY(-2px);
}
.project-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.project-number {
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.project-name {
font-size: 15px;
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.project-info {
font-size: 13px;
color: #666;
line-height: 1.6;
}
.project-info-item {
margin-bottom: 6px;
}
.project-amount {
font-size: 16px;
font-weight: 600;
color: #409EFF;
margin-top: 8px;
}
.project-actions {
display: flex;
gap: 8px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
}
.action-btn {
flex: 1;
padding: 6px 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background: white;
color: #606266;
cursor: pointer;
font-size: 12px;
text-align: center;
}
.action-btn:hover {
border-color: #409EFF;
color: #409EFF;
}
.search-bar {
background: #fafafa;
padding: 16px;
border-radius: 4px;
margin-bottom: 16px;
}
.search-row {
display: flex;
gap: 12px;
align-items: center;
}
.form-input, .form-select {
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
}
.form-input {
flex: 1;
}
.form-select {
width: 150px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.btn-primary {
background: #409EFF;
color: white;
}
.btn-default {
background: white;
color: #333;
border: 1px solid #d9d9d9;
}
.empty-column {
text-align: center;
padding: 40px 20px;
color: #909399;
font-size: 14px;
}
</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>
<!-- SortableJS for drag and drop -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.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 active">商机管理</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>
</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 class="page-title">项目看板</div>
<div style="display: flex; gap: 12px; align-items: center;">
<div class="view-switch">
<div class="view-btn active" onclick="switchView('board')">看板视图</div>
<div class="view-btn" onclick="switchView('list')">列表视图</div>
</div>
<button class="btn btn-primary" onclick="window.location.href='project-list.html'">📋 切换到列表</button>
</div>
</div>
<div class="search-bar">
<div class="search-row">
<input type="text" class="form-input" placeholder="搜索项目编号、项目名称" id="searchInput">
<select class="form-select" id="filterService">
<option value="">全部服务类型</option>
<option value="工程咨询">工程咨询</option>
<option value="跟踪审计">跟踪审计</option>
<option value="结算审计">结算审计</option>
<option value="项目管理">项目管理</option>
<option value="工程监理">工程监理</option>
</select>
<button class="btn btn-primary" onclick="searchProjects()">查询</button>
<button class="btn btn-default" onclick="resetSearch()">重置</button>
</div>
</div>
<div class="board-container" id="boardContainer">
<!-- 已立项 -->
<div class="board-column" data-status="initiated">
<div class="column-header">
<div class="column-title">已立项</div>
<div class="column-count" id="count-initiated">0</div>
</div>
<div class="column-content" id="column-initiated">
<!-- 项目卡片将动态生成 -->
</div>
</div>
<!-- 投标中 -->
<div class="board-column" data-status="bidding">
<div class="column-header">
<div class="column-title">投标中</div>
<div class="column-count" id="count-bidding">0</div>
</div>
<div class="column-content" id="column-bidding"></div>
</div>
<!-- 已中标 -->
<div class="board-column" data-status="won">
<div class="column-header">
<div class="column-title">已中标</div>
<div class="column-count" id="count-won">0</div>
</div>
<div class="column-content" id="column-won"></div>
</div>
<!-- 已签合同 -->
<div class="board-column" data-status="contract">
<div class="column-header">
<div class="column-title">已签合同</div>
<div class="column-count" id="count-contract">0</div>
</div>
<div class="column-content" id="column-contract"></div>
</div>
<!-- 已启动 -->
<div class="board-column" data-status="started">
<div class="column-header">
<div class="column-title">已启动</div>
<div class="column-count" id="count-started">0</div>
</div>
<div class="column-content" id="column-started"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="common.js"></script>
<script>
// 项目数据(模拟数据,实际应从后端获取)
const projects = [
{ id: '25-1', name: 'XX工程项目', customer: 'XX公司', serviceType: '工程咨询', amount: '¥500,000', status: 'started', owner: '张三' },
{ id: '25-2', name: 'YY工程项目', customer: 'YY公司', serviceType: '跟踪审计', amount: '¥800,000', status: 'bidding', owner: '李四' },
{ id: '25-3', name: 'ZZ工程项目', customer: 'ZZ公司', serviceType: '结算审计', amount: '¥600,000', status: 'contract', owner: '王五' },
{ id: '25-4', name: 'AA工程项目', customer: 'AA公司', serviceType: '工程监理', amount: '¥1,200,000', status: 'won', owner: '赵六' },
{ id: '25-5', name: 'BB工程项目', customer: 'BB公司', serviceType: '项目管理', amount: '¥900,000', status: 'initiated', owner: '孙七' },
{ id: '25-6', name: 'CC工程项目', customer: 'CC公司', serviceType: '工程咨询', amount: '¥450,000', status: 'initiated', owner: '周八' }
];
// 渲染看板
function renderBoard() {
// 清空所有列
document.querySelectorAll('.column-content').forEach(col => col.innerHTML = '');
// 按状态分组
const statusGroups = {
'initiated': [],
'bidding': [],
'won': [],
'contract': [],
'started': []
};
projects.forEach(project => {
if (statusGroups[project.status]) {
statusGroups[project.status].push(project);
}
});
// 渲染每个列
Object.keys(statusGroups).forEach(status => {
const column = document.getElementById('column-' + status);
const count = statusGroups[status].length;
document.getElementById('count-' + status).textContent = count;
if (count === 0) {
column.innerHTML = '<div class="empty-column">暂无项目</div>';
} else {
statusGroups[status].forEach(project => {
const card = createProjectCard(project);
column.appendChild(card);
});
}
// 初始化拖拽
initSortable(column, status);
});
}
// 创建项目卡片
function createProjectCard(project) {
const card = document.createElement('div');
card.className = 'project-card';
card.draggable = true;
card.dataset.projectId = project.id;
card.innerHTML = `
<div class="project-card-header">
<div style="flex: 1;">
<div class="project-number">${project.id}</div>
<div class="project-name">${project.name}</div>
</div>
</div>
<div class="project-info">
<div class="project-info-item">客户:${project.customer}</div>
<div class="project-info-item">服务类型:${project.serviceType}</div>
<div class="project-info-item">负责人:${project.owner}</div>
<div class="project-amount">${project.amount}</div>
</div>
<div class="project-actions">
<div class="action-btn" onclick="viewProject('${project.id}')">查看</div>
<div class="action-btn" onclick="editProject('${project.id}')">编辑</div>
</div>
`;
return card;
}
// 初始化拖拽功能
function initSortable(column, status) {
new Sortable(column, {
group: 'projects',
animation: 150,
onEnd: function(evt) {
const projectId = evt.item.dataset.projectId;
const newStatus = evt.to.closest('.board-column').dataset.status;
updateProjectStatus(projectId, newStatus);
}
});
}
// 更新项目状态
function updateProjectStatus(projectId, newStatus) {
const project = projects.find(p => p.id === projectId);
if (project) {
project.status = newStatus;
CommonUtils.showMessage(`项目 ${projectId} 状态已更新为:${getStatusName(newStatus)}`);
renderBoard();
}
}
// 获取状态名称
function getStatusName(status) {
const statusMap = {
'initiated': '已立项',
'bidding': '投标中',
'won': '已中标',
'contract': '已签合同',
'started': '已启动'
};
return statusMap[status] || status;
}
// 查看项目
function viewProject(projectId) {
window.location.href = 'project-detail.html?id=' + projectId;
}
// 编辑项目
function editProject(projectId) {
window.location.href = 'project-list.html?edit=' + projectId;
}
// 切换视图
function switchView(view) {
document.querySelectorAll('.view-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
if (view === 'list') {
window.location.href = 'project-list.html';
}
}
// 搜索项目
function searchProjects() {
const keyword = document.getElementById('searchInput').value.toLowerCase();
const serviceType = document.getElementById('filterService').value;
// 实际应用中应该调用后端API
CommonUtils.showMessage('搜索功能:' + (keyword || '全部') + (serviceType ? ' | ' + serviceType : ''));
renderBoard();
}
// 重置搜索
function resetSearch() {
document.getElementById('searchInput').value = '';
document.getElementById('filterService').value = '';
renderBoard();
}
// 页面加载时初始化
window.onload = function() {
renderBoard();
};
</script>
<script src="unified-layout.js"></script>
<script>
initUnifiedLayout('tool');
</script>
</body>
</html>