2025-06-25 11:52:01 +08:00

1684 lines
75 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.

{extend name="../../base/view/common/base" /}
{block name="style"}
<style>
.layui-timeline.pt-2 .layui-timeline-item {
padding-bottom: 12px;
text-decoration: line-through;
color: #acacac;
padding-bottom: 10px;
}
.layui-timeline.pt-2 .layui-timeline-item.delete-0 {
text-decoration: none;
color: #323232;
}
.check-items {
overflow-x: auto;
padding: 2px 0;
}
.flow-flex-row {
box-direction: row;
box-orient: horizontal;
-webkit-box-orient: horizontal;
-ms-flex-direction: row;
flex-direction: row;
}
.flow-flexbox {
width: 100%;
text-align: left;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
display: -webkit-flex;
box-align: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
flex-wrap: wrap;
}
.check-item {
width: auto;
-ms-flex-negative: 0;
flex-shrink: 0;
padding: 4px 0;
}
.check-item i {
font-size: 20px;
margin-right: 3px;
}
.check-item span {
color: #999;
margin: 0 1px;
}
.layui-icon[data-ok] {
color: #34a853
}
.layui-icon[data-no] {
color: #FF5722;
}
.layui-icon[data-on] {
color: #4285f4;
}
.check-item-status {
color: #969696;
font-size: 12px;
margin-left: 3px;
}
.check-item:last-child .layui-icon-right {
display: none;
}
</style>
{/block}
{block name="breadcrumb"}
<span class="layui-breadcrumb">
<a href="http://www.meteteme.com/" target="_blank">江苏美天科技</a>
<a href="/project/index/index">项目中心</a>
<a><cite>项目详情</cite></a>
</span>
{/block}
<!-- 主体 -->
{block name="body"}
<div class="main-content" style="min-height:100%; height:auto;">
{include file="/index/submenu" /}
<div id="pageBox" class="main-page-content p-3" style="background-color:#F5F8FA;padding-bottom: 0;">
<div class="layui-row">
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header" style="height:45px;">
<div class="layui-row">
<div class="layui-col-md9">
<h4 class="hover-edit"><span id="name_{$detail.id}" data-val="">{$detail.name}</span><i
class="iconfont icon-wodedianping" title="编辑" data-id="{$detail.id}"
data-name="name"></i></h4>
</div>
<div class="layui-col-md3" style="text-align:right">
{eq name="$role" value="2"}
<button type="button" class="layui-btn layui-btn-danger layui-btn-sm"
id="delProject">删除项目
</button>
<button type="button" class="layui-btn layui-btn-sm layui-bg-blue" id="editProject">编辑项目
</button>
{eq name="$detail.status" value="4"}
<button type="button" class="layui-btn layui-btn-normal layui-btn-sm"
id="reopenProject">开启项目
</button>
{else/}
<button type="button" class="layui-btn layui-btn-waring layui-btn-sm"
id="closeProject">关闭项目
</button>
{/eq}
{/eq}
</div>
</div>
</div>
<div class='layui-card-body {$role=="0"?"hover-view":"hover-edit"}'>
<div class="py-1">
<span class="font-gray">项目ID</span>
<span id="{$detail.id}" data-val="{$detail.id}">{$detail.id}</span>
<input type="hidden" name="id" value="{$detail.id}">
<!-- <i class="iconfont icon-wodedianping" title="编辑" data-id="{$detail.id}"-->
<!-- data-name="id"></i>-->
<!-- <button data-id="{$detail.id}" data-name="business_id">编辑</button> -->
</div>
<hr>
<div class="py-1">
<span class="font-gray">客户名称:</span>
<span id="business_id_{$detail.id}"
data-val="{$detail.business_id}">{$detail.business_id}</span>
<!-- <i class="iconfont icon-wodedianping" title="编辑" data-id="{$detail.id}"-->
<!-- data-name="business_id"></i>-->
<!-- <button data-id="{$detail.id}" data-name="business_id">编辑</button> -->
</div>
<hr>
<div class="py-1">
<span class="font-gray">合同周期:</span>
<span id="start_time_{$detail.id}" data-val="">{$detail.start_time|date='Y-m-d'}</span>
~&nbsp;&nbsp; <span id="end_time_{$detail.id}" data-val="">{$detail.end_time|
date='Y-m-d'}</span>
</div>
<hr>
<div class="py-1">
<span class="font-gray">项目周期:</span>
<span id="project_start_time_{$detail.id}"
data-val="">{$detail.project_start_time|date='Y-m-d'}</span>
~&nbsp;&nbsp;
<span id="project_end_time_{$detail.id}" data-val="">
{if $detail.project_end_time == '1970-01-01' || $detail.project_end_time == 0 ||
$detail.project_end_time == ''}
至今
{else}
{$detail.project_end_time|date='Y-m-d'}
{/if}
</span>
</div>
<hr>
<div class="py-1">
<span class="font-gray">创建人:</span>{$detail.admin_name}
<span class="font-gray" style="margin-left:32px">负责人:</span>
<span id="director_uid_{$detail.id}"
data-val="{$detail.director_uid}">{$detail.director_name}</span>
<!-- <i class="iconfont icon-wodedianping" title="编辑" data-id="{$detail.id}"-->
<!-- data-name="director_uid"></i>-->
<span class="font-gray" style="margin-left:32px">项目成员:</span>{$detail.team_admin_names}
</div>
<hr>
<div class="py-1">
<span class="font-gray">项目简介:</span>
<span id="content_{$detail.id}" data-val="{$detail.content}">{$detail.content}</span>
<!-- <i class="iconfont icon-wodedianping" title="编辑" data-id="{$detail.id}"-->
<!-- data-name="content"></i>-->
</div>
</div>
</div>
</div>
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header" style="height:45px;">
<div class="layui-row">
<div class="layui-col-md6">
<h4>项目阶段</h4>
</div>
{gt name="$role" value="0"}
{neq name="$detail.status" value="4"}
<div class="layui-col-md6" style="margin-top:10px">
<span data-href="/project/index/editstep/id/{$detail.id}"
class="layui-btn layui-btn-normal layui-btn-sm open-a fr">编辑项目阶段</span>
</div>
{/neq}
{/gt}
</div>
</div>
{notempty name="$step" id="vo"}
<div class="p-3 border-b jieduan">
<span class="gray">当前阶段:</span>{$step.flow_name}
<span class="gray" style="margin-left:20px">负责人:</span>{$step.check_name}
<span class="gray" style="margin-left:20px">成员:</span>{$step.flow_names}
<!-- <span class="gray" style="margin-left:20px">周期:</span>{$step.start_time|time_format=###,'Y-m-d'}-->
<!-- 到 {$step.end_time|time_format=###,'Y-m-d'}-->
{eq name="$step.flow_uid" value="$login_user"}
{neq name="$detail.status" value="4"}
<div class="pt-3">
<span class="layui-btn layui-btn-normal layui-btn-sm" data-event="step"
data-check="1">确认</span>
{gt name="$detail.step_sort" value="0"}
<span class="layui-btn layui-btn-danger layui-btn-sm" data-event="step"
data-check="2">退回</span>
{/gt}
</div>
{/neq}
{/eq}
</div>
{/notempty}
<div class="px-3 py-1 border-b">
<div class="flow-flexbox check-items flow-flex-row">
{volist name="$step_array" id="vo"}
<div class="flow-flexbox check-item flow-flex-row">
{gt name="$vo.sort" value="$detail.step_sort"}
<i class="layui-icon layui-icon-time"></i>
{/gt}
{eq name="$vo.sort" value="$detail.step_sort"}
<!-- <i class="layui-icon layui-icon-username" data-on=""></i>-->
{/eq}
{lt name="$vo.sort" value="$detail.step_sort"}
<i class="layui-icon layui-icon-ok-circle" data-ok=""></i>
{/lt}
<div class="check-item-name">{$vo.flow_name}</div>
<div class="check-item-status">{$vo.check_name}</div>
<span class="layui-icon layui-icon-right"></span>
</div>
{/volist}
</div>
</div>
<div class="p-3">
<p><strong>阶段流转记录</strong></p>
{notempty name="$step_record"}
<ul class="layui-timeline pt-2">
{volist name="$step_record" id="vo"}
<li class="layui-timeline-item delete-{$vo.delete_time}">
<i class="layui-icon layui-timeline-axis">&#xe63f;</i>
{if ($vo.status == 0)}
<p style="padding-left:24px">{$vo.check_time_str}<span
class="black mx-1">{$vo.check_name}</span><span
class="mr-1 green">{$vo.status_str}</span>了项目阶段的内容。</p>
{elseif ($vo.status == 1)}
<p style="padding-left:24px">{$vo.check_time_str}<span
class="black mx-1">{$vo.check_name}</span><span
class="mr-1 green">{$vo.status_str}</span>了{$vo.flow_name}的工作。</p>
{elseif ($vo.status == 2) /}
<p style="padding-left:24px">{$vo.check_time_str}<span
class="black mx-1">{$vo.check_name}</span>在{$vo.flow_name}执行了<span
class="mx-1 red">{$vo.status_str}</span>操作。操作意见:<span
class="red">{$vo.content}</span></p>
{else /}
<p style="padding-left:24px">{$vo.check_time_str}<span
class="black mx-1">{$vo.check_name}</span><span
class="mr-1 layui-color-{$vo.status}">{$vo.status_str}</span>了该阶段流程。操作意见:<span
class="red">{$vo.content}</span></p>
{/if}
</li>
{/volist}
</ul>
{else/}
<div class="layui-data-none">暂无记录</div>
{/notempty}
</div>
</div>
</div>
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header" style="height:45px;">
<div class="layui-row">
<div class="layui-col-md6">
<h4>项目附件</h4>
</div>
<div class="layui-col-md6" style="margin-top: 10px;">
<button type="button" class="layui-btn layui-btn-normal layui-btn-sm fr"
style="margin-left: 10px" id="fileBtn">选择文件
</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-sm2 fr" id="test9">开始上传
</button>
</div>
</div>
</div>
<div class="layui-row p-2" id="fileList">
{volist name="file_array" id="vo"}
<div class="layui-col-md4">
<div class="file-card">
<i class="file-icon iconfont icon-ziyuan"></i>
<div class="file-title" title="上传人:{$vo.admin_name}">
{$vo.name}
</div>
<div class="file-tool">
<a href="{$vo.filepath}" download="{$vo.name}" data-id="{$vo.id}" target="_blank"
title="大小:{$vo.filesize/1048576|round=2}MB">
<i class="iconfont icon-shujudaoru" style="color: #12bb37;"></i>
</a>
<i class="btn-delete iconfont icon-shanchu" data-id="{$vo.id}"
style="color: #FF5722;"></i>
</div>
</div>
</div>
{/volist}
{empty name="$file_array" }
<div class="content-none">暂无附件</div>
{/empty}
</div>
</div>
</div>
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header" style="height:45px;">
<div class="layui-row">
<div class="layui-col-md6">
<h4>关联链接</h4>
</div>
<div class="layui-col-md6" style="margin-top: 10px;">
<button type="button" class="layui-btn layui-btn-sm layui-btn-sm2 fr" id="linkBtn">新增链接
</button>
</div>
</div>
</div>
<div class="layui-card-body">
<table class="layui-table">
<thead>
<tr>
<th width="50%">链接URL</th>
<th width="30%" style="text-align:center">链接说明</th>
<th width="10%" style="text-align:center">添加人</th>
<th width="10%" style="text-align:center">操作</th>
</tr>
</thead>
<tbody id="linkList">
{volist name="link_array" id="vo"}
<tr>
<td><a href="{$vo.url}" target="_blank">{$vo.url}</a></td>
<td>{$vo.desc}</td>
<td style="text-align: center;">{$vo.admin_name}</td>
<td style="text-align: center;">
<div class="layui-btn-group" style="width:66px">
<button type="button" class="layui-btn layui-btn-xs link-edit"
data-id="{$vo.id}" data-href="{$vo.url}" data-desc="{$vo.desc}"><i
class="layui-icon"></i></button>
<button type="button"
class="layui-btn layui-btn-danger layui-btn-xs link-delete"
data-id="{$vo.id}"><i class="layui-icon"></i></button>
</div>
</td>
</tr>
{/volist}
{empty name="$link_array" }
<tr>
<td colspan="4" class="content-none">暂无链接</td>
</tr>
{/empty}
</tbody>
</table>
</div>
</div>
</div>
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header">
<h4>项目概况</h4>
</div>
<div class="layui-card-body">
<div style="height: 200px; text-align: center;">
<dl>
<dt class="layui-card-tips">工作记录</dt>
<dd class="layui-card-value" title="工作记录数">{$detail.schedules}</dd>
</dl>
<dl>
<dt class="layui-card-tips">项目工时</dt>
<dd class="layui-card-value" title="实际工时/计划工时">{$detail.hours} /
{$detail.plan_hours}
</dd>
</dl>
{volist name="$detail.task_cate" id="vo"}
<dl>
<dt class="layui-card-tips">{$vo.title}任务</dt>
<dd class="layui-card-value" title="已完成/总任务数">{$vo.unfinish} / {$vo.count}</dd>
</dl>
{/volist}
</div>
</div>
</div>
</div>
<div class="layui-col-xs12 ">
<div class="layui-card">
<div class="layui-card-header">
<h4>项目进度</h4>
</div>
<div class="layui-card-body">
<div class="layui-row">
<div class="layui-col-md6">
<!-- 任务完成率 -->
<div id="progress" class="data-none" style="width:100%;min-height: 200px;"></div>
</div>
<div class="layui-col-md6">
<!-- 任务延迟率 -->
<div id="delay" class="data-none" style="width:100%;min-height: 200px;"></div>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-xs12 ">
<div class="layui-card">
<div class="layui-card-header">
<h4>项目缺陷</h4>
</div>
<div class="layui-card-body">
<div id="rose" class="data-none" style="width:100%;min-height: 200px;"></div>
</div>
</div>
</div>
<div class="layui-col-xs12 layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">
<h4>项目燃尽图</h4>
</div>
<div class="layui-card-body">
<div id="cross" class="data-none" style="width:100%; height:360px;"
data-tips="任务数:{$detail.tasks},已完成:{$detail.tasks_finish},未完成:{$detail.tasks_unfinish}">
</div>
</div>
</div>
</div>
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header">
<h4>任务分配情况</h4>
</div>
<div class="layui-card-body">
<div id="plan" class="data-none" style="width:100%; height:150px;"></div>
</div>
</div>
</div>
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header">
<h4>工时登记情况</h4>
</div>
<div class="layui-card-body">
<div id="work" class="data-none" style="width:100%; height:150px;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
{/block}
<!-- /主体 -->
<!-- 脚本 -->
{block name="script"}
<script src="{__JS__}/echarts.min.js"></script>
<script>
//弹出上传附件框
function popUploadFile(id) {
//弹出框
layer.open({
title: '上传附件',
type: 1,
icon: 0,
area: ["600px", "400px"],
btn: ["确定", "取消"],
btn1: function (index, layro) {
//先删除要删除的已提交文件
if (deleteFilePaths) {
$.ajax({
url: basePath + 'deleteFile.do',
data: { deleteFilePaths: deleteFilePaths },
dataType: 'JSON',
Type: 'POST',
async: false,
success: function (data) {
if (data.code != 0) {
layer.msg(data.info);
return;
}
//更新弹出框对应的input
var deleteFilePathsArry = deleteFilePaths.split(",");
var newFileJsons = new Array();
for (var i in fileJsons) {
var fileData = JSON.parse(fileJsons[i]);
var isDelete = false;
for (var j in deleteFilePathsArry) {
if (fileData.filePath == deleteFilePathsArry[j]) {
isDelete = true;
break;
}
}
if (!isDelete) {
var newFileJson = {};
newFileJson.code = fileData.code;
newFileJson.filePath = fileData.filePath;
newFileJson.size = fileData.size;
newFileJson.fileDir = fileData.fileDir;
newFileJsons.push(newFileJson);
}
}
//赋值给弹出框对应的input
$("#" + id).val('');
for (var i in newFileJsons) {
var value = JSON.stringify(newFileJsons[i]);
if ($("#" + id).val() == '') {
$("#" + id).val(value);
} else {
$("#" + id).val($("#" + id).val() + ";" + value);
}
}
},
error: function (data) {
layer.msg("未知错误,请联系管理员");
return;
}
});
}
//开始上传新的文件
if (files && files.length != 0) {
$("#testListAction").click();
} else {
if ($("#" + id).val() != '') {
$("#" + id + "State").text("已上传");
} else {
$("#" + id + "State").text("未上传");
}
layer.closeAll('page'); //关闭所有页面层
}
},
btn2: function (index, layro) {
},
closeBtn: 1,
content: content
});
var deleteFilePaths;//删除已上传的文件路径列表
var fileDirIndex;//上传到本地的文件目录
var fileJsons;//已上传的文件基本数据
if ($("#" + id).val() == '') {
fileDirIndex = new Date().getTime();
} else {
fileJsons = $("#" + id).val().split(";");
fileDirIndex = JSON.parse(fileJsons[0]).fileDir;
}
var demoListView = $('#demoList');
var files;
//加载上传文件组件
layui.use('upload', function () {
var $ = layui.jquery
, upload = layui.upload
, uploadListIns = upload.render({
elem: '#testList'
, url: basePath + 'upload.do'
, data: { fileDir: fileDirIndex }
, accept: 'file'
, multiple: true
, auto: false
, bindAction: '#testListAction'
, choose: function (obj) {
files = this.files = obj.pushFile(); //将每次选择的文件追加到文件队列
//读取本地文件
obj.preview(function (index, file, result) {
var tr = $(['<tr id="upload-' + index + '">'
, '<td>' + file.name + '</td>'
, '<td>' + (file.size / 1024).toFixed(1) + 'kb</td>'
, '<td>等待上传</td>'
, '<td>'
, '<button class="layui-btn layui-btn-xs demo-reload layui-hide">重传</button>'
, '<button class="layui-btn layui-btn-xs layui-btn-danger notUpload-delete">删除</button>'
, '</td>'
, '</tr>'].join(''));
//单个重传
tr.find('.demo-reload').on('click', function () {
obj.upload(index, file);
});
//删除
tr.find('.notUpload-delete').on('click', function () {
delete files[index]; //删除对应的文件
tr.remove();
uploadListIns.config.elem.next()[0].value = ''; //清空 input file 值,以免删除后出现同名文件不可选
});
demoListView.append(tr);
});
}
, allDone: function (obj) { //当文件全部被提交后,才触发
$("#" + id + "State").text("已上传");
layer.closeAll('page'); //关闭所有页面层
}
, done: function (res, index, upload) {
if (res.code == 0) { //上传成功
var tr = demoListView.find('tr#upload-' + index)
, tds = tr.children();
tds.eq(2).html('<span style="color: #5FB878;">上传成功</span>');
tds.eq(3).html(''); //清空操作
//赋值给弹出框对应的input
var value = JSON.stringify(res);
if ($("#" + id).val() == '') {
$("#" + id).val(value);
} else {
$("#" + id).val($("#" + id).val() + ";" + value);
}
return delete this.files[index]; //删除文件队列已经上传成功的文件
}
this.error(index, upload);
}
, error: function (index, upload) {
var tr = demoListView.find('tr#upload-' + index)
, tds = tr.children();
tds.eq(2).html('<span style="color: #FF5722;">上传失败</span>');
tds.eq(3).find('.demo-reload').removeClass('layui-hide'); //显示重传
}
});
});
//添加已上传文件列表
for (var i in fileJsons) {
var fileJson = JSON.parse(fileJsons[i]);
var filePath = fileJson.filePath;
var fileName = fileJson.filePath.split("\\")[fileJson.filePath.split("\\").length - 1];
var fileSize = fileJson.size;
var tr = $(['<tr id="upload-' + i + '">'
, '<td>' + fileName + '</td>'
, '<td>' + (fileSize / 1024).toFixed(1) + 'kb</td>'
, '<td>已上传</td>'
, '<td>'
, '<input class="layui-btn layui-hide" type="text" value="' + filePath + '"/>'
, '<button class="layui-btn layui-btn-xs layui-btn-danger upload-delete">删除</button>'
, '</td>'
, '</tr>'].join(''));
//删除
tr.find('.upload-delete').on('click', function () {
//拼接要删除的已上传文件路径
var deleteFilePath = $(this).prev().val();
if (deleteFilePaths) {
deleteFilePaths += "," + deleteFilePath;
} else {
deleteFilePaths = deleteFilePath;
}
$(this).parent().parent().remove();
});
demoListView.append(tr);
}
}
</script>
<script>
var project_id = '{$detail.id}';
var project_start_time = '{$detail.start_time| date="Y-m-d"}';
const opsData = {
status: [
{ 'id': 1, 'title': '未开始' },
{ 'id': 2, 'title': '进行中' },
{ 'id': 3, 'title': '已完成' },
{ 'id': 4, 'title': '已关闭' }
]
}
var chartProgress = document.getElementById('progress');
var progressChart = echarts.init(chartProgress);
var optionA;
optionA = {
backgroundColor: "#ffffff",
title: {
text: '67.45%',//主标题文本
subtext: '任务完成率',//副标题文本
x: 'center',
y: '39%',
textStyle: {
fontWeight: 'normal',
fontSize: 18,
color: '#FF974C',
align: 'center'
},
subtextStyle: {
fontSize: 12,
color: '#6c7a89',
}
},
tooltip: {
trigger: "item",
formatter: '{b}<br/><strong>{c}</strong>',
show: true,
},
series: [
{
type: 'pie',
radius: ['60%', '80%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
data: [
{ value: 1048, name: '待处理' },
{ value: 735, name: '已完成' }
]
}
]
};
var chartDelay = document.getElementById('delay');
var delayChart = echarts.init(chartDelay);
var optionB;
optionB = {
backgroundColor: "#ffffff",
title: {
text: '40.25%',//主标题文本
subtext: '任务延迟率',//副标题文本
x: 'center',
y: '39%',
textStyle: {
fontWeight: 'normal',
fontSize: 18,
color: '#FF974C',
align: 'center',
marginLeft: '-10px'
},
subtextStyle: {
fontSize: 12,
color: '#6c7a89',
}
},
tooltip: {
trigger: "item",
formatter: '{b}<br/><strong>{c}</strong>',
show: true,
},
series: [
{
type: 'pie',
radius: ['60%', '80%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
data: [{
value: 1048,
name: '延误',
itemStyle: {
color: "#ED6666",
}
},
{
value: 735,
name: '按时完成',
itemStyle: {
color: "#91CC75",
}
}
]
}
]
};
var chartRose = document.getElementById('rose');
var roseChart = echarts.init(chartRose);
var optionC;
optionC = {
backgroundColor: "#ffffff",
legend: {
orient: 'vertical',
left: 'left'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
series: [
{
name: 'Bugfixed',
type: 'pie',
radius: ['25%', '75%'],
center: ['60%', '50%'],
data: [
{ value: 40, name: '待解决' },
{ value: 38, name: '进行中' },
{ value: 32, name: '已解决' },
{ value: 28, name: '不解决' },
{ value: 26, name: '已关闭' }
]
}
]
};
var chartCross = document.getElementById('cross');
var crossChart = echarts.init(chartCross);
var optionD;
var tips = $('#cross').data('tips');
optionD = {
backgroundColor: "#ffffff",
color: ['#8C92A4', '#2C7EF8'],
title: {
text: '',
subtext: tips,
top: -10,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['任务计划剩余', '任务实际剩余']
},
grid: {
top: 36,
left: 8,
right: 36,
bottom: 0,
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
splitLine: {
show: true,
lineStyle: {
type: 'dashed'
}
}
}
],
yAxis: [{
axisLine: {
show: true
},
boundaryGap: false,
splitLine: {
show: true,
lineStyle: {
type: 'dashed'
}
},
type: 'value'
}
]
};
var chartPlan = document.getElementById('plan');
var planChart = echarts.init(chartPlan);
var optionE;
optionE = {
backgroundColor: "#ffffff",
title: {
top: 0,
left: 0,
text: ''
},
tooltip: {
padding: 6,
formatter: function (obj) {
var value = obj.value;
var tips = '<div style="font-size: 12px;">' + value[0] + '<br>';
tips += '共 ' + value[1] + ' 个工作任务';
tips += '</div>';
return tips;
}
},
visualMap: {
min: 0,
max: 10,
show: false,
inRange: {
color: ['#fafafa', '#20BF3F']
}
},
calendar: {
top: 24,
left: 36,
right: 4,
cellSize: ['auto', 16],
range: ['2022-03-01', '2022-08-01'],
splitLine: {
lineStyle: {
color: '#333',
type: 'dashed',
}
},
itemStyle: {
borderWidth: 0.5
},
yearLabel: { show: false },
monthLabel: {
nameMap: 'cn',
fontSize: 12
},
dayLabel: {
show: true,
formatter: '{start} 1st',
fontWeight: 'lighter',
nameMap: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
fontSize: 12
}
},
series: {
type: 'heatmap',
coordinateSystem: 'calendar',
data: []
}
};
var chartWork = document.getElementById('work');
var workChart = echarts.init(chartWork);
var optionF;
optionF = {
backgroundColor: "#ffffff",
title: {
top: 0,
left: 0,
text: ''
},
tooltip: {
padding: 6,
formatter: function (obj) {
var value = obj.value;
var tips = '<div style="font-size: 12px;">' + value[0] + '<br>';
tips += '共 ' + value[1] + ' 个工时';
tips += '</div>';
return tips;
}
},
visualMap: {
min: 0,
max: 10,
show: false,
inRange: {
color: ['#fafafa', '#359AEF']
}
},
calendar: {
top: 24,
left: 36,
right: 4,
cellSize: ['auto', 16],
range: ['2022-03-01', '2022-08-01'],
splitLine: {
lineStyle: {
color: '#333',
type: 'dashed',
}
},
itemStyle: {
borderWidth: 0.5
},
yearLabel: { show: false },
monthLabel: {
nameMap: 'cn',
fontSize: 12
},
dayLabel: {
show: true,
formatter: '{start} 1st',
fontWeight: 'lighter',
nameMap: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
fontSize: 12
}
},
series: {
type: 'heatmap',
coordinateSystem: 'calendar',
data: []
}
};
function getCalendarData(arr) {
var rangeArray = [];
for (var property in arr) {
rangeArray.push(property);
}
var rangeArray = [rangeArray[0], rangeArray[rangeArray.length - 1]];
var start = +echarts.number.parseDate(rangeArray[0]);
var end = +echarts.number.parseDate(rangeArray[1]);
if (start + 7776000000 > end) {
end = start + 8640000000;
rangeArray[1] = echarts.format.formatTime('yyyy-MM-dd', end);
}
var dayTime = 3600 * 24 * 1000;
var data = [];
for (var time = start; time < end; time += dayTime) {
var this_date = echarts.format.formatTime('yyyy-MM-dd', time);
if (arr[this_date]) {
data.push([this_date, arr[this_date]]);
} else {
data.push([this_date, 0]);
}
}
var res = { 'range': rangeArray, 'data': data };
return res;
}
//燃尽图统计
function cross_count(arr, arr2) {
var planArray = [], doArray = [];
var today = new Date();
var todayStr = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
for (var a in arr) {
planArray.push(a);
}
var rangeArray = [planArray[0], planArray[planArray.length - 1]];
if (arr2 instanceof Array == false) {
for (var b in arr2) {
doArray.push(b);
}
if ((+echarts.number.parseDate(doArray[doArray.length - 1])) < (+echarts.number.parseDate(todayStr))) {
doArray.push(todayStr);
}
if ((+echarts.number.parseDate(planArray[planArray.length - 1])) < (+echarts.number.parseDate(doArray[doArray.length - 1]))) {
rangeArray[1] = doArray[doArray.length - 1];
}
}
var start = +echarts.number.parseDate(rangeArray[0]);
var end = +echarts.number.parseDate(rangeArray[1]);
var todayInt = +echarts.number.parseDate(todayStr);
var dayTime = 3600 * 24 * 1000;
var xArray = [], yArray = [], yArray2 = [], done = 0, doneArray = [];
for (var time = start; time <= end; time += dayTime) {
var this_date = echarts.format.formatTime('yyyy-MM-dd', time);
xArray.push(this_date);
var plan = cross_recursion(time, end, arr);
yArray.push(plan);
if (arr2[this_date]) {
done += arr2[this_date];
}
if (time <= todayInt) {
doneArray.push(done);
}
}
for (var i = 0; i < doneArray.length; i++) {
yArray2.push(yArray[0] - doneArray[i]);
}
var start_time = +echarts.number.parseDate(project_start_time), tem_x_array = [], tem_y_array = [];
if (start_time < start) {
for (var tem_time = start_time; tem_time < start; tem_time += dayTime) {
var this_date = echarts.format.formatTime('yyyy-MM-dd', tem_time);
tem_x_array.push(this_date);
tem_y_array.push(yArray[0]);
}
xArray = tem_x_array.concat(xArray);
yArray = tem_y_array.concat(yArray);
yArray2 = tem_y_array.concat(yArray2);
}
return { 'x': xArray, 'y': yArray, 'y2': yArray2 };
}
function cross_recursion(start, end, arr) {
var count = 0;
var dayTime = 3600 * 24 * 1000;
for (var time = start; time <= end; time += dayTime) {
var this_date = echarts.format.formatTime('yyyy-MM-dd', time);
if (arr[this_date]) {
count += arr[this_date];
}
}
return count;
}
const moduleInit = ['tool', 'gouguEdit', 'gouguComment', 'gouguSchedule', 'employeepicker', 'editormd'];
function gouguInit() {
const layer = layui.layer, tool = layui.tool, edit = layui.gouguEdit, comment = layui.gouguComment,
upload = layui.upload;
$('#subMenu').on('click', '[data-event="subpage"]', function () {
let page = $(this).data('page');
$('#subMenu').find('li').removeClass('active');
$(this).addClass('active');
tool.page(page);
})
$('#subMenu').on('click', '[data-event="open"]', function () {
let url = $(this).data('url');
tool.open(url);
})
$('.hover-edit').hover(function () {
$(this).addClass('hover-on');
}, function () {
$(this).removeClass('hover-on');
})
$('.hover-edit').on('click', 'i', function () {
let name = $(this).data('name');
let show_txt = $('#' + name + '_' + project_id).text().replace(/[\r\n\t]/g, "");
let real_txt = $('#' + name + '_' + project_id).data('val');
if (real_txt === '') {
real_txt = show_txt;
}
editShow(project_id, name, show_txt, real_txt);
})
let loading = false;
let editPost = function (id, name, show_val, real_val) {
let callback = function (e) {
layer.closeAll();
layer.msg(e.msg);
if (e.code == 0) {
setTimeout(function () {
location.reload();
}, 1000)
}
}
let postData = { id: id };
postData[name] = real_val;
tool.post("/project/index/edit", postData, callback);
}
function editShow(id, name, show_txt, real_txt) {
if (loading == true) {
return false;
}
if (name == "name") {
edit.text(id, name, real_txt, editPost);
}
if (name == "start_time" || name == "end_time") {
edit.date(id, name, real_txt, editPost);
}
if (name == "director_uid") {
edit.employee_one(id, name, show_txt, real_txt, editPost);
}
if (name == "status") {
edit.dropdown(id, name, real_txt, opsData[name], editPost);
}
if (name == "product_id") {
loading = true;
tool.get("/api/index/get_product", {}, function (res) {
let data = res.data;
loading = false;
edit.dropdown(id, name, real_txt, data, editPost, 1);
});
}
if (name == "content") {
edit.textarea(id, name, real_txt, editPost);
}
}
$('#delProject').on('click', function () {
layer.confirm('确定要删除该项目吗?请慎重', { icon: 3, title: '提示' }, function (index) {
let callback = function (e) {
layer.closeAll();
layer.msg(e.msg);
if (e.code == 0) {
setTimeout(function () {
location.href = '/project/index/index';
}, 1000)
}
}
let postData = { "id": project_id };
tool.delete("/project/index/delete", postData, callback);
});
})
function formatLocalDate(timestamp) {
let date = new Date(timestamp * 1000);
let offset = date.getTimezoneOffset() * 60000; // 获取当前时区偏移
date = new Date(date.getTime() - offset); // 调整为本地时间
return date.toISOString().slice(0, 10); // 格式化为 YYYY-MM-DD
}
$(document).ready(function () {
$('#editProject').on('click', function () {
// 获取项目数据
$.ajax({
url: '/project/index/edit',
method: 'GET',
data: { "id": project_id },
success: function (response) {
if (response.code === 0) {
var detail = response.data.detail;
// 获取客户名称列表后再打开弹出层
$.ajax({
url: '/business/index/getlistsmore',
method: 'GET',
success: function (businessResponse) {
if (businessResponse.code === 1) {
// 创建一个映射对象来快速查找业务ID对应的名称
var businessMap = {};
businessResponse.data.forEach(function (item) {
businessMap[item.id] = item.name; // 将ID和名称存储在映射中
});
// 创建下拉选项
var businessOptions = businessResponse.data.map(function (item) {
return `<option value="${item.id}">${item.name}</option>`;
}).join('');
// 获取当前项目的 business_id以便找到对应的名称
var selectedBusinessName = businessMap[detail.business_id] || '';
layer.open({
type: 1,
title: '编辑项目',
area: ['700px', '500px'],
content: `
<form class="layui-form" style="padding: 20px;">
<input type="hidden" name="id" value="${detail.id}">
<div class="layui-form-item" style="display:flex">
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">客户名称</label>
<div class="">
<select name="business_id" required lay-verify="required" lay-search>
<option value="${detail.business_id}">${selectedBusinessName}</option>
${businessOptions}
</select>
</div>
</div>
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">创建人</label>
<div class="" style="width: 212px">
<input type="text" name="admin_name" disabled class="layui-input" value="${detail.admin_name}">
<input type="hidden" name="admin_id" value="${detail.admin_id}">
</div>
</div>
</div>
<div class="layui-form-item" style="display:flex">
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">负责人</label>
<div class="" style="width: 212px">
<input type="text" name="director_name" autocomplete="off" readonly placeholder="选择阶段负责人" class="layui-input picker-one" value="${detail.director_name}">
<input type="hidden" name="director_uid" value="${detail.director_uid}">
</div>
</div>
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">项目成员</label>
<div class="" style="width: 212px">
<input type="text" name="team_admin_names" autocomplete="off" readonly placeholder="选择阶段成员,可多选" class="layui-input picker-more" value="${detail.team_admin_names}">
<input type="hidden" name="team_admin_ids" value="${detail.team_admin_ids}">
</div>
</div>
</div>
<div class="layui-form-item" style="display:flex">
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">合同开始</label>
<div class="" style="width: 212px">
<input type="text" name="start_time" placeholder="请输入开始时间" autocomplete="off"
class="layui-input" id="start_time" value="${new Date(detail.start_time * 1000).toLocaleDateString('en-CA')}"
onclick="layui.use('laydate', function(){ laydate({ elem: '#start_time', format: 'YYYY-MM-DD' }); })">
</div>
</div>
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">合同结束</label>
<div class="" style="width: 212px">
<input type="text" name="end_time" placeholder="请输入结束时间" autocomplete="off"
class="layui-input" id="end_time" value="${formatLocalDate(detail.end_time)}"
onclick="layui.use('laydate', function(){ laydate({ elem: '#end_time', format: 'YYYY-MM-DD' }); })">
</div>
</div>
</div>
<div class="layui-form-item" style="display:flex">
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">项目开始</label>
<div class="" style="width: 212px">
<input type="text" name="project_start_time" placeholder="请输入开始时间" autocomplete="off"
class="layui-input" id="project_start_time" value="${new Date(detail.project_start_time * 1000).toLocaleDateString('en-CA')}"
onclick="layui.use('laydate', function(){ laydate({ elem: '#project_start_time', format: 'YYYY-MM-DD' }); })">
</div>
</div>
<div class="layui-inline" style="display:flex;align-items:center;justify-content:center;">
<label class="layui-form-label">项目结束</label>
<div class="" style="width: 212px">
<input type="text" name="project_end_time" placeholder="请输入结束时间" autocomplete="off"
class="layui-input" id="project_end_time" value="${formatLocalDate(detail.project_end_time)}"
onclick="layui.use('laydate', function(){ laydate({ elem: '#project_end_time', format: 'YYYY-MM-DD' }); })">
</div>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">项目简介</label>
<div class="layui-input-block">
<textarea name="content" required lay-verify="required" placeholder="请输入项目简介" class="layui-textarea" style="width: 530px">${detail.content}</textarea>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="saveProject">保存</button>
</div>
</div>
</form>
`,
success: function (layero, index) {
layui.use(['form', 'laydate'], function () {
var form = layui.form;
var laydate = layui.laydate;
laydate.render({ elem: '#start_time', type: 'date', format: 'yyyy-MM-dd' });
laydate.render({ elem: '#end_time', type: 'date', format: 'yyyy-MM-dd' });
laydate.render({ elem: '#project_start_time', type: 'date', format: 'yyyy-MM-dd' });
laydate.render({ elem: '#project_end_time', type: 'date', format: 'yyyy-MM-dd' });
form.on('submit(saveProject)', function (data) {
$.ajax({
url: '/project/index/edit?id=' + detail.id,
method: 'POST',
data: data.field,
success: function (response) {
if (response.code === 0) {
layer.msg('保存成功');
layer.close(index);
location.reload();
} else {
layer.msg(response.msg || '保存失败');
}
},
error: function () {
layer.msg('保存失败,请检查网络');
}
});
return false;
});
form.render();
form.val('editProjectForm', { 'business_id': detail.business_id });
});
}
});
} else {
layer.msg(businessResponse.msg || '客户名称加载失败');
}
},
error: function () {
layer.msg('请求客户名称失败,请检查网络');
}
});
} else {
layer.msg(response.msg || '获取项目详情失败');
}
},
error: function () {
layer.msg('请求失败,请检查网络');
}
});
});
});
//关闭项目
$('#closeProject').on('click', function () {
layer.confirm('确定要关闭该项目吗?请慎重', { icon: 3, title: '提示' }, function (index) {
$.ajax({
url: "/project/index/close",
type: "POST",
data: { "id": project_id },
success: function (e) {
layer.closeAll();
layer.msg(e.msg);
if (e.code == 0) {
setTimeout(function () {
window.location.href = '/project/index/index';
}, 1000);
}
},
error: function () {
layer.msg('请求失败,请检查网络或联系管理员');
}
});
});
});
//开启项目
$('#reopenProject').on('click', function () {
layer.confirm('确定要开启该项目吗?请慎重', { icon: 3, title: '提示' }, function (index) {
$.ajax({
url: "/project/index/open",
type: "POST", // 根据实际情况调整请求类型
data: { "id": project_id },
success: function (e) {
layer.closeAll();
layer.msg(e.msg);
if (e.code == 0) {
setTimeout(function () {
window.location.href = '/project/index/index';
}, 1000);
}
},
error: function () {
layer.msg('请求失败,请检查网络或联系管理员');
}
});
});
});
$('body').on('click', '[data-event="step"]', function () {
let check = $(this).data('check');
let callback = function (e) {
layer.msg(e.msg);
if (e.code == 0) {
setTimeout(function () {
location.reload();
}, 2000)
}
}
if (check == 2) {
layer.open({
type: 1,
title: '请输入退回的原因或理由',
area: ['800px', '360px'],
content: '<div style="padding:5px;"><textarea class="layui-textarea" id="remarkTextarea" style="width: 100%; height: 240px;"></textarea></div>',
btnAlign: 'c',
btn: ['提交保存'],
yes: function () {
let remark = $("#remarkTextarea").val();
if (remark != '') {
tool.post("/api/project/step_check", {
id: project_id,
check: check,
content: remark
}, callback);
} else {
layer.msg('请输入原因或理由');
}
}
})
} else {
layer.confirm('确认已完成该阶段工作,进入下个阶段?', {
icon: 3,
title: '提示'
},
function (index) {
tool.post("/api/project/step_check", { id: project_id, check: check }, callback);
})
}
});
//选完文件后不自动上传
upload.render({
elem: '#fileBtn'
, url: '/api/index/upload' //此处配置你自己的上传接口即可
, exts: 'jpeg|jpg|png|gif|doc|docx|ppt|pptx|xls|xlsx|pdf|zip|rar|7z|ai|psd|drawio|txt|xmind' //只允许上传文件
, auto: false
//,multiple: true
, bindAction: '#test9'
, before: function (obj) {
layer.msg('上传中...', { time: 3600000 });
}
, done: function (res, index, upload) {
let callback = function (e) {
layer.msg('上传成功');
setTimeout(function () {
location.reload();
}, 2000)
}
let postData = {
'topic_id': project_id,
'file_id': res.data.id,
'file_name': res.data.name,
'module': 'project'
};
console.log('输出了:' + postData.file_name);
console.log('输出了:' + postData.file_id);
tool.post("/api/appendix/add", postData, callback);
}
, error: function (index, upload) {
layer.msg('上传失败');
}
});
$('#fileList').on('click', '.btn-delete', function () {
let id = $(this).data('id');
layer.confirm('确定要删除该附件吗?', { icon: 3, title: '提示' }, function (index) {
let callback = function (e) {
layer.closeAll();
layer.msg(e.msg);
setTimeout(function () {
location.reload();
}, 2000)
}
let postData = { "id": id };
tool.delete("/api/appendix/delete", postData, callback);
});
})
$('#linkBtn').on('click', function () {
comment.addLink(0, project_id, 'project', '', '');
})
$('#linkList').on('click', '.link-edit', function () {
let id = $(this).data('id');
let url = $(this).data('url');
let desc = $(this).data('desc');
comment.addLink(id, project_id, 'project', url, desc);
})
$('#linkList').on('click', '.link-delete', function () {
let id = $(this).data('id');
layer.confirm('确定要删除该链接吗?', { icon: 3, title: '提示' }, function (index) {
let callback = function (e) {
layer.closeAll();
layer.msg(e.msg);
setTimeout(function () {
location.reload();
}, 2000)
}
let postData = { "id": id };
tool.delete("/api/appendix/delete_link", postData, callback);
});
})
let callback = function (res) {
if (res.data.date_tasks instanceof Array == false) {
optionA.title.text = res.data.task_pie.ok_lv + '%';
optionA.series = [
{
type: 'pie',
radius: ['60%', '80%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
data: [
{ value: res.data.task_pie.count - res.data.task_pie.count_ok, name: '待处理' },
{ value: res.data.task_pie.count_ok, name: '已完成' }
]
}
];
optionA && progressChart.setOption(optionA);
optionB.title.text = res.data.task_pie.delay_lv + '%';
optionB.series = [
{
type: 'pie',
radius: ['60%', '80%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
data: [{
value: res.data.task_pie.delay,
name: '延误',
itemStyle: {
color: "#ED6666",
}
},
{
value: res.data.task_pie.count - res.data.task_pie.delay,
name: '按时完成',
itemStyle: {
color: "#91CC75",
}
}
]
}
];
optionB && delayChart.setOption(optionB);
optionC.series = [
{
name: 'Bugfixed',
type: 'pie',
radius: ['25%', '75%'],
center: ['60%', '50%'],
data: [
{ value: res.data.bug_status.status_a, name: '待解决' },
{ value: res.data.bug_status.status_b, name: '进行中' },
{ value: res.data.bug_status.status_c, name: '已解决' },
{ value: res.data.bug_status.status_d, name: '不解决' },
{ value: res.data.bug_status.status_e, name: '已关闭' }
]
}
]
optionC && roseChart.setOption(optionC);
var dataD = cross_count(res.data.date_tasks, res.data.date_tasks_ok);
optionD.xAxis = {
type: 'category',
boundaryGap: false,
splitLine: {
show: true,
lineStyle: {
type: 'dashed'
}
},
data: dataD.x,
axisLabel: {
rotate: 30,
formatter: function (value, index) {
return value.slice(5);
}
}
};
optionD.series = [
{
name: '任务计划剩余',
type: 'line',
showSymbol: false,
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
lineStyle: {
width: 2
},
data: dataD.y
},
{
name: '任务实际剩余',
type: 'line',
showSymbol: false,
areaStyle: {
opacity: 0.1
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
lineStyle: {
width: 2
},
data: dataD.y2
}
]
optionD && crossChart.setOption(optionD)
var dataE = getCalendarData(res.data.date_tasks);
optionE.calendar.range = dataE.range,
optionE.series = {
type: 'heatmap',
coordinateSystem: 'calendar',
data: dataE.data
}
optionE && planChart.setOption(optionE);
if (res.data.date_schedules instanceof Array == false) {
var dataF = getCalendarData(res.data.date_schedules);
optionF.calendar.range = dataF.range,
optionF.series = {
type: 'heatmap',
coordinateSystem: 'calendar',
data: dataF.data
}
optionF && workChart.setOption(optionF);
}
}
}
tool.get('/api/project/get_chart_data', { 'project_id': project_id }, callback);
window.onresize = function () {
progressChart.resize();
delayChart.resize();
roseChart.resize();
crossChart.resize();
planChart.resize();
workChart.resize();
}
}
</script>
{/block}
<!-- /脚本 -->