Jelajahi Sumber

迁移trace模块。

yangzhijie 4 tahun lalu
induk
melakukan
f2f7a28998
100 mengubah file dengan 11564 tambahan dan 7957 penghapusan
  1. 1 1
      config/index.js
  2. 259 7954
      package-lock.json
  3. 4 1
      package.json
  4. 61 0
      src/common/DynamicJsLoader.js
  5. 72 0
      src/lang/Language.js
  6. 6 0
      src/lang/en-US.js
  7. 6 0
      src/lang/zh-CN.js
  8. 41 1
      src/router/index.js
  9. 239 0
      src/trace/CompleteProjectList.vue
  10. 170 0
      src/trace/Finished.vue
  11. 325 0
      src/trace/FinishedProjectTraces.vue
  12. 437 0
      src/trace/NotFinishedProjectTraces.vue
  13. 252 0
      src/trace/Personage.vue
  14. 223 0
      src/trace/ProjectAdminUserList.vue
  15. 1126 0
      src/trace/ProjectArchive.vue
  16. 260 0
      src/trace/ProjectList.vue
  17. 557 0
      src/trace/ProjectManagement.vue
  18. 224 0
      src/trace/ProjectUserList.vue
  19. 135 0
      src/trace/TeamList.vue
  20. 390 0
      src/trace/Trace.vue
  21. 269 0
      src/trace/TraceAttachment.vue
  22. 137 0
      src/trace/TraceComment.vue
  23. 441 0
      src/trace/TraceCommentCreate.vue
  24. 344 0
      src/trace/TraceCommentEdit.vue
  25. 11 0
      src/trace/TraceCommon.js
  26. 97 0
      src/trace/TraceConfig.vue
  27. 341 0
      src/trace/TraceCreate.vue
  28. 422 0
      src/trace/TraceDynamic.vue
  29. 154 0
      src/trace/TraceHeader.vue
  30. 301 0
      src/trace/TraceList.vue
  31. 99 0
      src/trace/TraceLog.vue
  32. 113 0
      src/trace/TraceResource.js
  33. 332 0
      src/trace/TraceTimeLine.vue
  34. 209 0
      src/trace/TraceTimeLineCreate.vue
  35. 275 0
      src/trace/TraceTimeLineEdit.vue
  36. 385 0
      src/trace/TraceUpdate.vue
  37. 166 0
      src/trace/UseFinishedTrace.vue
  38. 238 0
      src/trace/UserNotFinishedTrace.vue
  39. 294 0
      src/widget/AudioField.vue
  40. 51 0
      src/widget/AuthAudio.vue
  41. 472 0
      src/widget/GanttScale2.vue
  42. 37 0
      src/widget/MyProgress.vue
  43. 66 0
      src/widget/QueryWidget.vue
  44. 48 0
      src/widget/TreeSelect.vue
  45. 72 0
      src/widget/UpladFile.js
  46. 194 0
      src/widget/recorder.js
  47. 0 0
      static/dhtmlxgantt/dhtmlxgantt.css
  48. 8 0
      static/dhtmlxgantt/dhtmlxgantt.js
  49. 0 0
      static/dhtmlxgantt/dhtmlxgantt.js.map
  50. 1000 0
      static/dhtmlxgantt/ext/api.js
  51. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_auto_scheduling.js
  52. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_auto_scheduling.js.map
  53. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_critical_path.js
  54. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_critical_path.js.map
  55. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_csp.js
  56. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_csp.js.map
  57. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_fullscreen.js
  58. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_fullscreen.js.map
  59. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_grouping.js
  60. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_grouping.js.map
  61. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_keyboard_navigation.js
  62. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_keyboard_navigation.js.map
  63. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_marker.js
  64. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_marker.js.map
  65. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_multiselect.js
  66. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_multiselect.js.map
  67. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_quick_info.js
  68. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_quick_info.js.map
  69. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_smart_rendering.js
  70. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_smart_rendering.js.map
  71. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_tooltip.js
  72. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_tooltip.js.map
  73. 8 0
      static/dhtmlxgantt/ext/dhtmlxgantt_undo.js
  74. 0 0
      static/dhtmlxgantt/ext/dhtmlxgantt_undo.js.map
  75. 8 0
      static/dhtmlxgantt/locale/locale.js
  76. 0 0
      static/dhtmlxgantt/locale/locale.js.map
  77. 8 0
      static/dhtmlxgantt/locale/locale_ar.js
  78. 0 0
      static/dhtmlxgantt/locale/locale_ar.js.map
  79. 8 0
      static/dhtmlxgantt/locale/locale_be.js
  80. 0 0
      static/dhtmlxgantt/locale/locale_be.js.map
  81. 8 0
      static/dhtmlxgantt/locale/locale_ca.js
  82. 0 0
      static/dhtmlxgantt/locale/locale_ca.js.map
  83. 8 0
      static/dhtmlxgantt/locale/locale_cn.js
  84. 0 0
      static/dhtmlxgantt/locale/locale_cn.js.map
  85. 8 0
      static/dhtmlxgantt/locale/locale_cs.js
  86. 0 0
      static/dhtmlxgantt/locale/locale_cs.js.map
  87. 8 0
      static/dhtmlxgantt/locale/locale_da.js
  88. 0 0
      static/dhtmlxgantt/locale/locale_da.js.map
  89. 8 0
      static/dhtmlxgantt/locale/locale_de.js
  90. 0 0
      static/dhtmlxgantt/locale/locale_de.js.map
  91. 8 0
      static/dhtmlxgantt/locale/locale_el.js
  92. 0 0
      static/dhtmlxgantt/locale/locale_el.js.map
  93. 8 0
      static/dhtmlxgantt/locale/locale_es.js
  94. 0 0
      static/dhtmlxgantt/locale/locale_es.js.map
  95. 8 0
      static/dhtmlxgantt/locale/locale_fa.js
  96. 0 0
      static/dhtmlxgantt/locale/locale_fa.js.map
  97. 8 0
      static/dhtmlxgantt/locale/locale_fi.js
  98. 0 0
      static/dhtmlxgantt/locale/locale_fi.js.map
  99. 8 0
      static/dhtmlxgantt/locale/locale_fr.js
  100. 0 0
      static/dhtmlxgantt/locale/locale_fr.js.map

+ 1 - 1
config/index.js

@@ -14,7 +14,7 @@ module.exports = {
 
     // Various Dev Server settings
     host: 'localhost', // can be overwritten by process.env.HOST
-    port: 8089, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+    port: 8091, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
     autoOpenBrowser: false,
     errorOverlay: true,
     notifyOnErrors: true,

File diff ditekan karena terlalu besar
+ 259 - 7954
package-lock.json


+ 4 - 1
package.json

@@ -11,12 +11,15 @@
     "build": "node build/build.js"
   },
   "dependencies": {
+    "@riophae/vue-treeselect": "^0.4.0",
     "pc-client-component": "0.0.22",
     "vue": "^2.5.2",
     "vue-bootstrap-pagination": "^2.10.3",
     "vue-i18n": "^8.24.5",
     "vue-router": "^3.0.1",
-    "vue-select": "^3.11.2"
+    "vue-select": "^3.11.2",
+    "vue-simple-suggest": "^1.11.1",
+    "vuedraggable": "^2.24.3"
   },
   "devDependencies": {
     "autoprefixer": "^7.1.2",

+ 61 - 0
src/common/DynamicJsLoader.js

@@ -0,0 +1,61 @@
+module.exports = {
+
+
+
+
+	/**
+	 * 动态加载css
+	 */
+	addCSS: function (cssName) {
+		var link = document.createElement('link');
+		link.type = 'text/css';
+		link.rel = 'stylesheet';
+		link.href = cssName;
+		document.getElementsByTagName("head")[0].appendChild(link);
+	},
+
+	/**
+	 * 动态加载loadDhtmlxgantt
+	 * @param {Object} success
+	 */
+	loadDhtmlxgantt: function (success) {
+		if (jQuery().gantt) {
+			success();
+		} else {
+			this.addCSS("../../static/dhtmlxgantt/dhtmlxgantt.css");
+			$.getScript("../../static/dhtmlxgantt/dhtmlxgantt.js", function (data, textStatus, jqxhr) {
+				$.getScript("../../static/dhtmlxgantt/locale/locale_cn.js", function (data, textStatus, jqxhr) {
+					$.getScript("../../static/dhtmlxgantt/ext/dhtmlxgantt_auto_scheduling.js", function (data, textStatus, jaxhr) {
+						$.getScript("../../static/dhtmlxgantt/ext/dhtmlxgantt_smart_rendering.js", function (data, textStatus, jaxhr) {
+							$.getScript("../../static/dhtmlxgantt/ext/dhtmlxgantt_undo.js", function (data, textStatus, jaxhr) {
+								$.getScript("../../static/dhtmlxgantt/ext/dhtmlxgantt_fullscreen.js", function (data, textStatus, jaxhr) {
+									$.getScript("../../static/dhtmlxgantt/ext/api.js", function (data, textStatus, jaxhr) {
+										success();
+									});
+								});
+							});
+						});
+					});
+				});
+			});
+		}
+	},
+
+
+	/**
+	 * 动态加载summernote
+	 * @param {Object} success
+	 */
+	 loadSummernote: function (success) {
+		if (jQuery().summernote) {
+			success();
+		} else {
+			this.addCSS("../../static/summernote-0.8.18/summernote.css");
+			$.getScript("../../static/summernote-0.8.18/summernote.min.js", function (data, textStatus, jqxhr) {
+				$.getScript("../../static/summernote-0.8.18/lang/summernote-zh-CN.min.js", function (data, textStatus, jaxhr) {
+					success();
+				})
+			});
+		}
+	},
+}

+ 72 - 0
src/lang/Language.js

@@ -0,0 +1,72 @@
+module.exports = {
+    /**
+     * 获取国际化的菜单名称
+     */
+    getMenuNameTrl: function(locale, menu){
+        if(menu == null || menu == ''){
+            return null;
+        }
+        if(locale == 'en-US'){
+            return (menu.nameEng == null || menu.nameEng == '') ? menu.name : menu.nameEng;
+        }else{
+            return menu.name;
+        }
+    },
+
+    /**
+     * 获取国际化的名称
+     */
+    getNameTrl: function(locale, data){
+
+        if(data.fieldName == "ai.name"){
+            let a = 0;
+        }
+        if(data == null || data == ''){
+            return null;
+        }
+        if(locale == 'en-US'){
+            return (data.nameEng == null || data.nameEng == '') ? data.name : data.nameEng;
+        }else{
+            return data.name;
+        }
+    },
+
+    /**
+     * 获取国际化的帮助
+     */
+     getHelpTrl: function(locale, data){
+        if(data == null || data == ''){
+            return null;
+        }
+        if(locale == 'en-US'){
+            return (data.helpEng == null || data.helpEng == '') ? data.help : data.helpEng;
+        }else{
+            return data.help;
+        }
+    },
+
+    /**
+     * 获取国际化的显示名称
+     */
+    getDisplayNameTrl: function(locale, data){
+        if(data == null || data == ''){
+            return null;
+        }
+        if(locale == 'en-US'){
+            return (data.displayNameEng == null || data.displayNameEng == '') ? data.displayName : data.displayNameEng;
+        }else{
+            return data.displayName;
+        }
+    },
+
+    getGroupNameTrl: function(locale, data){
+        if(data == null || data == ''){
+            return null;
+        }
+        if(locale == 'en-US'){
+            return (data.groupNameEng == null || data.groupNameEng == '') ? data.groupName : data.groupNameEng;
+        }else{
+            return data.groupName;
+        }
+    },
+}

+ 6 - 0
src/lang/en-US.js

@@ -0,0 +1,6 @@
+export const lang = {
+    login: {
+        login: "Login",
+    },
+    
+}

+ 6 - 0
src/lang/zh-CN.js

@@ -0,0 +1,6 @@
+export const lang = {
+    login: {
+        login: "登陆",
+    },
+
+}

+ 41 - 1
src/router/index.js

@@ -2,8 +2,48 @@
 
 const routes = [
 
-	{ path: '/wms/hello-world', component: resolve => require.ensure([], () => resolve(require('../components/HelloWorld.vue')), 'client-wms-0') },
+	{ path: '/trace/hello-world', component: resolve => require.ensure([], () => resolve(require('../components/HelloWorld.vue')), 'client-trace-0') },
 
+	// 追踪单主界面
+	// { path: '/trace/traceMenu/:uuid', component: resolve => require.ensure([], () => resolve(require('../trace/TraceMenu.vue')), 'client-trace-1') },
+	{ path: '/trace/traceList/:traceState', component: resolve => require.ensure([], () => resolve(require('../trace/TraceList.vue')), 'client-trace-2') },
+	{ path: '/trace/traceDynamic', component: resolve => require.ensure([], () => resolve(require('../trace/TraceDynamic.vue')), 'client-trace-3') },
+	//项目人员列表
+	{ path: '/trace/projectUserList/:projectId', component: resolve => require.ensure([], () => resolve(require('../trace/ProjectUserList.vue')), 'client-trace-4') },
+	// 项目追踪
+	{ path: '/trace/projectList', component: resolve => require.ensure([], () => resolve(require('../trace/ProjectList.vue')), 'client-trace-5') },
+	// 追踪单主界面-新建追踪单
+	{ path: '/trace/traceCreate/:projectId', component: resolve => require.ensure([], () => resolve(require('../trace/TraceCreate.vue')), 'client-trace-6') },
+	{ path: '/trace/traceUpdate/:traceId', component: resolve => require.ensure([], () => resolve(require('../trace/TraceUpdate.vue')), 'client-trace-7') },
+	// 追踪单主界面(追踪列表)-追踪单		
+	{ path: '/trace/trace/:traceId', component: resolve => require.ensure([], () => resolve(require('../trace/Trace.vue')), 'client-trace-8') },
+	// 创建追踪单评论信息
+	{ path: '/trace/traceCommentCreate/:traceId', component: resolve => require.ensure([], () => resolve(require('../trace/TraceCommentCreate.vue')), 'client-trace-9') },
+	// 创建追踪单评论信息
+	{ path: '/trace/traceCommentEdit/:traceCommentId', component: resolve => require.ensure([], () => resolve(require('../trace/TraceCommentEdit.vue')), 'client-trace-10') },
+	// 创建追踪单时间节点
+	{ path: '/trace/traceTimeLineCreate/:traceId', component: resolve => require.ensure([], () => resolve(require('../trace/TraceTimeLineCreate.vue')), 'client-trace-11') },
+	// 创建追踪单时间节点
+	{ path: '/trace/traceTimeLineEdit/:traceTimeLineId', component: resolve => require.ensure([], () => resolve(require('../trace/TraceTimeLineEdit.vue')), 'client-trace-12') },
+	// 个人未完成的任务
+	{ path: '/trace/userNotFinishedTrace/:userId', component: resolve => require.ensure([], () => resolve(require('../trace/UserNotFinishedTrace.vue')), 'client-trace-17') },
+	// 个人已完成的任务
+	{ path: '/trace/useFinishedTrace/:userId', component: resolve => require.ensure([], () => resolve(require('../trace/UseFinishedTrace.vue')), 'client-trace-18') },
+	// 未完成的项目任务
+	{ path: '/trace/notFinishedProjectTraces/:projectId', component: resolve => require.ensure([], () => resolve(require('../trace/NotFinishedProjectTraces.vue')), 'client-trace-19') },
+	// 已完成的项目任务
+	{ path: '/trace/finishedProjectTraces/:projectId', component: resolve => require.ensure([], () => resolve(require('../trace/FinishedProjectTraces.vue')), 'client-trace-19') },
+	{ path: '/trace/teamList', component: resolve => require.ensure([], () => resolve(require('../trace/TeamList.vue')), 'client-trace-20') },
+	//任务配置
+	{ path: '/trace/traceConfig', component: resolve => require.ensure([], () => resolve(require('../trace/TraceConfig.vue')), 'client-trace-22') },
+	// 项目归档界面
+	{ path: '/trace/projectArchive/:projectId', component: resolve => require.ensure([], () => resolve(require('../trace/ProjectArchive.vue')), 'client-trace-23') },
+	// 项目管理图
+	{ path: '/trace/projectManagement/:projectId', component: resolve => require.ensure([], () => resolve(require('../trace/ProjectManagement.vue')), 'client-trace-24') },
+	//项目管理员列表
+	{ path: '/trace/projectAdminUserList/:projectId', component: resolve => require.ensure([], () => resolve(require('../trace/ProjectAdminUserList.vue')), 'client-trace-25') },
+	// 已完成项目追踪
+	{ path: '/trace/completeProjectList', component: resolve => require.ensure([], () => resolve(require('../trace/CompleteProjectList.vue')), 'client-trace-26') },
 	
 ];
 

+ 239 - 0
src/trace/CompleteProjectList.vue

@@ -0,0 +1,239 @@
+<template>
+    <div>
+        <TraceHeader :type="'traceProject'"></TraceHeader>
+        <div>
+
+            <div >
+                <QueryWidget ref="queryWidget"
+                             @search="getDatas()"
+                             @valueChanged="getDatas()"></QueryWidget>
+                <p class="bg-danger" style="padding: 15px; margin-top:15px"                    
+                   v-if="projectList == null || projectList.length == 0">您参与的项目都在进行中</p>
+                <div class="row"
+                     style="margin-top: 10px;">
+                    <div class="col-md-4"
+                         v-for="item in projectList"
+                         :key="item.id">
+                        <div @click="openLine(item)"
+                             class="panel panel-default">
+                            <div class="panel-body"
+                                 style="height:70px;">
+                                <div>
+                                    <div>{{item.name}}</div>
+                                    <div>
+                                        <span>{{item.no}}</span>
+                                        <span style="color:green"
+                                              v-if="item.admin">
+                                           	 参与中
+                                        </span>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="row">
+                    <div class="col-md-12">
+                        <Pagination :pagination="pagination"
+                                    :callback="getDatas"
+                                    ></Pagination>
+                    </div>
+                </div>
+
+                <hr>
+                <button @click="isTheArchive"
+                        type="button"
+                        class="btn btn-link">
+                    进行中的项目</button>
+            </div>
+
+            <h3>&nbsp;</h3>
+        </div>
+    </div>
+</template>
+
+<script> 
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+
+import QueryWidget from "../widget/QueryWidget.vue";
+import TraceHeader from "./TraceHeader.vue";
+var Pagination = require("vue-bootstrap-pagination").default;
+var Navbar = require("pc-client-component").Navbar;
+
+export default {
+    data: function () {
+        return {
+            input: "",
+            searchText: "",
+            userId: "",
+            range: {
+                start: 0,
+                length: 30
+            },
+            totalSize: 1,
+            projectList: [],
+            pagination: {
+                total: 0,
+                //per_page: Common.pageSize,    // required
+                per_page: 30,    // required
+                current_page: 1, // required
+                last_page: 10,    // required
+            },
+            traceState: undefined,
+            traceStatus: undefined,
+            traceUserStatus: undefined,
+        }
+    },
+    components: {
+        Pagination, QueryWidget, Navbar, TraceHeader
+    },
+    methods: {
+        /**
+         * 查询数据
+         * @author ZhangTeng 20190220
+         */
+        getDatas: function () {
+            var _self = this;
+
+            _self.range = {
+                start: (_self.pagination.current_page - 1) * _self.pagination.per_page,
+                length: _self.pagination.per_page,
+            }
+
+            var queryParam = {
+                range: _self.range,
+                condition: _self.$refs.queryWidget.getSearchText()
+            }
+
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/queryProjectItemUserDto2'),
+                type: 'post',
+                dataType: 'json',
+                contentType: "application/json",
+                data: JSON.stringify(queryParam),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.projectList = data.dataList;
+                    _self.pagination.total = data.totalSize;
+                    _self.pagination.last_page = ((data.totalSize % data.range.length) == 0) ? (data.totalSize / data.range.length) : (Math.floor(data.totalSize / data.range.length) + 1);
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 显示归档/未归档的项目
+         */
+        isTheArchive: function () {
+            let routeData  = this.$router.push({path: '/desktop/projectList'});
+			window.open(routeData.href, '_blank');
+        },
+        /**
+         * 打开明细
+         */
+        openLine: function (obj) {
+            var uuid = Uuid.createUUID();
+            var _self = this;
+            this.$router.push(
+                {
+                    path: '/desktop/notFinishedProjectTraces/' + obj.id,
+                    query:
+                    {
+                        projectName: obj.name
+                    }                
+                });
+        },
+
+
+        open: function () {
+            history.back();
+        },
+
+    },
+
+    mounted: function () {
+        var uuid = this.$route.params.uuid;
+        if (uuid != null && uuid != undefined) {
+            var str = localStorage.getItem(uuid);
+
+            if (str != null) {
+                var object = JSON.parse(str);
+                var userId = JSON.parse(localStorage.json_LoginInfo).userId;
+
+                if (object.traceState != undefined) {
+                    this.traceState = object.traceState;
+                }
+                if (object.traceStatus != undefined) {
+                    this.traceStatus = object.traceStatus;
+                }
+                if (object.traceUserStatus != undefined) {
+                    this.traceUserStatus = object.traceUserStatus;
+                }
+            }
+        }
+        this.getDatas();
+    }
+}
+</script>
+
+<style scoped>
+.mui-card {
+    margin: 0px;
+    margin-top: 1px;
+}
+.mui-card-footer:before,
+.mui-card-header:after {
+    background-color: #ffffff;
+}
+.mui-card-content {
+    padding: 0px 10px;
+}
+.mui-card-content p {
+    margin: 0px;
+}
+p {
+    font-size: 14px !important;
+    padding: 2px 0px;
+    color: #000000;
+}
+.time {
+    /*color: #8f8f94;*/
+    font-weight: bold;
+}
+.index {
+    font-size: 16px;
+    font-weight: bold;
+}
+.div-statis {
+    margin-bottom: 5px;
+    font-size: 0.8em;
+    text-align: center;
+}
+.divs {
+    margin-top: 2%;
+    margin-left: 39%;
+}
+.div {
+    word-wrap: break-word;
+    white-space: normal;
+    font-size: 20px;
+}
+.div1 {
+    padding-left: 10%;
+    color: blue;
+}
+.div2 {
+    color: cadetblue;
+}
+.img {
+    height: 33px;
+    width: 38px;
+}
+
+</style>

+ 170 - 0
src/trace/Finished.vue

@@ -0,0 +1,170 @@
+<template>
+    <div>
+        <Navbar :title='userName + " 已完成的任务" + "(" + pagination.total + ")"'
+                :isGoBack="true"></Navbar>
+        <div v-for="trace in traces"
+             :key="trace.id"
+             style="margin-top: 5px; cursor: pointer;">
+            <div class="panel panel-default"
+                 style="margin-bottom: 0px;">
+                <div class="panel-body">
+                    <span>
+                        <Checkbox :id="'trace-finish-' + trace.id"
+                                  class-name="terms"
+                                  v-model="trace.finished"
+                                  class="trace-checkbox"
+                                  @input="updateTracefinished(trace)">
+                        </Checkbox>
+                    </span>
+                    <span @click="openLine(trace)">
+                        <span v-html="trace.summary"
+                              class="trace-summary"
+                              :class="{'font-color': trace.timeLineCompletion==true}">
+                        </span>
+                        <span class="glyphicon glyphicon-option-vertical trace-icon"
+                              aria-hidden="true"></span>
+                        <span class="label label-primary trace-user">
+                            <span v-html="trace.receiveUserName"></span>
+                            <span>{{formatDate(trace.planFinishedDate)}}</span>
+                        </span>
+                        <span class="badge">
+                            <span>{{trace.projectName}}</span>
+                        </span>
+                        <span class="badge">
+                            <span>完成时间:{{formatDate(trace.finishedDate)}}</span>
+                        </span>
+                    </span>
+                </div>
+            </div>
+        </div>
+        <Pagination :pagination="pagination"
+                    :callback="initData"
+                    
+                    v-if="traces.length">
+        </Pagination>
+        <p class="bg-danger"
+           style="padding: 15px;"
+           v-if="flag">未完成任何任务~</p>
+    </div>
+</template>
+
+<script>
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+import TraceCommon from "./TraceCommon.js";
+var Checkbox = require("pc-client-component").Checkbox;
+import TraceResource from "./TraceResource.js";
+var Navbar = require("pc-client-component").Navbar;
+var Pagination = require("vue-bootstrap-pagination").default;
+
+export default {
+    data: function () {
+        return {
+            traceNumber: {},
+            uuid: Uuid.createUUID(),
+            userId: '',
+            userName: '',
+            traces: [],
+            flag: false,
+            formatDate: TraceCommon.formatDate,
+            pagination: {
+                total: 0,
+                per_page: 10,    // 每页10条信息
+                current_page: 1, // 当前页码
+                last_page: 10,    // 最后页码
+                from: 1,
+                to: 10           // required
+            },
+        }
+    },
+    components: {
+        Common, Uuid, Navbar, Checkbox, TraceResource, TraceCommon, Pagination
+    },
+    methods: {
+
+        /**
+         * 初始化人员
+         */
+        initData: function () {
+            var _self = this;
+            _self.traces.splice(0, _self.traces.length);
+            var uuid = _self.$route.params.uuid;
+            var strData = localStorage.getItem(uuid);
+            var user = JSON.parse(strData);
+            _self.userId = user.userId;
+            _self.userName = user.userName;
+            $.ajax({
+                url: Common.getApiURL('TraceResource/useFinishedTrace'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "receiveUserId": user.userId,
+                    "currentPage": _self.pagination.current_page,
+                    "pageSize": _self.pagination.per_page,
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data == '') {
+                        _self.flag = true
+                    }
+                    // for(var index = 0; index < data.length; index ++){
+                    // 	_self.$set(_self.traces, index, data[index]);
+                    // }
+                    _self.pagination.total = data.totalSize;
+                    _self.pagination.last_page = Math.ceil(_self.pagination.total / _self.pagination.per_page);
+                    _self.traces = data.traceDtos
+                },
+            });
+        },
+
+        /**
+         * 打开明细
+         * @author ZhangTeng 2019131
+         */
+        openLine: function (obj) {
+            this.$router.push({
+                path: '/desktop/trace/' + obj.id
+            });
+        },
+        /**
+    * 修改追踪单的状态
+    * @author YangZhiJie 20171201
+    */
+        updateTracefinished: function (trace) {
+            var _self = this;
+            TraceResource.updateTraceFinished(trace.id).then(successData => {
+                _self.initData();
+            }, errorData => {
+                Common.processException(errorData);
+            });
+        },
+
+    },
+    mounted: function () {
+        this.initData();
+    }
+}
+</script>
+
+<style scoped>
+.trace-summary {
+    font-size: large;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.trace-icon {
+    opacity: 0.5;
+}
+
+.trace-user {
+    font-size: 100%;
+}
+.trace-checkbox {
+    display: inline;
+    font-size: large;
+}
+</style>

+ 325 - 0
src/trace/FinishedProjectTraces.vue

@@ -0,0 +1,325 @@
+<template>
+    <div>
+        <Navbar :title='projectName'
+                :isGoBack="true">
+            <button @click="goPersonList()"
+                    type="button"
+                    class="btn btn-link"
+                    style="padding: 0px;">
+                编辑人员({{projectUsers.length}})
+            </button>
+        </Navbar>
+
+        <h4>
+            已完成的任务({{pagination2.total}})
+            <div class="pull-right">
+            	<button class="btn btn-link"
+                        style="padding: 0px;"
+                        @click="projectManagement(projectId)">项目任务</button>
+                <button class="btn btn-link"
+                        style="padding: 0px;"
+                        @click="projectArchive(projectId)">项目归档</button>
+                <button class="btn btn-link"
+                        style="padding: 0px;"
+                        @click="addTrace(projectId)">添加任务</button>
+            </div>
+        </h4>
+		<div class="input-group">
+			<input type="text" class="form-control" v-model="content" placeholder="任务名称、任务内容" @blur="listFinishedTask" @keyup.enter="listFinishedTask">
+			<span class="input-group-btn">
+	        <button style="width:100%;background-color: #007aff;color: white;"  @click="listFinishedTask()" type="button" class="btn btn-blue search-button">
+				查询
+			</button>
+	      </span>
+		</div>
+        <div>
+            <div class="panel panel-default"
+                 style="margin-bottom: 10px;margin-top: 10px;">
+                <div class="panel-body">
+					<vuedraggable class="wrapper" v-model="finishedTraces" @change="end">
+						<transition-group>
+							<div v-for="trace in finishedTraces"
+							     :key="trace.id"
+							     style="margin-top: 5px; cursor: pointer;">
+							    <span>
+							        <span>
+							            <Checkbox :id="'trace-finish-' + trace.id"
+							                      class-name="terms"
+							                      v-model="trace.finished"
+							                      @input="updateTracefinished(trace)"
+							                      class="trace-checkbox">
+							            </Checkbox>
+							        </span>
+							        <span @click="openLine(trace)">
+							            <span v-html="trace.summary"
+							                  class="trace-summary"
+							                  :class="{'font-color': trace.timeLineCompletion==true}">
+							            </span>
+							            <span class="glyphicon glyphicon-option-vertical trace-icon"
+							                  aria-hidden="true"></span>
+							            <span class="label label-primary trace-user">
+							                <span v-html="trace.receiveUserName"></span>
+							                <span>{{formatDate(trace.planFinishedDate)}}</span>
+							            </span>
+							            <span class="badge">
+							                <span>完成时间:{{formatDate(trace.finishedDate)}}</span>
+							            </span>
+							        </span>
+							    </span>
+							</div>
+						</transition-group>
+					</vuedraggable>  
+                </div>
+
+            </div>
+			<div class="row" v-if="finishedTraces.length">
+				<div class="pull-left">
+					<span>第{{(pagination2.current_page-1)*pagination2.per_page+1}}-{{pagination2.current_page*pagination2.per_page}}条,共计{{pagination2.total}}条,每页显示</span>
+					<PageSizeSelect  v-on:pageSizeChanged="gridSizeSelect"></PageSizeSelect>
+					<span>条</span>
+				</div>
+			
+				<div class="pull-right">
+					<Pagination :pagination="pagination2" :callback="listFinishedTask"></Pagination>
+				</div>
+			</div>
+        </div>
+
+    </div>
+</template>
+
+<script>
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+import TraceCommon from "./TraceCommon.js";
+import TraceResource from "./TraceResource.js";
+var Navbar = require("pc-client-component").Navbar;
+var Pagination = require("vue-bootstrap-pagination").default;
+var PageSizeSelect = require("pc-client-component").PageSizeSelect;
+var Checkbox = require("pc-client-component").Checkbox;
+import vuedraggable from 'vuedraggable';
+
+export default {
+    data: function () {
+        return {
+            finishedTraces: [], //已完成的任务
+            isFinished: true, //显示 完成/未完成的任务
+            projectUsers: [],
+            obj: null,
+            projectName: '',
+            projectId: '',
+            pagination2: {
+                total: 0,
+                per_page: 50,    // 每页10条信息
+                current_page: 1, // 当前页码
+                last_page: 10,    // 最后页码
+            },
+            formatDate: TraceCommon.formatDate,
+            content:undefined
+        }
+    },
+    components: {
+        Navbar, Pagination, Checkbox,vuedraggable,PageSizeSelect
+    },
+    methods: {
+		gridSizeSelect: function(newPageSize) {
+			this.pagination2.per_page = newPageSize;
+			this.pagination2.current_page = 1;
+			// 刷新界面
+			this.listFinishedTask();
+		},
+		end(evt) {
+			var _self = this;
+			$.ajax({
+				url: Common.getApiURL('TraceResource/sortFinished'),
+				type: 'get',
+				dataType: 'json',
+				contentType: "application/json",
+				data: {
+					"id": evt.moved.element.id,
+					"projectId": _self.projectId,
+					"oldIndex": evt.moved.oldIndex,
+					"newIndex": evt.moved.newIndex,
+					"currentPage": _self.pagination2.current_page,
+					"pageSize": _self.pagination2.per_page,
+					"content": _self.content
+				},
+				beforeSend: function(request) {
+					Common.addTokenToRequest(request);
+				},
+				success: function(data) {
+					_self.listFinishedTask();
+				},
+				error: function(XMLHttpRequest, textStatus, errorThrown) {
+					Common.processException(XMLHttpRequest, textStatus, errorThrown);
+				}
+			})
+		},
+        /**
+         * 编辑
+         */
+        edit: function (trace) {
+            var _self = this;
+            this.$router.push("/desktop/traceUpdate/" + trace.id);
+        },
+
+        /**
+         * 初始化项目
+         */
+        listFinishedTask: function () {
+            var _self = this;
+            _self.finishedTraces.splice(0, _self.finishedTraces.length);
+            TraceResource.listFinishedByProjectId(_self.projectId, _self.pagination2, _self.content).then(data => {
+                if (data == null) {
+                    return;
+                }
+                _self.pagination2.total = data.totalSize;
+                _self.pagination2.last_page = Math.ceil(_self.pagination2.total / _self.pagination2.per_page);
+                _self.finishedTraces = data.traceDtos
+            }, error => {
+                Common.processException(error);
+            })
+        },
+
+
+
+        /**
+         * 初始化接收该项目的人数
+         * @author ZhangTeng 20190212
+         */
+        personNumber: function () {
+            var _self = this;
+            _self.projectUsers.splice(0, _self.projectUsers.length);
+
+            $.ajax({
+                url: Common.getApiURL('TraceResource/listByProjectId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "projectId": _self.projectId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data == null) {
+                        return;
+                    }
+                    for (var index = 0; index < data.length; index++) {
+                        _self.$set(_self.projectUsers, index, data[index]);
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            })
+        },
+
+        /**
+         * 打开明细
+         * @author ZhangTeng 20190131
+         */
+        openLine: function (obj) {
+            this.$router.push({
+                path: '/desktop/trace/' + obj.id
+            });
+        },
+
+        /**
+         * 添加任务
+         * @author ZhangTeng 20190201
+         */
+        addTrace: function (projectId) {
+            this.$router.push("/desktop/traceCreate/" + projectId);
+        },
+
+		/**
+		 * 项目归档
+		 * @param {Object} projectId
+		 * @author GuoZhiBo 20190926
+		 */
+        projectArchive: function (projectId) {
+            var _self = this;
+            this.$router.push({
+                path: '/desktop/projectArchive/' + projectId,
+                query:
+                {
+                    projectName: _self.projectName
+                }
+            });
+        },
+        /**
+		 * 项目任务
+		 * @param {Object} projectId
+		 * @author GuoZhiBo 20190926
+		 */
+		projectManagement: function (projectId) {
+			var _self = this;
+            this.$router.push({
+                    path: '/desktop/projectManagement/' + projectId,
+                    query:
+                    {
+                        projectName: _self.projectName
+                    }                
+                });
+        },
+        /**
+         *  跳转到人员界面
+         *  @author ZhangTeng 20190212
+         */
+        goPersonList: function () {
+            var _self = this;
+            this.$router.push({
+                path: "/desktop/projectUserList/" + _self.projectId,
+                query: {
+                    projectName: _self.projectName,
+                },
+            });
+        },
+
+        /**
+         * 修改追踪单的状态
+         * @author YangZhiJie 20171201
+         */
+        updateTracefinished: function (trace) {
+            var _self = this;
+            TraceResource.updateTraceFinished(trace.id).then(successData => {
+                _self.listFinishedTask();
+            }, errorData => {
+                Common.processException(errorData);
+            });
+        },
+
+    },
+
+    mounted: function () {
+        var _self = this;
+        this.projectId = _self.$route.params.projectId;
+        this.projectName = _self.$route.query.projectName;
+        this.personNumber();
+        this.listFinishedTask();
+    }
+}
+</script>
+
+<style scoped>
+.trace-summary {
+    font-size: large;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.trace-icon {
+    opacity: 0.5;
+}
+
+.trace-user {
+    font-size: 100%;
+}
+
+.trace-checkbox {
+    display: inline;
+    font-size: large;
+}
+</style>

+ 437 - 0
src/trace/NotFinishedProjectTraces.vue

@@ -0,0 +1,437 @@
+<template>
+	<div>
+		<Navbar :title='projectName' :isGoBack="true">
+			<button v-if="adminUserShow" @click="goAdminPersonList()" type="button" class="btn btn-link" style="padding: 5px;">
+				编辑项目管理员({{projectAdminUsers.length}})
+			</button>
+			<button v-if="userShow" @click="goPersonList()" type="button" class="btn btn-link" style="padding: 5px;">
+				编辑项目人员({{projectUsers.length}})
+			</button>
+		</Navbar>
+
+		<h4>
+			未完成的任务({{pagination.total}})
+			<div class="pull-right">
+				<button v-if="timeShow" class="btn btn-link" style="padding: 0px;" @click="projectManagement(projectId)">项目任务</button>
+				<button class="btn btn-link" style="padding: 0px;" @click="projectArchive(projectId)">项目归档</button>
+				<button class="btn btn-link" style="padding: 0px;" @click="addTrace(projectId)">添加任务</button>
+			</div>
+		</h4>
+		<div class="m-container">
+			<div class="input-group">
+				<input type="text" class="form-control" v-model="content" placeholder="任务名称、任务内容" @blur="listNotFinishedTask" @keyup.enter="listNotFinishedTask">
+				<span class="input-group-btn">
+		        <button style="width:100%;background-color: #007aff;color: white;"  @click="listNotFinishedTask()" type="button" class="btn btn-blue search-button">
+					查询
+				</button>
+		      </span>
+			</div>
+		</div>
+		<div class="panel panel-default" style="margin-bottom: 10px;margin-top: 10px;">
+			<div class="panel-body">
+				<vuedraggable class="wrapper" v-model="noFinishedTraces" @change="end" :delay="500" :delayOnTouchOnly="true">
+					<transition-group>
+						<div v-for="trace in noFinishedTraces" :key="trace.id" style="margin-top: 5px; cursor: pointer;">
+							<span>
+								<span>
+									<span>
+										<Checkbox :id="'trace-finish-' + trace.id" class-name="terms" v-model="trace.finished" class="trace-checkbox"
+										 @input="updateTracefinished(trace)">
+										</Checkbox>
+									</span>
+							<span @click="openLine(trace)">
+										<span v-html="trace.summary" class="trace-summary" :class="{'font-color': trace.timeLineCompletion==true}">
+										</span>
+							<span class="glyphicon glyphicon-option-vertical trace-icon" aria-hidden="true"></span>
+							<span class="label trace-user" :class="{'label-danger' : trace.overdue == true, 'label-primary' : trace.overdue != true}">
+											<span v-html="trace.receiveUserName"></span>
+							<span>{{formatDate(trace.planFinishedDate)}}</span>
+							</span>
+							</span>
+							</span>
+							<a class="fa-pull-right" @click="edit(trace)" style="color: blue;">编辑</a>
+							</span>
+						</div>
+					</transition-group>
+				</vuedraggable>
+			</div>
+		</div>
+		<div class="row" v-if="noFinishedTraces.length" style="margin-left:0px; margin-right: 0px;">
+			<div class="pull-left">
+				<span>第{{(pagination.current_page-1)*pagination.per_page+1}}-{{pagination.current_page*pagination.per_page}}条,共计{{pagination.total}}条,每页显示</span>
+				<PageSizeSelect v-on:pageSizeChanged="gridSizeSelect"></PageSizeSelect>
+				<span>条</span>
+			</div>
+
+			<div class="pull-right">
+				<Pagination :pagination="pagination" :callback="listNotFinishedTask" ></Pagination>
+			</div>
+		</div>
+		<!-- <Pagination :pagination="pagination" :callback="listNotFinishedTask"  v-if="noFinishedTraces.length"></Pagination> -->
+		<hr>
+
+		<button @click="openFinishedProjectTraces" type="button" class="btn btn-link" style="color:green">
+			查看已完成的任务</button>
+	</div>
+</template>
+
+<script>
+	var Common = require("../common/Common.js");
+	var Uuid = require("pc-client-component").Uuid;
+	import TraceCommon from "./TraceCommon.js";
+	import TraceResource from "./TraceResource.js";
+	var Navbar = require("pc-client-component").Navbar;
+	var Pagination = require("vue-bootstrap-pagination").default;
+	var PageSizeSelect = require("pc-client-component").PageSizeSelect;
+	var Checkbox = require("pc-client-component").Checkbox;
+	import vuedraggable from 'vuedraggable';
+
+	export default {
+		data: function() {
+			return {
+				finishedTraces: [], //已完成的任务
+				projectUsers: [],
+				projectAdminUsers: [],
+				projectName: '',
+				projectId: '',
+				noFinishedTraces: [], //未完成的任务
+				pagination: {
+					total: 0,
+					per_page: 50, // 每页50条信息
+					current_page: 1, // 当前页码
+					last_page: 10, // 最后页码
+				},
+				formatDate: TraceCommon.formatDate,
+				adminUserShow: false,
+				userShow: false,
+				timeShow: false,
+				content: undefined
+			}
+		},
+		components: {
+			Navbar,
+			Pagination,
+			Checkbox,
+			vuedraggable,
+			PageSizeSelect
+		},
+		methods: {
+			gridSizeSelect: function(newPageSize) {
+				this.pagination.per_page = newPageSize;
+				this.pagination.current_page = 1;
+				// 刷新界面
+				this.listNotFinishedTask();
+			},
+			end(evt) {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceResource/sortNotFinished'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"id": evt.moved.element.id,
+						"projectId": _self.projectId,
+						"oldIndex": evt.moved.oldIndex,
+						"newIndex": evt.moved.newIndex,
+						"currentPage": _self.pagination.current_page,
+						"pageSize": _self.pagination.per_page,
+						"content": _self.content
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						_self.listNotFinishedTask();
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+			/**
+			 * 编辑
+			 */
+			edit: function(trace) {
+				var _self = this;
+				this.$router.push("/desktop/traceUpdate/" + trace.id);
+			},
+
+			/**
+			 * 打开已完成的项目任务
+			 */
+			openFinishedProjectTraces: function() {
+				var _self = this;
+				this.$router.push("/desktop/finishedProjectTraces/" + _self.projectId + "?projectName=" + _self.projectName);
+			},
+
+			listNotFinishedTask: function() {
+				var _self = this;
+				_self.noFinishedTraces.splice(0, _self.noFinishedTraces.length);
+				$.ajax({
+					url: Common.getApiURL('TraceResource/listNotFinishedByProjectId'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": _self.projectId,
+						"currentPage": _self.pagination.current_page,
+						"pageSize": _self.pagination.per_page,
+						"content": _self.content
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if(data == null) {
+							return;
+						}
+						_self.pagination.total = data.totalSize;
+						_self.pagination.last_page = Math.ceil(_self.pagination.total / _self.pagination.per_page);
+						_self.noFinishedTraces = data.traceDtos
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+			//查询登录用户权限
+			personnelJurisdictionSet: function() {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceResource/queryPersonnelJurisdiction'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": _self.projectId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if(data == null) {
+							return;
+						}
+						if(data.levelOfPerson == 1) {
+							_self.timeShow = true
+							return;
+						}
+						if(data.levelOfPerson == 2) {
+							_self.timeShow = true,
+								_self.adminUserShow = false,
+								_self.userShow = true
+						}
+						if(data.levelOfPerson == 3) {
+							_self.timeShow = true,
+								_self.adminUserShow = true,
+								_self.userShow = true
+						}
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+
+			/**
+			 * 初始化接收该项目的人数
+			 * @author ZhangTeng 20190212
+			 */
+			personNumber: function() {
+				var _self = this;
+				_self.projectUsers.splice(0, _self.projectUsers.length);
+
+				$.ajax({
+					url: Common.getApiURL('TraceResource/listByProjectId'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": _self.projectId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if(data == null) {
+							return;
+						}
+						for(var index = 0; index < data.length; index++) {
+							_self.$set(_self.projectUsers, index, data[index]);
+						}
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+
+			/**
+			 * 初始化接收该项目的管理员人数
+			 * @author TangWenXiang 20191009
+			 */
+			adminPersonNumber: function() {
+				var _self = this;
+				_self.projectAdminUsers.splice(0, _self.projectUsers.length);
+
+				$.ajax({
+					url: Common.getApiURL('TraceResource/listAdminByProjectId'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": _self.projectId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if(data == null) {
+							return;
+						}
+						for(var index = 0; index < data.length; index++) {
+							_self.$set(_self.projectAdminUsers, index, data[index]);
+						}
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+			/**
+			 * 打开明细
+			 * @author ZhangTeng 20190131
+			 */
+			openLine: function(obj) {
+				this.$router.push({
+					path: '/desktop/trace/' + obj.id
+				});
+			},
+
+			/**
+			 * 添加任务
+			 * @author ZhangTeng 20190201
+			 */
+			addTrace: function(projectId) {
+				this.$router.push("/desktop/traceCreate/" + projectId);
+			},
+
+			/**
+			 * 项目归档
+			 * @param {Object} projectId
+			 * @author GuoZhiBo 20190926
+			 */
+			projectArchive: function(projectId) {
+				var _self = this;
+				this.$router.push({
+					path: '/desktop/projectArchive/' + projectId,
+					query: {
+						projectName: _self.projectName
+					}
+				});
+			},
+			/**
+			 * 项目任务
+			 * @param {Object} projectId
+			 * @author GuoZhiBo 20190926
+			 */
+			projectManagement: function(projectId) {
+				var _self = this;
+				this.$router.push({
+					path: '/desktop/projectManagement/' + projectId,
+					query: {
+						projectName: _self.projectName
+					}
+				});
+			},
+			/**
+			 *  跳转到人员界面
+			 *  @author ZhangTeng 20190212
+			 */
+			goPersonList: function() {
+				var _self = this;
+				this.$router.push({
+					path: "/desktop/projectUserList/" + _self.projectId,
+					query: {
+						projectName: _self.projectName,
+					},
+				});
+			},
+
+			/**
+			 *  跳转到项目管理人员界面
+			 *  @author ZhangTeng 20190212
+			 */
+			goAdminPersonList: function() {
+				var _self = this;
+				this.$router.push({
+					path: "/desktop/projectAdminUserList/" + _self.projectId,
+					query: {
+						projectName: _self.projectName,
+					},
+				});
+			},
+
+			/**
+			 * 修改追踪单的状态
+			 * @author YangZhiJie 20171201
+			 */
+			updateTracefinished: function(trace) {
+				var _self = this;
+				TraceResource.updateTraceFinished(trace.id).then(successData => {
+					_self.listNotFinishedTask();
+				}, errorData => {
+					Common.processException(errorData);
+				});
+			},
+
+			/**
+			 * 项目归档
+			 * @param {Object} projectId
+			 * @author GuoZhiBo 20190926
+			 */
+			projectManagement: function(projectId) {
+				var _self = this;
+				this.$router.push({
+					path: '/desktop/projectManagement/' + projectId,
+					query: {
+						projectName: _self.projectName
+					}
+				});
+			},
+
+		},
+
+		mounted: function() {
+			var _self = this;
+			this.projectId = _self.$route.params.projectId;
+			this.projectName = _self.$route.query.projectName;
+			this.personNumber();
+			this.adminPersonNumber();
+			this.listNotFinishedTask();
+			this.personnelJurisdictionSet();
+		}
+	}
+</script>
+
+<style scoped>
+	.trace-summary {
+		font-size: large;
+		margin-left: 5px;
+		margin-right: 5px;
+	}
+	
+	.trace-icon {
+		opacity: 0.5;
+	}
+	
+	.trace-user {
+		font-size: 100%;
+	}
+	
+	.trace-checkbox {
+		display: inline;
+		font-size: large;
+	}
+</style>

+ 252 - 0
src/trace/Personage.vue

@@ -0,0 +1,252 @@
+<template>
+    <div>
+        <Navbar :title='"团队——"+userName'
+                :isGoBack="true"></Navbar>
+        <div class="container-fluid"
+             id="trace-content">
+            <div class="boxes">
+                <div class="col-sm-4">
+                    <a class="box">
+                        <span>&nbsp;</span>
+                        <span class="box__statistics box__statistics--upcoming2">
+                            <span>{{userName.substr(userName.length-1,userName.length)}}</span>
+                        </span>
+                        <p class="box__title">{{userName}}</p>
+                    </a>
+                </div>
+            </div>
+        </div>
+        <hr>
+        <b style="font-size:20px">任务({{pagination.total}})</b>
+        <hr>
+        <button @click="finished(userId)"
+                type="button"
+                class="btn btn-link"
+                style="color:green;margin-left:90%">
+            查看已完成的任务</button>
+
+        <div v-for="trace in traces"
+             :key="trace.id"
+             style="margin-top: 5px; cursor: pointer;">
+            <div class="panel panel-default"
+                 style="margin-bottom: 0px;">
+                <div class="panel-body">
+                    <span>
+                        <Checkbox :id="'trace-finish-' + trace.id"
+                                  class-name="terms"
+                                  v-model="trace.finished"
+                                  class="trace-checkbox"
+                                  @input="updateTracefinished(trace)"
+                                  style=""></Checkbox>
+                    </span>
+                    <span @click="openLine(trace)">
+                        <span v-html="trace.summary"
+                              class="trace-summary"
+                              :class="{'font-color': trace.timeLineCompletion==true}">
+                        </span>
+                        <span class="glyphicon glyphicon-option-vertical trace-icon"
+                              aria-hidden="true"></span>
+                        <span class="label trace-user"
+                              :class="{'label-danger' : trace.overdue == true, 'label-primary' : trace.overdue != true}">
+                            <span v-html="trace.receiveUserName"></span>
+                            <span>{{formatDate(trace.planFinishedDate)}}</span>
+                        </span>
+                        <span class="badge">
+                            <span>{{trace.projectName}}</span>
+                        </span>
+                    </span>
+                </div>
+            </div>
+        </div>
+		<Pagination :pagination="pagination"
+                    :callback="initData"
+                    
+                    v-if="traces.length">
+		</Pagination>
+        <p class="bg-danger"
+           style="padding: 15px;"
+           v-if="flag">未安排任何任务~</p>
+
+    </div>
+
+</template>
+
+<script>
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+var Navbar = require("pc-client-component").Navbar;
+import TraceCommon from "./TraceCommon.js";
+var Checkbox = require("pc-client-component").Checkbox;
+import TraceResource from "./TraceResource.js";
+var Pagination = require("vue-bootstrap-pagination").default;
+
+export default {
+    data: function () {
+        return {
+            uuid: Uuid.createUUID(),
+            userId: '',
+            userName: '',
+            traces: [],
+            flag: false,
+			formatDate: TraceCommon.formatDate,
+			pagination: {
+                total: 0,
+                per_page: 10,    // 每页10条信息
+                current_page: 1, // 当前页码
+                last_page: 10,    // 最后页码
+                from: 1,
+                to: 10           // required
+            },
+        }
+    },
+    components: {
+        Common, Uuid, Navbar, TraceCommon, TraceResource, Checkbox, Pagination
+    },
+    methods: {
+
+        /**
+         * 打开明细
+         * @author ZhangTeng 2019131
+         */
+        openLine: function (obj) {
+            this.$router.push({ 
+                path: '/desktop/trace/' + obj.id 
+            });
+        },
+        /**
+         * 初始化人员
+         */
+        initData: function () {
+			var _self = this;
+			_self.traces.splice(0, _self.traces.length);
+            var uuid = _self.$route.params.uuid;
+            var dataStr = localStorage.getItem(uuid);
+            var user = JSON.parse(dataStr);
+            _self.userId = user.userId;
+			_self.userName = user.userName;
+            $.ajax({
+                url: Common.getApiURL('TraceResource/userNotFinishedTrace'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: { 
+						"receiveUserId": user.userId,
+						"currentPage": _self.pagination.current_page,
+                        "pageSize": _self.pagination.per_page,
+					  },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+
+                    if (data == '') {
+                        _self.flag = true
+                    }
+                    // for (var index = 0; index < data.length; index++) {
+                    //     _self.$set(_self.traces, index, data[index]);
+					// }
+					_self.pagination.total = data.totalSize;
+                    _self.pagination.last_page = Math.ceil(_self.pagination.total / _self.pagination.per_page);
+                    _self.traces = data.traceDtos
+                },
+            });
+        },
+        /**
+		* 修改追踪单的状态
+		* @author YangZhiJie 20171201
+		*/
+        updateTracefinished: function (trace) {
+            var _self = this;
+            TraceResource.updateTraceFinished(trace.id).then(successData => {
+                _self.initData();
+            }, errorData => {
+                Common.processException(errorData);
+			});
+        },
+
+        finished: function (userId) {
+            var obj = {
+                userId: this.userId,
+                userName: this.userName
+            }
+            localStorage.setItem(this.uuid, JSON.stringify(obj))
+            this.$router.push("/desktop/finished/" + this.uuid);
+        },
+	},
+	
+    mounted: function () {
+        this.initData();
+
+    }
+}
+</script>
+
+<style scoped>
+.boxes .box {
+    display: block;
+    width: 288px;
+    height: 235px;
+    border-radius: 4px;
+    text-align: center;
+    color: #000;
+}
+.boxes .box__statistics {
+    display: block;
+    width: 90px;
+    height: 90px;
+    padding: 10px;
+    margin: 15px auto 0;
+    border-radius: 50%;
+    color: #fff;
+    font-size: 46px;
+}
+.boxes .box__statistics--upcoming2 {
+    background-color: #f6aa39;
+}
+a {
+    text-decoration: none;
+    margin: 10px;
+    font-size: 100%;
+    vertical-align: baseline;
+    background: transparent;
+}
+.boxes .box__title {
+    margin-top: 25px;
+    font-size: 24px;
+}
+.boxes .box__title__weeks {
+    font-size: 18px;
+}
+.box:hover {
+    background-color: ghostwhite;
+    width: 288px;
+    height: 235px;
+}
+.row {
+    height: 200px;
+    margin-bottom: 10px;
+}
+.dashboard-header {
+    cursor: pointer;
+    font-family: "\5FAE\8F6F\96C5\9ED1";
+    font-weight: bold;
+    font-size: 1.1em;
+}
+.trace-summary {
+    font-size: large;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.trace-icon {
+    opacity: 0.5;
+}
+
+.trace-user {
+    font-size: 100%;
+}
+.trace-checkbox {
+    display: inline;
+    font-size: large;
+}
+</style>

+ 223 - 0
src/trace/ProjectAdminUserList.vue

@@ -0,0 +1,223 @@
+<template>
+    <div class="container-fluid">
+        <Navbar :title="projectName + '-项目人员'"
+                :isGoBack="true"></Navbar>
+
+        <button @click="edit"
+                type="button"
+                class="btn btn-success"
+                style="margin-bottom: 10px; margin-top: 10px;">编辑</button>
+
+        <div v-if="editFlag">
+            <div class="row">
+                <div class="col-sm-6 col-md-4"
+                     :key="user.id"
+                     v-for="user in userList">
+                    <div class="thumbnail">
+                        <div class="caption">
+                            <h4><input type='checkbox'
+                                       :value="user.userId"
+                                       v-model="user.selected" />
+                                {{user.name}} </h4>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <button @click="save()"
+                    type="button"
+                    class="btn btn-danger">保存项目管理人员</button>
+        </div>
+
+        <div v-else>
+            <p class="bg-danger"
+               style="padding: 15px;"
+               v-if="projectItemUsers == null || projectItemUsers.length == 0">该项目未有任何人员参与</p>
+            <div class="row">
+                <div class="col-sm-6 col-md-4"
+                     :key="user.id"
+                     v-for="user in projectItemUsers">
+                    <div class="thumbnail"
+                         @click="openLine2(user)">
+                        <div class="caption">
+                            <h4>
+                                {{user.userName}} </h4>
+                        </div>
+                    </div>
+                </div>
+
+            </div>
+        </div>
+    </div>
+
+</template>
+
+<script>
+var Navbar = require("pc-client-component").Navbar;
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+
+export default {
+    data: function () {
+        return {
+            projectId: '',
+            projectName: '',
+            userList: [],
+            projectItemUsers: [],
+            // 编辑的标识符
+            editFlag: false,
+        }
+    },
+    components: {
+        Navbar
+    },
+    methods: {
+        /**
+         * 初始化项目
+         */
+        initData: function () {
+            this.projectId = this.$route.params.projectId;
+            this.projectName = this.$route.query.projectName;
+        },
+        
+        /**
+         * 加载所有的人员
+         * @author ZhangTeng 20190212
+         */
+        initUsers: function () {
+            var _self = this;
+
+            // 清空人员数组
+            _self.userList.splice(0, _self.userList.length);
+
+            $.ajax({
+                url: Common.getApiURL('userResource/listByClientId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    for (var index = 0; index < data.length; index++) {
+                        data[index].selected = false;
+                        _self.$set(_self.userList, index, data[index]);
+                    }
+                    _self.initProjectItemUser();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 打开用户的任务
+         */
+        openLine2: function (user) {
+            var uuid = Uuid.createUUID();
+            var obj={
+                userId:user.userId,
+                userName:user.userName
+            }
+            localStorage.setItem(uuid, JSON.stringify(obj));
+            this.$router.push("/desktop/userNotFinishedTrace/" + uuid);
+        },
+
+        /**
+         * 编辑人员
+         * @author ZhangTeng 20190212
+         */
+        edit: function () {
+            this.editFlag = !this.editFlag;
+            this.initProjectItemUser();
+        },
+
+        /**
+         * 初始化项目用户
+         * @author ZhangTeng 20190212
+         */
+        initProjectItemUser: function () {
+            var _self = this;
+
+            // 清空项目-人员数组
+            _self.projectItemUsers.splice(0, _self.projectItemUsers.length);
+
+            $.ajax({
+                url: Common.getApiURL('ProjectItemAdminUserResource/listByProjectItemId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "projectItemId": this.projectId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if(data == null){
+                        return;
+                    }
+                    for (var index1 = 0; index1 < _self.userList.length; index1++) {
+                        for (var index = 0; index < data.length; index++) {
+                            if (data[index].userId == _self.userList[index1].id) {
+                                _self.userList[index1].selected = true;
+                            }
+                        }
+                    }
+                    for (var index = 0; index < data.length; index++) {
+                        _self.$set(_self.projectItemUsers, index, data[index]);
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 保存项目用户
+         */
+        save: function () {
+            var _self = this;
+
+            var userIds = [];
+            for (var index1 = 0; index1 < _self.userList.length; index1++) {
+                if (true == _self.userList[index1].selected) {
+                    userIds.push(_self.userList[index1].id);
+                }
+            }
+
+            if (userIds.length == 0) {
+                Common.showDialog("不能保存", "至少需要选择一个人员", 'error');
+                return;
+            }
+
+            $.ajax({
+                url: Common.getApiURL('ProjectItemAdminUserResource/save?projectItemId=' + this.projectId),
+                type: 'post',
+                contentType: "application/json",
+                data: JSON.stringify(userIds),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.edit();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+
+        }
+    },
+
+    mounted: function () {
+        this.initData();
+        this.initUsers();
+    }
+}
+</script>
+
+<style scoped>
+</style>

+ 1126 - 0
src/trace/ProjectArchive.vue

@@ -0,0 +1,1126 @@
+<template>
+    <div class="">
+        <div class="">
+
+            <div class="flex-container">
+                <div class="flex-content">
+                    <!--文件夹管理-->
+                    <div class="flex-aside-left">
+                        <div>
+                            <Navbar :title='projectName'
+                                    :isGoBack="true">
+
+                            </Navbar>
+                            <div class="flex-box">
+                                <button @click="updateProjectArchiveFolderByTaskId()"
+                                        class="btn btn-default flex-item">项目任务自动生成</button>
+                            </div>
+                            <div class="flex-box">
+                                <button @click="openFolder()"
+                                        class="btn btn-default flex-item">新建</button>
+                                <button @click="openFolder4()"
+                                        class="btn btn-default flex-item" style="margin-left: 5px">修改</button>
+                                <button @click="openDeleteFolder()"
+                                        class="btn btn-default flex-item" style="margin-left: 5px">删除</button>
+                            </div>
+                            <div style="margin-top: 10px;"
+                                 id="tree">
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="flex-main">
+                        <div>
+                            <div class="flex-main-serch">
+                                <div class="form-inline">
+                                    <div class="flex-box">
+                                        <input class="form-control flex-item"
+                                               id="archiveName"
+                                               v-model="param"
+                                               placeholder="归档项名称或者文件名称"
+                                               @keyup.enter="queryByProjectArchiveDtosByParam()">
+
+                                        <button class="btn btn-default flex-item-1"
+                                                @click="queryByProjectArchiveDtosByParam()" style="margin-left: 5px">查询</button>
+                                        <button @click="openFolder1()"
+                                                class="btn btn-default flex-item-1" style="margin-left: 5px">新建归档项</button>
+                                        <button @click="openDeleteProject()"
+                                                class="btn btn-danger flex-item-1" style="margin-left: 5px">删除归档项</button>
+                                    </div>
+
+                                </div>
+                            </div>
+
+                            <table class="table"
+                                   style="margin-top: 10px;">
+                                <thead>
+                                    <tr>
+                                        <!--<th>
+									项目名称
+								</th>-->
+                                        <th>
+                                            归档项名称
+                                        </th>
+                                        <th>
+                                            创建时间
+                                        </th>
+                                        <th>
+                                            计划归档日期
+                                        </th>
+                                        <th>
+                                            归档文件名
+                                        </th>
+                                        <!--<th>
+									归档文件大小
+								</th>-->
+                                        <th>
+                                            归档人
+                                        </th>
+                                        <th>
+                                            归档时间
+                                        </th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                    <tr v-for="item,index in  projectArchiveDtos"
+                                        @click="trClick(item)"
+                                        @dblclick="openFolder2()"
+                                        :class="projectArchivesId == item.id ?'success':''">
+                                        <!--<td>
+									{{item.projectItemName}}
+								</td>-->
+                                        <td>
+                                            {{item.name}}
+                                        </td>
+                                        <td>
+                                            {{item.createDate}}
+                                        </td>
+                                        <td>
+                                            {{item.planArchiveDate}}
+                                        </td>
+                                        <td>
+                                            {{item.fileName}}
+                                        </td>
+                                        <!--<td>
+									{{item.fileSize}}
+								</td>-->
+                                        <td>
+                                            {{item.archiveUserName}}
+                                        </td>
+                                        <td>
+                                            {{item.archiveDate}}
+                                        </td>
+                                    </tr>
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                    <!--归档项下载和历史记录-->
+                    <div class="flex-aside-right">
+                        <div>
+                            <div class="tabbable"
+                                 id="tabs-911500">
+                                <ul class="nav nav-tabs">
+                                    <li class="active">
+                                        <a href="#panel1"
+                                           data-toggle="tab">详细信息</a>
+                                    </li>
+                                    <li>
+                                        <a href="#panel3"
+                                           data-toggle="tab">历史记录</a>
+                                    </li>
+                                </ul>
+                                <div class="tab-content">
+                                    <div class="tab-pane active"
+                                         id="panel1">
+                                        <div v-if="projectArchivesDto.fileName != '' && projectArchivesDto.fileName != undefined"
+                                             class="row m-row">
+                                            <div class="col-sm-12">
+                                                <div class="thumbnail">
+                                                    <span class="glyphicon-class">glyphicon glyphicon-file</span>
+                                                    <div class="caption">
+                                                        <p>
+                                                            <strong>文件名:</strong>{{projectArchivesDto.fileName}}
+                                                        </p>
+                                                        <p>
+                                                            <strong>文件大小:</strong>{{projectArchivesDto.fileSize}}KB
+                                                        </p>
+                                                        <p>
+                                                            <strong>上传人:</strong>{{projectArchivesDto.archiveUserName}}
+                                                        </p>
+                                                        <p>
+                                                            <strong>上传时间:</strong>{{projectArchivesDto.archiveDate}}
+                                                        </p>
+                                                        <button class="btn btn-link"
+                                                                @click="download(projectArchivesDto.fileName)">下载</button>
+
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="tab-pane"
+                                         id="panel3">
+                                        <template v-for="(item) in  projectArchiveAuditDtos">
+                                            <div v-if="(item.fileName != '' && item.fileName != undefined)"
+                                                 :key="item.id"
+                                                 class="row m-row">
+                                                <div class="col-sm-12">
+                                                    <div class="thumbnail">
+                                                        <span class="glyphicon-class">glyphicon glyphicon-file</span>
+                                                        <div class="caption">
+                                                            <p>
+                                                                <strong>文件名:</strong>{{item.fileName}}
+                                                            </p>
+                                                            <p>
+                                                                <strong>文件大小:</strong>{{item.fileSize}}KB
+                                                            </p>
+                                                            <p>
+                                                                <strong>上传人:</strong>{{item.archiveUserName}}
+                                                            </p>
+                                                            <p>
+                                                                <strong>上传时间:</strong>{{item.archiveDate}}
+                                                            </p>
+                                                            <button class="btn btn-link"
+                                                                    @click="download(item.fileName)">下载</button>
+
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </template>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+
+                    </div>
+
+                </div>
+            </div>
+        </div>
+
+        <div>
+            <Modal ref="modal1"
+                   :small="true"
+                   :showOkButton="true"
+                   :showCanelButton="true"
+                   @ok="saveProjectArchiveFolder"
+                   @cancel="cancel">
+                <template #header>
+                    {{type + '文件夹'}}
+                </template>
+                <label>文件名</label>
+                <input type="text"
+                       class="form-control"
+                       v-model="folderName"
+                       placeholder="文件夹名称" />
+            </Modal>
+
+            <Modal ref="modal4"
+                   :small="true"
+                   :showOkButton="true"
+                   :showCanelButton="true"
+                   @ok="updateProjectArchiveFolder"
+                   @cancel="cancel2">
+                <template #header>
+                    {{type + '文件夹'}}
+                </template>
+                <div class="form-group">
+                    <label for="projectFolder">上级文件名</label>
+                    <select id="projectFolder" class="form-control"></select>
+                </div>
+                <div class="form-group">
+                    <label>文件名</label>
+                    <input type="text"
+                           class="form-control"
+                           v-model="folderName"
+                           placeholder="文件夹名称" />
+                </div>
+            </Modal>
+
+            <!-- 是否删除文件夹 -->
+            <Modal ref="modal5"
+                   :small="true"
+                   :showOkButton="true"
+                   :showCanelButton="true"
+                   @ok="deleteFolder"
+                   @cancel="cancelFolder">
+                   
+                <template #header>
+                    {{'删除文件夹'}}
+                </template>
+                <h3>您确认要删除{{folderName}}文件夹吗?如果是的话,请点击【确定】按钮,否则点击【取消】按钮。</h3>
+            </Modal>
+            <!-- 是否删除归档项 -->
+            <Modal ref="modal6"
+                   :small="true"
+                   :showOkButton="true"
+                   :showCanelButton="true"
+                   @ok="deleteProjectArchives"
+                   @cancel="cancelProject">
+                   
+                <template #header>
+                    {{'删除归档项'}}
+                </template>
+                <h3>您确认要删除{{projectArchivesDto.name}}归档项吗?如果是的话,请点击【确定】按钮,否则点击【取消】按钮。</h3>
+            </Modal>
+			
+			<!--
+            	作者:GuoZhiBo
+            	时间:2019-10-14
+            	描述:归档项新增和修改
+            -->
+            <Modal ref="modal2"
+                   :small="true"
+                   :showOkButton="true"
+                   :showCanelButton="true"
+                   @ok="saveProjectArchives"
+                   @cancel="cancel1">
+                <template #header>
+                    {{type + '归档项'}}
+                </template>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">归档项名称</label>
+                    <input style="width: 100%;"
+                           class="form-control"
+                           v-model="projectArchivesDto.name"></input>
+                </div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">计划归档日期</label>
+                    <DateWidget @on-value-change="dateChanged"
+                                :dateValue="projectArchivesDto.planArchiveDate"
+                                class="form-control"></DateWidget>
+                </div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">文件名称</label>
+                    <input style="width: 100%;"
+                           class="form-control"
+                           v-model="projectArchivesDto.fileName"
+                           readonly="readonly"></input>
+                    <!--<span @click="projectArchivesDto.fileName = ''">删除</span>-->
+                </div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">文件大小(KB)</label>
+                    <input style="width: 100%;"
+                           class="form-control"
+                           v-model="projectArchivesDto.fileSize"
+                           readonly="readonly"></input>
+                </div>
+                <div>
+                    <input ref="fileInput"
+                           type="file"
+                           class="form-control file-input"
+                           @change="onFileChanges" />
+                    <label for="attachment">
+                        <a role="button"
+                           class="btn btn-primary btn-sm"
+                           @click="clickButton">
+                            <i class="fa fa-upload" />
+                            &nbsp;
+                            上传附件
+                        </a>
+                    </label>
+                    <button @click="cleanFile(projectArchivesDto)"
+                            class="btn btn-primary">清空文件</button>
+                </div>
+            </Modal>
+        </div>
+        <div>
+			<Loading ref="loading"></Loading>
+		</div>
+    </div>
+</template>
+<script type="text/javascript">
+var Notify = require("pc-client-component").Notify;
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+var UpladFile = require("../widget/UpladFile.js");
+var Navbar = require("pc-client-component").Navbar;
+var DynamicJsLoader = require("../common/DynamicJsLoader.js");
+var Modal = require("pc-client-component").Modal;
+var Loading = require("pc-client-component").Loading;
+var DateWidget = require("pc-client-component").Date;
+
+export default {
+    data: function () {
+        return {
+            projectId: '', //项目Id
+            projectName: undefined,
+            projectArchiveFolderDtos: [], //文件夹dtos
+            folderName: undefined, //文件夹名称
+            tree: undefined,
+            projectArchiveDtos: [],//归档项Dto
+            projectArchivesDto: {
+                id: undefined,
+                name: undefined,
+                projectItemName: undefined,
+                projectItemId: undefined,
+                createDate: undefined,
+                planArchiveDate: undefined,
+                fileName: undefined,
+                fileSize: undefined,
+                archiveUserName: undefined,
+                archiveUserId: undefined,
+                archiveDate: undefined,
+                projectArchiveFolderId: undefined,
+                projectArchiveFolderName: undefined
+            },
+            className: "com.leanwo.prodog.model.project.ProjectArchives",
+            type: undefined,
+            folderId: undefined,//选中文件夹Id
+            projectArchivesId: undefined,//选中归档项Id
+            param: undefined,//查询参数
+            projectArchiveAuditDtos: [],//历史归档项记录
+            parentProjectArchiveFolderId: undefined,//上级文件夹Id
+            data: undefined
+        }
+    },
+    components: {
+        Navbar,
+        Common,
+        Notify,
+        UpladFile,
+        DynamicJsLoader,
+        Modal,
+        Loading,
+        DateWidget
+    },
+    methods: {
+        /**
+         * 清空文件
+         * @param {Object} item
+         */
+        cleanFile: function (item) {
+            var _self = this;
+            _self.$refs.modal2.show = false;
+            if (item.id != undefined) {
+                $.ajax({
+                    url: Common.getApiURL('ProjectArchivesResource/cleanFile'),
+                    type: "post",
+                    contentType: "application/json",
+                    data: JSON.stringify(_self.projectArchivesDto),
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    success: function (data) {
+                        Notify.success("提示", "清空文件成功!", 1000);
+                        item.fileName = undefined;
+                        item.fileSize = undefined;
+                        _self.queryByProjectArchiveDtos();
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+                item.fileName = undefined;
+                item.fileSize = undefined;
+            }
+        },
+        /**
+         * 下载文件
+         * @param  {String} fileName 文件名
+         * @return {void}          
+         */
+        download: function (fileName) {
+            var _self = this;
+            var downloadUrl = Common.getResourceUrl('file', _self.className, fileName);
+            window.open(downloadUrl);
+        },
+        /**
+         * 表格点击事件
+         * @param {Object} item
+         */
+        trClick: function (item) {
+            var _self = this;
+            _self.projectArchivesId = item.id
+            _self.projectArchivesDto = item;
+        },
+        /**
+         * 点击上传按钮事件
+         * @return {[type]} [description]
+         */
+        clickButton: function () {
+            $(this.$refs.fileInput).click();
+        },
+        //日期选择
+        dateChanged: function (value) {
+            this.projectArchivesDto.planArchiveDate = value
+        },
+        /**
+         * 新建文件夹取消
+         */
+        cancel: function () {
+            var _self = this;
+            _self.$refs.modal1.show = false;
+        },
+        /**
+         * 归档项取消
+         */
+        cancel1: function () {
+            var _self = this;
+            _self.$refs.modal2.show = false;
+        },
+        /**
+         * 修改文件夹取消
+         */
+        cancel2: function () {
+            var _self = this;
+            _self.$refs.modal4.show = false;
+        },
+        /**
+         * 打开新建文件夹弹窗
+         */
+        openFolder: function () {
+            var _self = this;
+            _self.type = '新建';
+            _self.$refs.modal1.show = true;
+            _self.folderName = undefined;
+        },
+        /**
+         * 打开新建归档项弹窗
+         */
+        openFolder1: function () {
+            var _self = this;
+            _self.type = '新建';
+            if (_self.folderId == undefined) {
+                Notify.error("提示", "请先选择文件夹", false);
+                return;
+            }
+            _self.$refs.modal2.show = true;
+            _self.cleanProjectArchivesDto();
+        },
+        /**
+         * 打开编辑归档项弹窗2
+         */
+        openFolder2: function () {
+            var _self = this;
+            _self.type = '新建';
+            if (_self.projectArchivesDto.id == undefined) {
+                Notify.error("提示", "请先选择归档项", false);
+                return;
+            }
+            _self.$refs.modal2.show = true;
+        },
+        /**
+         * 打开修改文件夹弹窗
+         */
+        openFolder4: function () {
+            var _self = this;
+            _self.type = '修改';
+            if (_self.folderId == undefined) {
+                Notify.error("提示", "请先选择要修改的文件夹", false);
+                return;
+            }
+            _self.$refs.modal4.show = true;
+            _self.loadProjectFolder();
+        },
+        /**
+         * 返回上一页
+         */
+        back: function () {
+            history.back();
+        },
+        /**
+         * 初始化文件夹数据
+         */
+        initData: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchiveFolderResource/queryByProjectArchiveFolderDtos'),
+                type: 'get',
+                dataType: 'json',
+                contentType: 'application/json',
+                data: {
+                    projectId: _self.projectId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.projectArchiveFolderDtos = data;
+                    _self.folderId = undefined;
+                    _self.folderName = undefined;
+                    $('#tree').treeview({
+                        data: _self.projectArchiveFolderDtos,
+                        onNodeSelected: function (event, data) {
+                            _self.folderId = data.id;
+                            _self.folderName = data.name;
+                            _self.parentProjectArchiveFolderId = data.parentProjectArchiveFolderId;
+                        },
+                        onNodeUnselected: function (event, data) {
+                            _self.folderId = undefined;
+                            _self.parentProjectArchiveFolderId = undefined;
+                        }
+                    });
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 保存文件夹
+         */
+        saveProjectArchiveFolder: function () {
+            var _self = this;
+            _self.$refs.modal1.show = false;
+            var projectItemFolder = {
+                projectItemId: _self.projectId,
+                name: _self.folderName,
+                parentProjectArchiveFolderId: _self.folderId
+            }
+            $.ajax({
+                url: Common.getApiURL('ProjectArchiveFolderResource/saveProjectArchiveFolder'),
+                type: "post",
+                contentType: "application/json",
+                data: JSON.stringify(projectItemFolder),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    Notify.success("提示", "保存成功!", 1000);
+                    _self.folderName = undefined;
+                    _self.initData();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 修改文件夹
+         */
+        updateProjectArchiveFolder: function () {
+            var _self = this;
+            _self.$refs.modal4.show = false;
+            var projectItemFolder = {
+                projectItemId: _self.projectId,
+                name: _self.folderName,
+                id: _self.folderId,
+                parentProjectArchiveFolderId: _self.parentProjectArchiveFolderId
+            }
+            $.ajax({
+                url: Common.getApiURL('ProjectArchiveFolderResource/saveProjectArchiveFolder'),
+                type: "post",
+                contentType: "application/json",
+                data: JSON.stringify(projectItemFolder),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    Notify.success("提示", "修改成功!", 1000);
+                    _self.folderName = undefined;
+                    _self.initData();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        //查询当前文件夹下面所有的归档项
+        queryByProjectArchiveDtos: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchivesResource/queryByProjectArchiveDtos'),
+                type: 'get',
+                dataType: 'json',
+                contentType: 'application/json',
+                data: {
+                    folderId: _self.folderId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.projectArchiveDtos = data;
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 保存归档项
+         */
+        saveProjectArchives: function () {
+            var _self = this;
+            _self.$refs.modal2.show = false;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchivesResource/saveProjectArchives'),
+                type: "post",
+                contentType: "application/json",
+                data: JSON.stringify(_self.projectArchivesDto),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    Notify.success("提示", "保存成功!", 1000);
+                    _self.projectArchivesDto = data;
+                    _self.projectArchivesId = data.id;
+                    _self.queryByProjectArchiveDtos();
+                    _self.queryByProjectArchiveDtoAudit();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 选择文件发生改变
+         * @param  {[type]} e [description]
+         * @return {[type]}   [description]
+         */
+        onFileChanges: function (e) {
+            var files = e.target.files || e.dataTransfer.files;
+            if (!files.length)
+                return;
+            this.uploadFile(files[0]);
+        },
+
+        /**
+         * 上传文件
+         * @param  {File} selectedFile 选择的文件
+         */
+        uploadFile: function (selectedFile) {
+            var _self = this;
+            if (selectedFile == undefined) {
+                return;
+            }
+            _self.$refs.loading.show();
+            if (selectedFile.size != undefined && selectedFile.size != null) {
+                var formData = new FormData();
+                formData.append("上传文件", selectedFile);
+                formData.append("className", _self.className);
+                $.ajax({
+                    url: Common.getApiURL("file/classFileUpload"),
+                    type: "post",
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    data: formData,
+                    contentType: false,
+                    processData: false,
+                    success: function (data) {
+                    	_self.$refs.loading.hide();
+                        if (data != "error") {
+                            Notify.success("提示", "文件上传成功!", 1000);
+                            var fileName = data.substring(data.indexOf(":") + 1);
+                            _self.$set(_self.projectArchivesDto, "fileName", fileName);
+                            _self.$set(_self.projectArchivesDto, "fileSize", Math.round(selectedFile.size / 1024 * 100) / 100);
+                            if(_self.projectArchivesDto.name == null || _self.projectArchivesDto.name == ""){
+                                _self.$set(_self.projectArchivesDto, "name", fileName);
+                            }
+                            if(_self.projectArchivesDto.planArchiveDate == null || _self.projectArchivesDto.planArchiveDate == ""){
+                                _self.$set(_self.projectArchivesDto, "planArchiveDate", moment().format("YYYY-MM-DD HH:mm:ss"));
+                            }
+
+
+                            _self.saveProjectArchives();
+                        }
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    	_self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+            	_self.$refs.loading.hide();
+                Notify.error("提示", "上传的文件为空!");
+            }
+        },
+        /**
+         * 清空数据
+         */
+        cleanData: function () {
+            var _self = this;
+            _self.cleanProjectArchivesDto();
+            _self.projectArchiveDtos = [];
+            _self.projectArchivesId = undefined;
+        },
+        /**
+         * 清空归档项Dto
+         */
+        cleanProjectArchivesDto: function () {
+            var _self = this;
+            _self.projectArchivesDto = {
+                id: undefined,
+                name: undefined,
+                projectItemName: undefined,
+                projectItemId: _self.projectId,
+                createDate: undefined,
+                planArchiveDate: undefined,
+                fileName: undefined,
+                fileSize: undefined,
+                archiveUserName: undefined,
+                archiveUserId: undefined,
+                archiveDate: undefined,
+                projectArchiveFolderId: _self.folderId,
+                projectArchiveFolderName: undefined
+            }
+        },
+        /**
+         * 打开删除文件夹提示
+         * @author GuoZhiBo 20190929
+         */
+        openDeleteFolder: function () {
+            var _self = this;
+            if (_self.folderId == undefined) {
+                Notify.error("提示", "请选择要删除的文件夹!");
+                return;
+            }
+            _self.$refs.modal5.show = true;
+            _self.parentProjectArchiveFolderId = undefined;
+        },
+        /**
+         * 关闭删除文件夹提示
+         * @author GuoZhiBo 20190929
+         */
+        cancelFolder: function () {
+            var _self = this;
+            _self.$refs.modal5.show = false;
+        },
+        /**
+         * 打开删除归档项提示
+         * @author GuoZhiBo 20190929
+         */
+        openDeleteProject: function () {
+            var _self = this;
+            if (_self.projectArchivesId == undefined) {
+                Notify.error("提示", "请选择要删除的归档项!");
+                return;
+            }
+            _self.$refs.modal6.show = true;
+        },
+        /**
+         * 关闭删除文件提示
+         * @author GuoZhiBo 20190929
+         */
+        cancelProject: function () {
+            var _self = this;
+            _self.$refs.modal6.show = false;
+        },
+        /**
+         * 删除文件夹
+         * @author GuoZhiBo 20190927
+         */
+        deleteFolder: function () {
+            var _self = this;
+            _self.$refs.modal5.show = false;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchiveFolderResource/deleteProjectArchiveFolder'),
+                type: 'get',
+                dataType: 'json',
+                contentType: 'application/json',
+                data: {
+                    folderId: _self.folderId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.folderId = undefined;
+                    _self.initData();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 删除归档项
+         * @author GuoZhiBo 20190927
+         */
+        deleteProjectArchives: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchivesResource/deleteProjectArchives'),
+                type: 'get',
+                dataType: 'json',
+                contentType: 'application/json',
+                data: {
+                    projectArchiveId: _self.projectArchivesId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.projectArchivesId = undefined;
+                    _self.queryByProjectArchiveDtos();
+                    _self.cleanProjectArchivesDto();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 根据选中的文件夹id和param查询对应的归档项
+         */
+        queryByProjectArchiveDtosByParam: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchivesResource/queryByProjectArchiveDtosByParam'),
+                type: 'get',
+                dataType: 'json',
+                contentType: 'application/json',
+                data: {
+                    folderId: _self.folderId,
+                    param: _self.param
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.projectArchiveDtos = data;
+                    _self.projectArchivesId = undefined;
+                    _self.cleanProjectArchivesDto();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 根据选中的文件夹id和param查询对应的归档项
+         */
+        queryByProjectArchiveDtoAudit: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchivesResource/queryByProjectArchiveDtoAudit'),
+                type: 'get',
+                dataType: 'json',
+                contentType: 'application/json',
+                data: {
+                    projectArchivesId: _self.projectArchivesId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.projectArchiveAuditDtos = data;
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 项目任务自动生成文件夹
+         * @author GuoZhiBo 20190927
+         */
+        updateProjectArchiveFolderByTaskId: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('ProjectArchiveFolderResource/updateProjectArchiveFolderByTaskId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: 'application/json',
+                data: {
+                    projectItemId: _self.projectId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    Notify.success("提示", "自动生成文件夹成功!", 1000);
+                    _self.initData();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        /**
+         * 加载当前项目全部文件夹
+         */
+        loadProjectFolder: function () {
+            var _self = this;
+            var $c_HospitalCode = $("#projectFolder").select2({
+                // 请求搜索框数据
+                //data: _self.data,
+                placeholder: "父文件夹",
+                minimumInputLength: 0,
+                quietMillis: 250,
+                allowClear: true,
+                language: "zh-CN",
+                width: '100%',
+                // 请求搜索框数据
+                ajax: {
+                    url: function (params) {
+                        return Common.getApiURL("ProjectArchiveFolderResource/queryProjectArchiveFolderDto");
+                    },
+                    dataType: 'json',
+                    type: "get",
+                    delay: 250,
+                    minimumInputLength: 0,
+                    transport: function (params, success, failure) {
+                        params.beforeSend = Common.addTokenToRequest;
+                        params.error = Common.processException;
+                        var $request = $.ajax(params);
+                        $request.then(success);
+                        $request.fail(failure);
+                        return $request;
+                    },
+                    data: function (params) {
+                        return {
+                            "name": params.term,
+                            "projectId": _self.projectId,
+                            "folderId": _self.folderId
+                        };
+                    },
+                    processResults: function (data, params) {
+                        var more = (params * 10) <= data.length;
+                        for (var i = 0; i < data.length; i++) {
+                            data[i].text = data[i].name;
+                            data[i].id = data[i].id;
+                        }
+                        return { results: data, more: more };
+                    }
+                },
+            }).on("change", function () {
+                _self.parentProjectArchiveFolderId = $(this).val();
+            });
+            $.ajax({
+                url: Common.getApiURL("ProjectArchiveFolderResource/queryProjectArchiveFolderDto"),
+                data: {
+                    "name": undefined,
+                    "projectId": _self.projectId,
+                    "folderId": _self.folderId
+                },
+                dataType: 'json',
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    for (var d = 0; d < data.length; d++) {
+                        var item = data[d];
+                        if (item.id == _self.parentProjectArchiveFolderId) {
+                            var option = new Option(item.name, item.id, true, true);
+                            $c_HospitalCode.append(option);
+                        }
+                        if (_self.parentProjectArchiveFolderId == undefined) {
+                            $("#projectFolder").empty();
+                        }
+                    }
+                    $c_HospitalCode.trigger('change');//使用这个方法显示到select2上.
+                }
+            });
+        }
+    },
+
+    mounted: function () {
+        var _self = this;
+        _self.projectId = this.$route.params.projectId;
+        _self.projectName = _self.$route.query.projectName + "-项目归档";
+        if(_self.projectName.length > 7){
+        	_self.projectName = _self.projectName.substr(0,7)+"...";
+        }
+        DynamicJsLoader.loadBootstrapTree(function () {
+
+        })
+        setTimeout(function () {
+            _self.initData();
+        }, 300)
+    },
+
+    destroyed: function () {
+
+    },
+    watch: {
+        "folderId": function (currentValue, oldValue) {
+            this.cleanData();
+            this.queryByProjectArchiveDtos();
+        },
+        "projectArchivesId": function (currentValue, oldValue) {
+            this.queryByProjectArchiveDtoAudit();
+        }
+    }
+
+}
+</script>
+
+<style scoped>
+.flex-container {
+    display: flex;
+    /* 垂直*/
+    flex-direction: column;
+    width: 100%;
+    /*视口被均分为100单位的vh 占据整个窗口,扣掉顶部topNav的距离后,计算得到container的高度*/
+    height: calc(100vh - 80px);
+}
+
+.flex-content {
+    display: flex;
+    flex: 1;
+    height: calc(100vh - 80px);
+}
+
+.flex-aside-left {
+    flex: 0 0 300px;
+    /* margin-right: 10px; */
+    border: 1px solid #e4e4e4;
+    background-color: white;
+    padding: 10px;
+    height: calc(100vh - 80px);
+    overflow-y: auto;
+}
+
+.flex-aside-right {
+    flex: 0 0 300px;
+    /* margin-left: 10px; */
+    border: 1px solid #e4e4e4;
+    background-color: white;
+    padding: 10px;
+    height: calc(100vh - 80px);
+    overflow-y: auto;
+}
+
+.flex-main {
+    overflow: auto;
+    flex: 1;
+    border-top: 1px solid #e4e4e4;
+    border-bottom: 1px solid #e4e4e4;
+    overflow-y: auto;
+}
+
+.flex-main-serch {
+    padding: 10px;
+    background-color: white;
+    border-bottom: 1px solid #e4e4e4;
+}
+
+tr:hover {
+    background-color: #eee;
+}
+
+.file-input {
+    width: 0.1px;
+    height: 0.1px;
+    opacity: 0;
+    overflow: hidden;
+    position: absolute;
+    z-index: -1;
+}
+
+.m-row {
+    margin-top: 10px;
+}
+
+.flex-box {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: nowrap;
+    justify-content: center;
+    margin: 5px 0;
+}
+
+.flex-item {
+    flex: 1 1 auto;
+}
+
+.flex-item-1{
+    flex: 0 0 auto;
+}
+</style>

+ 260 - 0
src/trace/ProjectList.vue

@@ -0,0 +1,260 @@
+<template>
+    <div>
+        <TraceHeader :type="'traceProject'"></TraceHeader>
+        <div>
+            <div >
+                <QueryWidget ref="queryWidget"
+                             @search="getDatas()"
+                             @valueChanged="getDatas()"></QueryWidget>
+                <p class="bg-danger"
+                   style="padding: 15px; margin-top:15px;"
+                   v-if="projectList == null || projectList.length == 0">您未参与任何项目~</p>
+                <div class="row"
+                     style="margin-top: 10px;">
+                    <div class="col-md-4"
+                         v-for="item in projectList"
+                         :key="item.id">
+                        <div @click="openLine(item)"
+                             class="panel panel-default">
+                            <div class="panel-body"
+                                 style="height:70px;">
+                                <div>
+                                    <div>{{item.name}}</div>
+                                    <div>
+                                        <span>{{item.no}}</span>
+                                        <span style="color:red"
+                                              v-if="item.admin">
+                                            参与中
+                                        </span>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="row">
+                    <div class="col-md-12">
+                        <Pagination :pagination="pagination"
+                                    :callback="getDatas"
+                                    ></Pagination>
+                    </div>
+                </div>
+
+                <hr>
+                <button @click="isTheArchive"
+                        type="button"
+                        class="btn btn-link"
+                        style="color:green">
+                    已归档的项目</button>
+            </div>
+
+            <h3>&nbsp;</h3>
+        </div>
+    </div>
+</template>
+
+<script> 
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+
+import QueryWidget from "../widget/QueryWidget.vue";
+import TraceHeader from "./TraceHeader.vue";
+var Pagination = require("vue-bootstrap-pagination").default;
+var Navbar = require("pc-client-component").Navbar;
+
+export default {
+    data: function () {
+        return {
+            input: "",
+            searchText: "",
+            userId: "",
+            range: {
+                start: 0,
+                length: 30
+            },
+            range2: {
+                start: 0,
+                length: 30
+            },
+            totalSize: 1,
+            projectList: [],
+            projectList2: [],
+            pagination: {
+                total: 0,
+                //per_page: Common.pageSize,    // required
+                per_page: 30,    // required
+                current_page: 1, // required
+                last_page: 10,    // required
+            },
+            pagination2: {
+                total: 0,
+                //per_page: Common.pageSize,    // required
+                per_page: 30,    // required
+                current_page: 1, // required
+                last_page: 10,    // required
+                from: 1,
+                to: 10           // required
+            },
+            traceState: undefined,
+            traceStatus: undefined,
+            traceUserStatus: undefined,
+            //flag: false
+        }
+    },
+    components: {
+        Pagination, QueryWidget, Navbar, TraceHeader
+    },
+    methods: {
+        /**
+         * 查询数据
+         * @author GuoZhiBo 20180226
+         */
+        getDatas: function () {
+            var _self = this;
+
+            _self.range = {
+                start: (_self.pagination.current_page - 1) * _self.pagination.per_page,
+                length: _self.pagination.per_page,
+            }
+			var val = _self.$refs.queryWidget.getSearchText();
+			if(val != undefined && val.length > 0){
+				_self.range = {
+	                start: 0,
+	                length: _self.pagination.per_page,
+	            }
+			}
+            var queryParam = {
+                range: _self.range,
+                condition: _self.$refs.queryWidget.getSearchText()
+            }
+
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/queryProjectItemUserDto'),
+                type: 'post',
+                dataType: 'json',
+                contentType: "application/json",
+                data: JSON.stringify(queryParam),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.projectList = data.dataList;
+                    _self.pagination.total = data.totalSize;
+                    _self.pagination.last_page = Math.ceil(data.totalSize / data.range.length);
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 显示归档/未归档的项目
+         */
+        isTheArchive: function () {
+           this.$router.push("/desktop/completeProjectList");
+        },
+        /**
+         * 打开明细
+         */
+        openLine: function (obj) {
+            var uuid = Uuid.createUUID();
+            var _self = this;
+            this.$router.push(
+                {
+                    path: '/desktop/notFinishedProjectTraces/' + obj.id,
+                    query:
+                    {
+                        projectName: obj.name
+                    }                
+                });
+        },
+
+
+        open: function () {
+            history.back();
+        },
+
+    },
+
+    mounted: function () {
+        var uuid = this.$route.params.uuid;
+        if (uuid != null && uuid != undefined) {
+            var str = localStorage.getItem(uuid);
+
+            if (str != null) {
+                var object = JSON.parse(str);
+                var userId = JSON.parse(localStorage.json_LoginInfo).userId;
+
+                if (object.traceState != undefined) {
+                    this.traceState = object.traceState;
+                }
+                if (object.traceStatus != undefined) {
+                    this.traceStatus = object.traceStatus;
+                }
+                if (object.traceUserStatus != undefined) {
+                    this.traceUserStatus = object.traceUserStatus;
+                }
+            }
+        }
+        this.getDatas();
+    }
+}
+</script>
+
+<style scoped>
+.mui-card {
+    margin: 0px;
+    margin-top: 1px;
+}
+.mui-card-footer:before,
+.mui-card-header:after {
+    background-color: #ffffff;
+}
+.mui-card-content {
+    padding: 0px 10px;
+}
+.mui-card-content p {
+    margin: 0px;
+}
+p {
+    font-size: 14px !important;
+    padding: 2px 0px;
+    color: #000000;
+}
+.time {
+    /*color: #8f8f94;*/
+    font-weight: bold;
+}
+.index {
+    font-size: 16px;
+    font-weight: bold;
+}
+.div-statis {
+    margin-bottom: 5px;
+    font-size: 0.8em;
+    text-align: center;
+}
+.divs {
+    margin-top: 2%;
+    margin-left: 39%;
+}
+.div {
+    word-wrap: break-word;
+    white-space: normal;
+    font-size: 20px;
+}
+.div1 {
+    padding-left: 10%;
+    color: blue;
+}
+.div2 {
+    color: cadetblue;
+}
+.img {
+    height: 33px;
+    width: 38px;
+}
+
+</style>

+ 557 - 0
src/trace/ProjectManagement.vue

@@ -0,0 +1,557 @@
+<template>
+	<div class="container-fluid">
+		<Navbar :title="title" :isGoBack="true"></Navbar>
+		<GanttScale ref="ganttScale" ganttDivId="ganttContainer"></GanttScale>
+		<div style="line-height: 10px">
+			<br>
+		</div>
+		<div id="ganttContainer" style='width:100%; height:100%px; min-height: 500px; background-color:white;'></div>
+	</div>
+</template>
+
+<script>
+	var Common = require("../common/Common.js");
+	var Navbar = require("pc-client-component").Navbar;
+	var Loading = require("pc-client-component").Loading;
+	var GanttScale = require("../widget/GanttScale2.vue").default;
+	var DynamicJsLoader = require("../common/DynamicJsLoader.js");
+
+
+	module.exports = {
+		data: function() {
+			return {
+				title: "",
+				projectId: undefined,
+				userList: [],
+				canEdit: false
+			}
+		},
+
+		components: {
+			Common,
+			Navbar,
+			Loading,
+			GanttScale
+		},
+
+		methods: {
+			/**
+			 * 上一步
+			 */
+			undo: function() {
+				gantt.undo();
+			},
+			/**
+			 * 下一步
+			 */
+			redo: function() {
+				gantt.redo();
+			},
+			//查询所有任务及链接
+			showTaskDtos: function() {
+				var _self = this;
+				var obj = _self.$route.params.projectId;
+				$.ajax({
+					url: Common.getApiURL('ProjectTaskResource/queryProjectAndTasks'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": obj,
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if (data == null) {
+							return;
+						}
+						_self.title = data.projectname + "-时间节点";
+
+						var projectTaskDtos = data.data;
+						var events = [];
+						var projectTaskRelationDtos = data.links;
+						var events2 = [];
+						for (var i = 0; i < projectTaskDtos.length; i++) {
+							var event = {
+								id: projectTaskDtos[i].id,
+								text: projectTaskDtos[i].name,
+								remarks: projectTaskDtos[i].remarks,
+								owner_id: projectTaskDtos[i].owenUserId,
+								start_date: projectTaskDtos[i].startDate,
+								duration: projectTaskDtos[i].duration,
+								parent: projectTaskDtos[i].parentId,
+								type: projectTaskDtos[i].type,
+								open: true,
+								progress: projectTaskDtos[i].progress,
+								index: projectTaskDtos[i].sequenceNo
+							}
+							events.push(event);
+						}
+						for (var i = 0; i < projectTaskRelationDtos.length; i++) {
+							var event = {
+								id: projectTaskRelationDtos[i].id,
+								source: projectTaskRelationDtos[i].predecessorTaskId,
+								target: projectTaskRelationDtos[i].taskId,
+								type: projectTaskRelationDtos[i].type
+							}
+							events2.push(event);
+						}
+						var tasks = {
+							data: events,
+							links: events2
+						}
+						gantt.clearAll();
+						gantt.parse(tasks);
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+			/**
+			 * 初始化用户
+			 * @author ZhangTeng 20190221
+			 */
+			initData: function() {
+				var _self = this;
+				//var obj = _self.$route.params.projectId;
+				$.ajax({
+					url: Common.getApiURL('TraceResource/queryUserByProjectId'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": _self.projectId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						for (var x = 0; x < data.length; x++) {
+							var event = {
+								key: data[x].id,
+								label: data[x].name
+							}
+							_self.userList.push(event);
+						}
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+			//保存数据
+			runSaveGantt: function() {
+				var _self = this;
+				var canEdit = _self.canEdit;
+				if (canEdit == false) {
+					return;
+				}
+				var ganttData = [];
+				var tasks = gantt.getTaskByTime();
+				for (var x = 0; x < tasks.length; x++) {
+					var globalTaskIndex = gantt.getGlobalTaskIndex(tasks[x].id);
+					var event = {
+						id: tasks[x].id,
+						parentId: tasks[x].parent == 0 ? undefined : tasks[x].parent,
+						name: tasks[x].text,
+						type: tasks[x].type,
+						projectItemId: _self.$route.params.projectId,
+						startDate: tasks[x].start_date,
+						endDate: tasks[x].end_date,
+						progress: tasks[x].progress,
+						sequenceNo: globalTaskIndex,
+						remarks: tasks[x].remarks,
+						owenUserId: tasks[x].owner_id
+					}
+					ganttData.push(event);
+				}
+				var links = gantt.getLinks();
+				var links2 = [];
+				for (var x = 0; x < links.length; x++) {
+					var event = {
+						id: links[x].id,
+						predecessorTaskId: links[x].source,
+						taskId: links[x].target,
+						type: links[x].type
+					}
+					links2.push(event);
+				}
+				var projectItemDto2 = {
+					projectId: _self.$route.params.projectId,
+					data: ganttData,
+					links: links2
+				}
+				$.ajax({
+					url: Common.getApiURL("ProjectTaskResource/saveTasksAndLinks"),
+					type: "post",
+					dataType: "json",
+					contentType: "application/json",
+					data: JSON.stringify(projectItemDto2),
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						return;
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+			//获得登录用户权限
+			personnelJurisdictionSet: function() {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceResource/queryPersonnelJurisdiction'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": _self.projectId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if (data == null) {
+							return;
+						}
+						if (data.levelOfPerson == 1) {
+							_self.canEdit = false
+						}
+						if (data.levelOfPerson == 2 || data.levelOfPerson == 3) {
+							_self.canEdit = true
+						}
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+			//拿到前台数据
+			getProjectItemDto: function() {
+				var _self = this;
+				var ganttData = [];
+				var tasks = gantt.getTaskByTime();
+				for (var x = 0; x < tasks.length; x++) {
+					var globalTaskIndex = gantt.getGlobalTaskIndex(tasks[x].id);
+					var event = {
+						id: tasks[x].id,
+						parentId: tasks[x].parent == 0 ? undefined : tasks[x].parent,
+						name: tasks[x].text,
+						type: tasks[x].type,
+						projectItemId: _self.$route.params.projectId,
+						startDate: tasks[x].start_date,
+						endDate: tasks[x].end_date,
+						progress: tasks[x].progress,
+						sequenceNo: globalTaskIndex,
+						remarks: tasks[x].remarks,
+						owenUserId: tasks[x].owner_id
+					}
+					ganttData.push(event);
+				}
+				var links = gantt.getLinks();
+				var links2 = [];
+				for (var x = 0; x < links.length; x++) {
+					var event = {
+						id: links[x].id,
+						predecessorTaskId: links[x].source,
+						taskId: links[x].target,
+						type: links[x].type
+					}
+					links2.push(event);
+				}
+				var projectItemDto2 = {
+					projectId: _self.$route.params.projectId,
+					data: ganttData,
+					links: links2
+				}
+				return projectItemDto2;
+			},
+
+			//设置返回前台的数据
+			setDate: function(data) {
+				var _self = this;
+				_self.title = data.projectname + "-时间节点";
+
+				var projectTaskDtos = data.data;
+				var events = [];
+				var projectTaskRelationDtos = data.links;
+				var events2 = [];
+				for (var i = 0; i < projectTaskDtos.length; i++) {
+					var event = {
+						id: projectTaskDtos[i].id,
+						text: projectTaskDtos[i].name,
+						remarks: projectTaskDtos[i].remarks,
+						owner_id: projectTaskDtos[i].owenUserId,
+						start_date: projectTaskDtos[i].startDate,
+						duration: projectTaskDtos[i].duration,
+						parent: projectTaskDtos[i].parentId,
+						type: projectTaskDtos[i].type,
+						open: true,
+						progress: projectTaskDtos[i].progress,
+						index: projectTaskDtos[i].sequenceNo
+					}
+					events.push(event);
+				}
+				for (var i = 0; i < projectTaskRelationDtos.length; i++) {
+					var event = {
+						id: projectTaskRelationDtos[i].id,
+						source: projectTaskRelationDtos[i].predecessorTaskId,
+						target: projectTaskRelationDtos[i].taskId,
+						type: projectTaskRelationDtos[i].type
+					}
+					events2.push(event);
+				}
+				var tasks = {
+					data: events,
+					links: events2
+				}
+				return tasks;
+			},
+			datedifference: function(sDate1, sDate2) {
+				var dateSpan,
+					tempDate,
+					iDays;
+				if (sDate1 == undefined || sDate2 == undefined) {
+					return 0;
+				}
+				dateSpan = sDate2 - sDate1;
+				dateSpan = Math.abs(dateSpan);
+				iDays = (dateSpan / (24 * 3600 * 1000));
+				return iDays;
+			},
+		},
+
+
+		mounted: function() {
+			var _self = this;
+			_self.projectId = this.$route.params.projectId;
+			console.log(_self.projectId);
+			_self.initData();
+			_self.personnelJurisdictionSet();
+
+			window.ganttTimer = setInterval(() => {
+				// 某些定时器操作  
+				_self.runSaveGantt();
+			}, 60000);
+
+			DynamicJsLoader.loadDhtmlxgantt(function() {
+				gantt.serverList("staff", _self.userList);
+
+				// end test data
+				gantt.config.grid_width = 480;
+				gantt.config.grid_resize = true;
+				gantt.config.open_tree_initially = true;
+
+				var labels = gantt.locale.labels;
+				labels.section_description = "任务名";
+				labels.column_remarks = labels.section_remarks = "备注";
+				labels.column_owner = labels.section_owner = "责任人";
+
+				function byId(list, id) {
+					for (var i = 0; i < list.length; i++) {
+						if (list[i].key == id)
+							return list[i].label || "";
+					}
+					return "";
+				}
+				gantt.config.columns = [{
+						name: "text",
+						resize: true,
+						label: "任务名",
+						tree: true,
+						width: "*",
+						align: "left"
+					},
+					{
+						name: "start_date",
+						resize: true,
+						label: "开始日期",
+						width: 80,
+						align: "center",
+					},
+					{
+						name: "owner",
+						resize: true,
+						width: 50,
+						align: "center",
+						template: function(item) {
+							return byId(gantt.serverList('staff'), item.owner_id)
+						}
+					},
+					{
+						name: "duration",
+						resize: true,
+						label: "耗时",
+						width: 40,
+						align: "center",
+					},
+					{
+						name: "add",
+						width: 40
+					}
+				];
+				// gantt.locale.labels.section_period = "时间范围";
+				// gantt.config.lightbox_additional_height = 120;
+
+				var sections = [{
+						name: "description",
+						height: 40,
+						map_to: "text",
+						type: "textarea",
+					},
+					{
+						name: "owner",
+						height: 26,
+						map_to: "owner_id",
+						type: "select",
+						options: gantt.serverList("staff")
+					},
+					{
+						name: "type",
+						height: 26,
+						type: "typeselect",
+						map_to: "type",
+					},
+					{
+						name: "time",
+						height: 26,
+						type: "duration",
+						map_to: "auto"
+					},
+					{
+						name: "remarks",
+						map_to: "remarks",
+						type: "textarea",
+					}
+				];
+				gantt.config.lightbox.sections = sections;
+				gantt.config.lightbox.project_sections = sections;
+				gantt.config.lightbox.milestone_sections = sections;
+
+
+				gantt.templates.task_text = function(start, end, task) {
+					var progress = task.progress || 0;
+					return task.text + "(" + Math.floor(progress * 100) + "%" + ")";
+				};
+
+				//gantt.templates.grid_row_class =
+				//    gantt.templates.task_row_class =
+				//    gantt.templates.task_class = function (start, end, task) {
+				//       var css = [];
+				//        if (task.$virtual || task.type == gantt.config.types.project)
+				//           css.push("summary-bar");
+
+				//        if (task.owner_id) {
+				//            css.push("gantt_resource_task gantt_resource_" + task.owner_id);
+				//        }
+
+				//        return css.join(" ");
+				//    };
+
+				gantt.attachEvent("onLoadEnd", function() {
+					var styleId = "dynamicGanttStyles";
+					var element = document.getElementById(styleId);
+					if (!element) {
+						element = document.createElement("style");
+						element.id = styleId;
+						document.querySelector("head").appendChild(element);
+					}
+					var html = [];
+					var resources = gantt.serverList("staff");
+					element.innerHTML = html.join("");
+				});
+				//保存后执行
+				// gantt.attachEvent("onAfterTaskAdd", function (id, item) {
+				//     var projectItemDto = _self.getProjectItemDto();
+				//     _self.setTaskType(projectItemDto);
+				// });
+
+				//删除后执行
+				// gantt.attachEvent("onAfterTaskDelete", function (id, item) {
+				//     var projectItemDto = _self.getProjectItemDto();
+				//     _self.setTaskType(projectItemDto);
+				// });
+
+				//拖动任务后执行
+				// gantt.attachEvent("onAfterTaskMove", function (id, item) {
+				//     var projectItemDto = _self.getProjectItemDto();
+				//     _self.setTaskType(projectItemDto);
+				// });
+
+				//初始化自动排版
+				gantt.config.undo = true;
+				gantt.config.redo = true;
+				gantt.config.auto_scheduling = true;
+				gantt.autoSchedule();
+
+				//设置左边项目栏可拖动			
+				gantt.config.order_branch = true;
+
+				//设置右边显示进度条
+				gantt.config.layout = {
+					//css: "gantt_container",
+					rows: [{
+							cols: [{
+									view: "grid",
+									width: 320,
+									scrollY: "scrollVer"
+								},
+								{
+									resizer: true,
+									width: 1
+								},
+								{
+									view: "timeline",
+									scrollX: "scrollHor",
+									scrollY: "scrollVer"
+								},
+								{
+									resizer: true,
+									width: 1
+								},
+								{
+									view: "scrollbar",
+									id: "scrollVer"
+								}
+							]
+
+						},
+						{
+							view: "scrollbar",
+							id: "scrollHor",
+							height: 20
+						}
+					]
+				};
+				/* 				//显示进度百分比
+				                gantt.templates.progress_text = function(start, end, task) {
+				                    return "<span style='text-align:left;'></span>";
+				                }; */
+				//设置传入后台日期格式
+				gantt.config.xml_date = "%Y-%m-%d %H:%i:%s"; // XML中的日期格式
+				//允许出现异常时错误警报
+				gantt.config.show_errors = true;
+				//分上下级显示
+				// gantt.templates.task_class = function (st, end, item) {
+				//     return item.$level == 0 ? "gantt_project" : ""
+				// };
+				gantt.config.autosize = "y";
+
+				gantt.init("ganttContainer");
+				_self.$refs.ganttScale.setDefaultScale();
+				_self.showTaskDtos();
+			});
+		},
+
+		beforeDestroy: function() {
+			if (window.ganttTimer != null) {
+				window.clearInterval(ganttTimer);
+				window.ganttTimer = null;
+			}
+		}
+	}
+</script>

+ 224 - 0
src/trace/ProjectUserList.vue

@@ -0,0 +1,224 @@
+<template>
+    <div class="container-fluid">
+        <Navbar :title="projectName + '-项目人员'"
+                :isGoBack="true"></Navbar>
+
+        <button @click="edit"
+                type="button"
+                class="btn btn-success"
+                style="margin-bottom: 10px; margin-top: 10px;">编辑</button>
+
+        <div v-if="editFlag">
+            <div class="row">
+                <div class="col-sm-6 col-md-4"
+                     :key="user.id"
+                     v-for="user in userList">
+                    <div class="thumbnail">
+                        <div class="caption">
+                            <h4><input type='checkbox'
+                                       :value="user.userId"
+                                       v-model="user.selected" />
+                                {{user.name}} </h4>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <button @click="save()"
+                    type="button"
+                    class="btn btn-danger">保存项目成员</button>
+        </div>
+
+        <div v-else>
+            <p class="bg-danger"
+               style="padding: 15px;"
+               v-if="projectItemUsers == null || projectItemUsers.length == 0">该项目未有任何人员参与</p>
+            <div class="row">
+                <div class="col-sm-6 col-md-4"
+                     :key="user.id"
+                     v-for="user in projectItemUsers">
+                    <div class="thumbnail"
+                         @click="openLine2(user)">
+                        <div class="caption">
+                            <h4>
+                                {{user.userName}} </h4>
+                        </div>
+                    </div>
+                </div>
+
+            </div>
+        </div>
+    </div>
+
+</template>
+
+<script>
+var Navbar = require("pc-client-component").Navbar;
+var Common = require("../common/Common.js");
+var Notify = require("pc-client-component").Notify;
+var Uuid = require("pc-client-component").Uuid;
+
+export default {
+    data: function () {
+        return {
+            projectId: '',
+            projectName: '',
+            userList: [],
+            projectItemUsers: [],
+            // 编辑的标识符
+            editFlag: false,
+        }
+    },
+    components: {
+        Navbar
+    },
+    methods: {
+        /**
+         * 初始化项目
+         */
+        initData: function () {
+            this.projectId = this.$route.params.projectId;
+            this.projectName = this.$route.query.projectName;
+        },
+        
+        /**
+         * 加载所有的人员
+         * @author ZhangTeng 20190212
+         */
+        initUsers: function () {
+            var _self = this;
+
+            // 清空人员数组
+            _self.userList.splice(0, _self.userList.length);
+
+            $.ajax({
+                url: Common.getApiURL('userResource/listByClientId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    for (var index = 0; index < data.length; index++) {
+                        data[index].selected = false;
+                        _self.$set(_self.userList, index, data[index]);
+                    }
+                    _self.initProjectItemUser();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 打开用户的任务
+         */
+        openLine2: function (user) {
+            var uuid = Uuid.createUUID();
+            var obj={
+                userId:user.userId,
+                userName:user.userName
+            }
+            localStorage.setItem(uuid, JSON.stringify(obj));
+            this.$router.push("/desktop/userNotFinishedTrace/" + uuid);
+        },
+
+        /**
+         * 编辑人员
+         * @author ZhangTeng 20190212
+         */
+        edit: function () {
+            this.editFlag = !this.editFlag;
+            this.initProjectItemUser();
+        },
+
+        /**
+         * 初始化项目用户
+         * @author ZhangTeng 20190212
+         */
+        initProjectItemUser: function () {
+            var _self = this;
+
+            // 清空项目-人员数组
+            _self.projectItemUsers.splice(0, _self.projectItemUsers.length);
+
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/listByProjectItemId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "projectItemId": this.projectId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if(data == null){
+                        return;
+                    }
+                    for (var index1 = 0; index1 < _self.userList.length; index1++) {
+                        for (var index = 0; index < data.length; index++) {
+                            if (data[index].userId == _self.userList[index1].id) {
+                                _self.userList[index1].selected = true;
+                            }
+                        }
+                    }
+                    for (var index = 0; index < data.length; index++) {
+                        _self.$set(_self.projectItemUsers, index, data[index]);
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 保存项目用户
+         */
+        save: function () {
+            var _self = this;
+
+            var userIds = [];
+            for (var index1 = 0; index1 < _self.userList.length; index1++) {
+                if (true == _self.userList[index1].selected) {
+                    userIds.push(_self.userList[index1].id);
+                }
+            }
+
+            if (userIds.length == 0) {
+                Notify.error("不能保存", "至少需要选择一个人员", false);
+                return;
+            }
+
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/save?projectItemId=' + this.projectId),
+                type: 'post',
+                contentType: "application/json",
+                data: JSON.stringify(userIds),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.edit();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+
+        }
+    },
+
+    mounted: function () {
+        this.initData();
+        this.initUsers();
+    }
+}
+</script>
+
+<style scoped>
+</style>

+ 135 - 0
src/trace/TeamList.vue

@@ -0,0 +1,135 @@
+<template>
+    <div>
+        <TraceHeader :type="'team'"></TraceHeader>
+        <div class="container-fluid"
+             id="trace-content">
+            <div class="boxes">
+                <div class="col-xs-6 col-sm-4 col-md-3 col-lg-2"
+					:key="user.id"
+                    v-for="user in userList">
+                    <a class="box"
+                       @click="openUserNotFinishedTrace(user)">
+                        <span>&nbsp;</span>
+                        <span class="box__statistics box__statistics--upcoming">
+                            <span>{{user.name.substring(user.name.length-1,user.name.length)}}</span>
+                        </span>
+                        <p class="box__title">{{user.name}}</p>
+                    </a>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+var Navbar = require("pc-client-component").Navbar;
+import TraceHeader from "./TraceHeader.vue";
+export default {
+    data: function () {
+        return {
+            userList: []
+        }
+    },
+    components: {
+        Common, Uuid, Navbar, TraceHeader
+    },
+    methods: {
+
+        /**
+         * 初始化用户
+         * @author ZhangTeng 20190221
+         */
+        initData: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('userResource/listByClientId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.userList = data;
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 打开用户未完成的任务
+         * @author ZhangTeng 20190221
+         */
+        openUserNotFinishedTrace: function (user) {
+            this.$router.push({
+                path: '/desktop/userNotFinishedTrace/' + user.id,
+                query: {
+                    userName : user.name
+                }
+            })
+        }
+
+    },
+    mounted: function () {
+        this.initData();
+    }
+}
+</script>
+
+<style scoped>
+.boxes .box {
+    display: block;
+    /* width: 90px; */
+    height: 235px;
+    border-radius: 4px;
+    text-align: center;
+    color: #000;
+    cursor: pointer;
+}
+.boxes .box__statistics {
+    display: block;
+    width: 90px;
+    height: 90px;
+    padding: 10px;
+    margin: 15px auto 0;
+    border-radius: 50%;
+    color: #fff;
+    font-size: 46px;
+}
+.boxes .box__statistics--upcoming {
+    background-color: #f6aa39;
+}
+a {
+    text-decoration: none;
+    margin: 10px;
+    font-size: 100%;
+    vertical-align: baseline;
+    background: transparent;
+}
+.boxes .box__title {
+    margin-top: 25px;
+    font-size: 24px;
+}
+.boxes .box__title__weeks {
+    font-size: 18px;
+}
+.box:hover {
+    background-color: ghostwhite;
+    /* width: 90px; */
+    height: 235px;
+}
+.row {
+    height: 200px;
+    margin-bottom: 10px;
+}
+.dashboard-header {
+    cursor: pointer;
+    font-family: "\5FAE\8F6F\96C5\9ED1";
+    font-weight: bold;
+    font-size: 1.1em;
+}
+</style>s

+ 390 - 0
src/trace/Trace.vue

@@ -0,0 +1,390 @@
+<template>
+    <div>
+        <Navbar :title="'任务详细信息' + ((trace == null) ? '' : '-' + trace.projectName)"
+                :isGoBack="true">
+        </Navbar>
+        <div v-if="isflag">
+            <div>
+                <div class="row">
+                    <div align="center">
+                        <img @click="getTimeLineNumber()"
+                             class="mui-img"
+                             :src="finishedImageName" />
+                    </div>
+                </div>
+            </div>
+
+            <div>
+                <div class="page-header">
+                    <h3>
+                        <span v-html="trace.summary"></span>
+                        <span class="label"
+                              :class="{'label-danger' : trace.overdue == true, 'label-primary' : trace.overdue != true}">
+                            责任人:{{trace.receiveUserName}},
+                            时间节点:{{formatDate(trace.planFinishedDate)}}
+                        </span>
+                        <span class="pull-right">
+                            &nbsp;
+                            <button @click="copyShaneUrl"
+                                    type="button"
+                                    class="btn btn-link"
+                                    style="padding: 0px;">复制链接</button>
+                        </span>
+                        <span class="pull-right">
+                            &nbsp;
+                            <button @click="deleteTrace"
+                                    type="button"
+                                    class="btn btn-link"
+                                    style="padding: 0px;">删除</button>
+                        </span>
+                        <span class="pull-right">
+                            <button @click="edit"
+                                    type="button"
+                                    class="btn btn-link"
+                                    style="padding: 0px;">编辑</button>
+                        </span>
+                    </h3>
+                </div>
+            </div>
+
+            <div id = "v-html" v-html="trace.detail">
+            </div>
+			<AudioField :className = "className" :srcArray = "srcArray" @deleteAudioSrc = "" :isReadonly = "'true'"/>
+			
+            <TraceTimeLine ref="traceTimeLine"
+                           @refreshTraceLog="refreshTraceLog"
+                           :traceId="traceId"
+                           :trace="trace">
+            </TraceTimeLine>
+
+            <div class="page-header">
+                <h4>附件</h4>
+            </div>
+
+            <TraceAttachment :traceId="traceId">1</TraceAttachment>
+
+            <div>
+                <div class="page-header">
+                    <h4>评论信息</h4>
+                </div>
+                <TraceComment :traceId="traceId">
+                </TraceComment>
+            </div>
+
+            <div>
+                <button @click="openTraceComment"
+                        type="button"
+                        style="width: 100%;margin-top: 5px;"
+                        class="btn btn-default"
+                        aria-label="Left Align">
+                    <span class="glyphicon glyphicon-pencil"
+                          aria-hidden="true"></span>评论
+                </button>
+            </div>
+
+            <div style="margin-top:20px;">
+                <TraceLog ref="traceLog"
+                          :traceId="traceId"
+                          :trace="trace">
+                </TraceLog>
+            </div>
+
+        </div>
+        <Loading ref="loading"></Loading>
+    </div>
+</template>
+<script>
+var Vue = require('vue').default;
+var Uuid = require("pc-client-component").Uuid;
+var Common = require("../common/Common.js");
+var Notify = require("pc-client-component").Notify;
+var UpladFile = require("../widget/UpladFile.js");
+import TraceCommon from "./TraceCommon.js";
+import TraceResource from "./TraceResource.js";
+
+var TraceLog = require("./TraceLog.vue").default;
+var TraceComment = require("./TraceComment.vue").default;
+var TraceTimeLine = require("./TraceTimeLine.vue").default;
+var TraceAttachment = require("./TraceAttachment.vue").default;
+var Navbar = require("pc-client-component").Navbar;
+var Loading = require("pc-client-component").Loading;
+var AudioField = require("../widget/AudioField.vue").default;
+
+export default {
+    data: function () {
+        this.formatDate = TraceCommon.formatDate;
+
+        return {
+            "traceId": undefined,
+            "trace": {},
+            "finished": undefined,
+            count: 0,
+            attachmentList: [],
+            className: "com.leanwo.prodog.Trace",
+            srcArray:[],
+            isflag : true
+        }
+    },
+    components: {
+        TraceLog,
+        TraceComment,
+        TraceTimeLine,
+        Loading,
+        TraceAttachment,
+        Navbar,
+        UpladFile,
+        AudioField
+    },
+    methods: {
+    	/**
+    	 * 复制链接
+    	 * @author GuoZhiBo 20191024
+    	 */
+    	copyShaneUrl:function(){
+	       var input = document.createElement("input");     // 直接构建input
+	       var url = window.location.href;
+	       input.value = url;   // 设置内容
+	       document.body.appendChild(input);        // 添加临时实例
+	       input.select();      // 选择实例内容
+	       document.execCommand("Copy");     // 执行复制
+	       document.body.removeChild(input);  // 删除临时实例
+	        Notify.success("提示", "复制链接成功");
+	   },
+        back: function () {
+            history.back();
+        },
+
+        getCount: function () {
+            return this.$refs.traceTimeLine.count;
+        },
+
+        /**
+         * 初始化数据
+         * @author GuoZhiBo 20171201
+         */
+        initData: function () {
+            this.loadTraceById();
+        },
+
+        /**
+         * 根据追踪单Id查询追踪单的数据
+         */
+        loadTraceById: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceResource/uniqueByTraceId'),
+                type: 'get',
+                dataType: 'json',
+                data: {
+                    traceId: _self.traceId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data != null) {
+                        _self.traceId = data.id;
+                        _self.finished = data.finished;
+                        _self.trace = data;
+                        if(data.audioSrcs != null){
+                        	_self.srcArray = JSON.parse(data.audioSrcs);
+                        }
+                    }
+                    
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        getUrl:function(item){
+        	var _self = this;
+        	var url = Common.getApiURL('file/fileDownload') + "?className=" + _self.className
+                					+ "&fileName=" + item;
+            return url;
+        },
+        /**
+         * 修改追踪单的状态
+         * @author GuoZhiBo 20171201
+         */
+        updateTracefinished: function () {
+            var _self = this;
+            if (_self.count == 0) {
+                _self.$refs.loading.show();
+                TraceResource.updateTraceFinished(_self.trace.id).then(successData => {
+                    _self.loadTraceById();
+                    _self.$refs.loading.hide();
+                    _self.$refs.traceLog.getTraceLog();
+                }, errorData => {
+                    _self.$refs.loading.hide();
+                    Common.processException(errorData);
+                });
+            } else {
+                Notify.error("提示", "还有时间节点没有完成!");
+            }
+        },
+
+        getTimeLineNumber: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceTimeLineResource/queryTraceTimeLineNumberFalse'),
+                type: 'get',
+                dataType: 'json',
+                async: false,
+                data: {
+                    traceId: _self.trace.id
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.count = data;
+                    _self.updateTracefinished();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+
+                }
+            });
+        },
+
+        /**
+         * 打开评论界面进行评论
+         * @author GuoZhiBo 20171201
+         */
+        openTraceComment: function () {
+            var _self = this;
+            this.$router.push("/desktop/traceCommentCreate/" + _self.trace.id);
+        },
+
+        /**
+         * 获取图片地址
+         * @param  {String} item 图片名称
+         * @return {String}      图片URL地址
+         */
+        getImageSrc: function (item) {
+            var _self = this;
+            if (item != undefined && item != null) {
+                return Common.getResourceUrl("image", "com.leanwo.prodog.trace.model.TraceComment", item);
+            } else {
+                return "";
+            }
+        },
+
+
+
+        /**
+         * 编辑
+         */
+        edit: function () {
+            var _self = this;
+            _self.$router.push("/desktop/traceUpdate/" + _self.traceId);
+        },
+
+        /**
+         * 删除
+         */
+        deleteTrace: function () {
+            var _self = this;
+
+            Notify.show({
+                title: "删除确认",
+                message:
+                    "您确定要删除任务" +
+                    _self.trace.summary +
+                    '吗?如果"是"的话,请点击"确定"按钮,否则点击"取消"按钮',
+                buttons: [
+                    {
+                        label: "确定",
+                        cssClass: "btn-primary",
+                        action: function (dialogItself) {
+                            dialogItself.close();
+                            $.ajax({
+                                url: Common.getApiURL('TraceResource/deleteTraceById'),
+                                type: 'get',
+                                dataType: 'json',
+                                data: {
+                                    traceId: _self.traceId
+                                },
+                                beforeSend: function (request) {
+                                    Common.addTokenToRequest(request);
+                                },
+                                success: function (data) {
+                                    Notify.success("提示", "删除成功!");
+                                    _self.back();
+                                },
+                                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                                    Notify.error("提示", "删除失败!");
+                                }
+                            });
+                        }
+                    },
+                    {
+                        label: "取消",
+                        action: function (dialogItself) {
+                            dialogItself.close();
+                        }
+                    }
+                ]
+            });
+        },
+        refreshTraceLog: function () {
+            this.$refs.traceLog.getTraceLog();
+        },
+        /**
+         * 根据追踪单Id查询追踪单的数据
+         */
+        judgePermission: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceResource/judgePermission'),
+                type: 'get',
+                dataType: 'json',
+                data: {
+                    traceId: _self.traceId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.isflag = data;
+                    if(_self.isflag){
+			    		_self.initData();
+			    	}else{
+			    		Notify.error("提示", "你没有访问权限!");
+			    	}
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        }
+    },
+
+    mounted: function () {
+    	this.traceId = this.$route.params.traceId;
+    	this.judgePermission();
+    },
+
+    computed: {
+        "finishedImageName": function () {
+            if (this.finished != true) {
+                return '../../static/image/finished.png';
+            } else {
+                return '../../static/image/finished2.png';
+            }
+        }
+    }
+}
+</script>
+<style scoped>
+.mui-img {
+    width: 80px;
+}
+
+.mui-color {
+    background-color: #007aff;
+    color: white;
+}
+</style>

+ 269 - 0
src/trace/TraceAttachment.vue

@@ -0,0 +1,269 @@
+<template>
+    <div>
+        <div>
+            <input ref="fileInput"
+                   type="file"
+                   class="form-control file-input"
+                   @change="onFileChanges" />
+            <label for="attachment">
+                <a role="button"
+                   class="btn btn-primary btn-sm"
+                   @click="clickButton">
+                    <i class="fa fa-upload" />
+                    &nbsp;
+                    上传附件
+                </a>
+            </label>
+        </div>
+
+        <div v-if="attachmentList != undefined && attachmentList.length > 0">
+            <ul class="list-group">
+                <template v-for="attachment in attachmentList">
+                    <li class="list-group-item"
+                        :key="attachment.id">
+                        <span class="badge"
+                              @click="deleteByTraceAttachmentId(attachment.id)">删除</span>
+                        <span @click="download(attachment)"
+                              class="badge">下载</span>
+                        {{attachment.attachment}}
+                    </li>
+                </template>
+            </ul>
+        </div>
+        <Loading ref="loading"></Loading>
+    </div>
+</template>
+
+<script>
+var Common = require("../common/Common.js");
+var Notify = require("pc-client-component").Notify;
+var DownloadService =require("pc-client-component").DownloadService;
+var Loading = require("pc-client-component").Loading;
+
+export default {
+    props: ["traceId"],
+    data: function () {
+        return {
+            className: "com.leanwo.prodog.model.trace.TraceAttachment",
+            attachmentList: [],
+            traceConfigDto:{}
+        }
+    },
+    components: {
+        Loading,DownloadService
+    },
+    methods: {
+
+        /**
+         * 下载文件
+         * @param  {String} fileName 文件名
+         * @return {void}          
+         */
+        download: function (item) {
+             var _self = this;
+			DownloadService.fileDownload(_self.className, item.attachment);
+        },
+
+        /**
+         * 删除任务附件
+         */
+        deleteByTraceAttachmentId: function (traceAttachementId) {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceAttachmentResource/deleteByTraceAttachmentId'),
+                type: 'get',
+                dataType: 'json',
+                data: {
+                    'id': traceAttachementId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data == false) {
+                        Notify.error("提示", "您没有权限删除附件,只有任务发起人和附件上传人有权限删除附件!");
+                        _self.listTraceAttachmentByTraceId();
+                    } else {
+                        _self.listTraceAttachmentByTraceId();
+                    }
+                },
+            });
+        },
+
+        /* 选择文件发生改变
+         * @param  {[type]} e [description]
+         * @return {[type]}   [description]
+         */
+        onFileChanges: function (e) {
+            var files = e.target.files || e.dataTransfer.files;
+            if (!files.length)
+                return;
+            this.uploadFile(files[0]);
+        },
+
+        /**
+         * 上传文件
+         * @param  {File} selectedFile 选择的文件
+         */
+        uploadFile: function (selectedFile) {
+            var _self = this;
+            if (selectedFile == undefined) {
+                return;
+            }
+            var size = undefined;
+            if(_self.traceConfigDto.traceAttachmentSize != undefined && _self.traceConfigDto.traceAttachmentSize != null){
+            	size = _self.traceConfigDto.traceAttachmentSize;
+            }
+            //当系统没有配置附件大小时默认8m
+            if(size == undefined){
+            	size = 8;
+            }
+            if ((selectedFile.size / 1024) <= (1024 * size)) {
+	            var formData = new FormData();
+	            formData.append("上传文件", selectedFile);
+	            formData.append("className", _self.className);
+	            _self.$refs.loading.show();
+	            $.ajax({
+	                url: Common.getApiURL("file/classFileUpload"),
+	                type: "post",
+	                beforeSend: function (request) {
+	                    Common.addTokenToRequest(request);
+	                },
+	                data: formData,
+	                contentType: false,
+	                processData: false,
+	                success: function (data) {
+	                    _self.$refs.loading.hide();
+	                    if (data != "error") {
+	                        var fileName = data.substring(data.indexOf(":") + 1);
+	                        _self.uploadTraceAttachment(fileName);
+	                    }
+	                },
+	                error: function (XMLHttpRequest, textStatus, errorThrown) {
+	                    _self.$refs.loading.hide();
+	                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+	                }
+	            });
+	        } else {
+                Notify.error("提示", "文件大小不能超过"+size+"M,可在任务配置功能中配置!");
+            }
+        },
+
+		/**
+		 * 获取任务管理配置
+		 */
+		getTraceConfig: function() {
+			var _self = this;
+			$.ajax({
+				url: Common.getApiURL('TraceConfigResource/queryTraceConfigDto'),
+				type: "get",
+				dataType: "json",
+				beforeSend: function(request) {
+					Common.addTokenToRequest(request);
+				},
+
+				success: function(data) {
+					_self.traceConfigDto = data;
+				},
+				error: function(XMLHttpRequest, textStatus, errorThrown) {
+					Common.processException(XMLHttpRequest, textStatus, errorThrown);
+				}
+			});
+		},
+
+        /**
+         * 查询当前追踪单下所有的附件
+         */
+        listTraceAttachmentByTraceId: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL("TraceAttachmentResource/listByTraceId"),
+                type: "get",
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                data: {
+                    'traceId': _self.traceId,
+                },
+                success: function (data) {
+                    console.log(data);
+                    _self.attachmentList = data;
+                },
+            });
+        },
+
+
+
+        /**
+         * 上传附件
+         */
+        uploadTraceAttachment: function (fileName) {
+            var _self = this;
+            var traceAttachement = {
+                traceId: _self.traceId,
+                attachment: fileName,
+            };
+            $.ajax({
+                url: Common.getApiURL("TraceAttachmentResource/save"),
+                type: "post",
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                data: JSON.stringify(traceAttachement),
+                success: function (data) {
+                    _self.listTraceAttachmentByTraceId();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    Notify.error("提示", "上传失败!");
+                }
+            });
+        },
+
+        
+        /**
+         * 点击上传按钮事件
+         * @return {[type]} [description]
+         */
+        clickButton: function () {
+            $(this.$refs.fileInput).click();
+        },
+    },
+
+    mounted: function () {
+        if (this.traceId != undefined) {
+            this.listTraceAttachmentByTraceId();
+        }
+        this.getTraceConfig();
+    },
+
+    watch: {
+        "traceId": function (curVal, oldVal) {
+            if (curVal != undefined) {
+                this.listTraceAttachmentByTraceId();
+            }
+        }
+    }
+}
+</script>
+
+<style scoped>
+.badge {
+    cursor: pointer;
+    background-color: #428bca;
+    color: white;
+}
+</style>
+
+<style scoped>
+.file-input {
+    width: 0.1px;
+    height: 0.1px;
+    opacity: 0;
+    overflow: hidden;
+    position: absolute;
+    z-index: -1;
+}
+</style>

+ 137 - 0
src/trace/TraceComment.vue

@@ -0,0 +1,137 @@
+<!--
+	作者:yangzhijie1488@163.com
+	时间:2017-12-12
+	描述:追踪日志
+-->
+
+<template>
+	<div >
+		<div v-for="items in traceComments" class="media">
+			<h4 class="media-heading">{{items.createdName}}<a class="fa-pull-right" @click="edit(items)">编辑</a></h4>
+			<div class="media-body">    
+			    <div>
+					<div v-html="items.content"></div><br/>
+					{{items.created}}
+			    </div>
+			    <div v-if="items.attachments != '' && items.attachments != undefined" v-for="(item,index) in split(items.attachments)" @click="download(item)">
+					<a>{{item}}</a>
+			    </div>
+		    </div>
+		</div>
+	</div>
+</template>
+
+<script>
+	var Common = require("../common/Common.js");
+	var DownloadService = require("pc-client-component").DownloadService;
+	export default {
+		props: ["traceId","trace"],
+
+		data: function() {
+			return {
+            	traceComments: [],
+            	className: "com.leanwo.prodog.trace.model.TraceComment",
+            	uuid:""
+			}
+		},
+        components: {
+			Common,DownloadService
+		},
+		methods: {
+		
+			/**
+			 * 根据追踪表Id获取评论
+			 * @author GuoZhiBo 20171201
+			 */
+			getTraceComment: function() {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceCommentResource/queryTraceComment'),
+					type: 'get',
+					dataType: 'json',
+					async: false,
+					data: {
+						traceId: _self.traceId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						_self.traceComments = data;
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+			
+	        /**
+	         * 将字符串以”,“进行分割成字符串
+	         * @author GuoZhiBo 20171201
+	         */
+	        split: function(items) {
+	            if (items != null && items != "") {
+	                return items.split(",");
+	            } else {
+	                return null;
+	            }
+	
+	        },
+	        /**
+			 * 下载文件
+			 * @param  {String} fileName 文件名
+			 * @return {void}          
+			 */
+			download: function(fileName) {
+				var _self = this;
+				DownloadService.fileDownload(_self.className, fileName);
+			},
+			/**
+			 * 获取图片地址
+			 * @param  {String} item 图片名称
+			 * @return {String}      图片URL地址
+			 */
+			getImageSrc: function(item) {
+				var _self = this;
+				if(item != undefined && item != null) {
+					return Common.getImageSrc("com.leanwo.prodog.trace.model.TraceComment", item);
+				} else {
+					return "";
+				}
+			},
+			/**
+			 * 获取图片地址
+			 */
+			getImageSrcName: function(imageName){
+				return Common.getImageSrc("com.leanwo.prodog.model.base.User",imageName);
+			},
+			
+			/**
+			 * 打开评论界面进行评论
+			 * @author GuoZhiBo 20171201
+			 */
+			edit: function(item) {
+				this.$router.push("/desktop/traceCommentEdit/" + item.id);
+			}
+		},
+		
+		mounted: function(){
+			this.uuid = this.$route.params.uuid;
+			if(this.traceId != undefined){
+				this.getTraceComment();
+			}
+		},
+		
+		watch: {
+			"traceId": function(curVal,oldVal){
+				if(curVal != undefined){
+					this.getTraceComment();
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 441 - 0
src/trace/TraceCommentCreate.vue

@@ -0,0 +1,441 @@
+<template>
+    <div>
+        <div>
+            <Navbar title="创建评论"
+                    :isGoBack="true"></Navbar>
+            <div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">评论内容</label>
+					<div id="summernote"></div>
+                    <!-- <textarea style="width: 100%;"
+                              id="hcqk"
+                              class="form-control"
+                              rows="5"
+                              v-model="content"></textarea> -->
+                </div>
+                <div>
+                    <input ref="fileInput"
+                           type="file"
+                           class="form-control file-input"
+                           @change="onFileChanges" />
+                    <label for="attachment">
+                        <a role="button"
+                           class="btn btn-primary btn-sm"
+                           @click="clickButton">
+                            <i class="fa fa-upload" />
+                            &nbsp;
+                            上传附件
+                        </a>
+                    </label>
+                </div>
+
+                <div style="margin-top: 10px;">
+                    <ul class="list-group">
+                        <li v-for="item,index in files"
+                            class="list-group-item">
+                            <span @click="download(item)"
+                                  class="badge">下载</span>
+                            <span @click="removeFile(item)"
+                                  class="badge">删除</span>
+                            {{item}}
+                        </li>
+                    </ul>
+                </div>
+                <div>
+                    <div style="margin-top: 10px;">
+                        <button @click="photograph"
+                                type="text"
+                                class="btn btn-default">确认</button>
+                        <button @click="back"
+                                type="text"
+                                class="btn btn-default">取消</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <Loading ref="loading"></Loading>
+    </div>
+</template>
+<script>
+var Uuid = require("pc-client-component").Uuid;
+var Loading = require("pc-client-component").Loading;
+var DynamicJsLoader = require("../common/DynamicJsLoader.js");
+var UpladFile = require("../widget/UpladFile.js");
+var Notify = require("pc-client-component").Notify;
+var Common = require("../common/Common.js");
+var Navbar = require("pc-client-component").Navbar;
+var DownloadService = require("pc-client-component").DownloadService;
+
+export default {
+    data: function () {
+        return {
+            traceId: null,
+            content: '',
+            images: [],
+            className: "com.leanwo.prodog.trace.model.TraceComment",
+            files: [],
+            traceConfigDto:{},
+			summernoteInitSuccess: false, // summernote初始化成功
+        }
+    },
+    components: {
+        Uuid, Loading, UpladFile, Notify, Common, Navbar,DynamicJsLoader,DownloadService
+    },
+    methods: {
+    	
+        /**
+         * 点击上传按钮事件
+         * @return {[type]} [description]
+         */
+        clickButton: function () {
+            $(this.$refs.fileInput).click();
+        },
+        /**
+         * 初始化数据
+         * @author GuoZhiBo 20171201
+         */
+        initData: function () {
+            var _self = this;
+            this.traceId = Number(this.$route.params.traceId);
+        },
+        /**
+         * 监听文件改变事件
+         * @author GuoZhiBo 20171201
+         */
+        onFileChange(e) {
+            var _self = this;
+            var files = e.target.files || e.dataTransfer.files;
+            if (!files.length)
+                return;
+            _self.uploadImage(files[0]);
+        },
+        /**
+         * 上传图片
+         * @author GuoZhiBo 20171201
+         */
+        uploadImage: function (selectedFile) {
+            var _self = this;
+            if (selectedFile == undefined) {
+                return;
+            }
+            if (!/image\/\w+/.test(selectedFile.type)) {
+                Notify.error("提示", "请确保文件为图像类型!");
+                return;
+            }
+            UpladFile.photoCompress(selectedFile, {
+                quality: 0.2
+            }, function (base64Codes) {
+                var bl = UpladFile.convertBase64UrlToBlob(base64Codes);
+                var rst = new FormData();
+                rst.append("上传图片", bl, "file_" + Date.parse(new Date()) + ".jpg");
+                rst.append("className", "com.leanwo.prodog.trace.model.TraceComment");
+                _self.$refs.loading.show();
+                $.ajax({
+                    url: Common.getApiURL("file/imageUpload"),
+                    type: "post",
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    data: rst,
+                    contentType: false,
+                    processData: false,
+                    success: function (data) {
+                        _self.$refs.loading.hide();
+                        if (data != "error") {
+                            var imageName = data.substring(data.indexOf(":") + 1);
+                            _self.addImg(imageName);
+                        }
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        _self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+                return rst.file;
+            });
+        },
+        /**
+         * 添加图片
+         * @author GuoZhiBo 20171201
+         */
+        addImg: function (imageName) {
+            var _self = this;
+            _self.images.push(imageName);
+        },
+        /**
+         * 获取图片地址
+         * @param  {String} item 图片名称
+         * @return {String}      图片URL地址
+         */
+        getImageSrc: function (item) {
+            var _self = this;
+            if (item != undefined && item != null) {
+                return Common.getResourceUrl('image', "com.leanwo.prodog.trace.model.TraceComment", item);
+            } else {
+                return "";
+            }
+        },
+        /**
+         * 获取图片路径供外部调用
+         * @return {Array} 图片地址
+         */
+        getImages: function () {
+            return this.images;
+        },
+        /**
+         * 删除图片
+         *@author GuoZhiBo 20171201
+         */
+        deleteImg(index) {
+            var _self = this;
+            _self.images.splice(index, 1);
+        },
+        //----------文件------------
+        /**
+         * 删除文件
+         * @param  {String} item 文件Name
+         */
+        removeFile: function (index) {
+            var _self = this;
+            _self.files.splice(index, 1);
+        },
+        /**
+         * 选择文件发生改变
+         * @param  {[type]} e [description]
+         * @return {[type]}   [description]
+         */
+        onFileChanges: function (e) {
+            var files = e.target.files || e.dataTransfer.files;
+            if (!files.length)
+                return;
+            this.uploadFile(files[0]);
+        },
+
+        /**
+         * 上传文件
+         * @param  {File} selectedFile 选择的文件
+         */
+        uploadFile: function (selectedFile) {
+            var _self = this;
+            if (selectedFile == undefined) {
+                return;
+            }
+            var size = undefined;
+            if(_self.traceConfigDto.commentAttachmentSize != undefined && _self.traceConfigDto.commentAttachmentSize != null){
+            	size = _self.traceConfigDto.commentAttachmentSize;
+            }
+            //当系统没有配置附件大小时默认8m
+            if(size == undefined){
+            	size = 8;
+            }
+            if (selectedFile.size != undefined && selectedFile.size != null) {
+                if ((selectedFile.size / 1024) <= (1024 * size)) {
+                    var formData = new FormData();
+                    formData.append("上传文件", selectedFile);
+                    formData.append("className", _self.className);
+                    _self.$refs.loading.show();
+                    $.ajax({
+                        url: Common.getApiURL("file/classFileUpload"),
+                        type: "post",
+                        beforeSend: function (request) {
+                            Common.addTokenToRequest(request);
+                        },
+                        data: formData,
+                        contentType: false,
+                        processData: false,
+                        success: function (data) {
+                            _self.$refs.loading.hide();
+                            if (data != "error") {
+                                var fileName = data.substring(data.indexOf(":") + 1);
+                                _self.files.push(fileName);
+                            }
+                        },
+                        error: function (XMLHttpRequest, textStatus, errorThrown) {
+                            _self.$refs.loading.hide();
+                            Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                        }
+                    });
+                } else {
+                    Notify.error("提示", "文件大小不能超过"+size+"M,可在任务配置功能中配置!");
+                }
+            } else {
+                Notify.error("提示", "上传的文件为空!");
+            }
+        },
+		/**
+		 * 获取任务管理配置
+		 */
+		getTraceConfig: function() {
+			var _self = this;
+			$.ajax({
+				url: Common.getApiURL('TraceConfigResource/queryTraceConfigDto'),
+				type: "get",
+				dataType: "json",
+				beforeSend: function(request) {
+					Common.addTokenToRequest(request);
+				},
+
+				success: function(data) {
+					_self.traceConfigDto = data;
+				},
+				error: function(XMLHttpRequest, textStatus, errorThrown) {
+					Common.processException(XMLHttpRequest, textStatus, errorThrown);
+				}
+			});
+		},
+        /**
+         * 获取文件地址
+         * @param  {String} item 图片名称
+         * @return {String}      图片URL地址
+         */
+        getFileSrc: function (item) {
+            var _self = this;
+            if (item != undefined && item != null) {
+                return Common.getResourceUrl('file', _self.className, item);
+            } else {
+                return "";
+            }
+        },
+
+        /**
+         * 下载文件
+         * @param  {String} fileName 文件名
+         * @return {void}          
+         */
+        download: function (fileName) {
+            var _self = this;
+			DownloadService.fileDownload(_self.className, fileName);
+        },
+
+
+		//内容图片上传
+		sendFile: function (file, editor, $editable) {
+		    var _self = this;
+		    UpladFile.photoCompress(file, {
+		        quality: 1
+		    }, function (base64Codes) {
+		        var bl = UpladFile.convertBase64UrlToBlob(base64Codes);
+		        var rst = new FormData();
+		        rst.append("上传图片", bl, "file_" + Date.parse(new Date()) + ".jpg");
+		        rst.append("className", _self.className);
+		        $.ajax({
+		            url: Common.getApiURL("file/imageUpload"),
+		            type: "post",
+		            data: rst,
+		            contentType: false,
+		            processData: false,
+		            beforeSend : function(request){
+						Common.addTokenToRequest(request);
+					},
+		            success: function (data) {
+		                if (data != "error") {
+		                	var imageName = data.substring(data.indexOf(":") + 1);
+							$("#summernote").summernote('insertImage', Common.getResourceUrl("image", _self.className, imageName), 'image name');
+		                }
+		            },
+		            error: function () {
+		                Notify.error("提示", "上传失败!");
+		                return;
+		            }
+		        });
+		        return rst.file;
+		    });
+		},
+		
+        /**
+         * 保存评论表数据
+         * @author GuoZhiBo 20171201
+         */
+        photograph: function () {
+            var _self = this;
+			_self.content=$('#summernote').summernote('code');
+            if (_self.content != "" && _self.content != null) {
+                var traceComment = {
+                    traceId: _self.traceId,
+                    content: _self.content,
+                    attachments: _self.files.join(","),
+                }
+                _self.$refs.loading.show();
+                $.ajax({
+                    url: Common.getApiURL("TraceCommentResource/saveTraceComment"),
+                    type: "post",
+                    contentType: "application/json",
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    data: JSON.stringify(traceComment),
+                    success: function (data) {
+                        _self.$refs.loading.hide();
+                        _self.back();
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        _self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+                Notify.error("提示", "请填写评论内容!");
+            }
+        },
+
+        back: function () {
+            history.back();
+        }
+    },
+
+    mounted: function () {
+		 var _self = this;
+        $(".input-tan").focus(function () {
+            document.activeElement.blur();
+        });
+        this.initData();
+        this.getTraceConfig();
+		DynamicJsLoader.loadSummernote(function () {
+		    $('#summernote').summernote({
+		    	toolbar:[
+			        ['insert', ['link', 'picture', 'video', 'audio', 'hr', 'table', //插件
+			        'fontname', 'fontsize', 'color', 'bold', 'italic', 'underline', 'strikethrough', 'clear',//字体样式
+			        'style', 'ol', 'ul', 'paragraph', 'height',//段落样式
+			        'fullscreen', 'codeview', 'undo', 'redo', 'help'//Misc
+			        ]],
+			    ],
+		        height: 400,
+		        minHeight: 300,
+		        maxHeight: 500,
+		        focus: true,
+		        lang: 'zh-CN',
+		        callbacks: {
+		            onImageUpload: function (files, editor, $editable) {
+		                _self.sendFile(files[0], editor, $editable);
+		            }
+		        }
+		    });
+		    _self.summernoteInitSuccess = true;
+		})
+		
+    },
+	destroyed: function(){
+	    if(this.summernoteInitSuccess == true){
+	        $('#summernote').summernote('destroy');
+	    }
+	}
+}
+</script>
+<style scoped>
+.mui-divs3 {
+    margin-top: 10px;
+}
+.file-input {
+    width: 0.1px;
+    height: 0.1px;
+    opacity: 0;
+    overflow: hidden;
+    position: absolute;
+    z-index: -1;
+}
+.badge {
+    cursor: pointer;
+    background-color: #428bca;
+    color: white;
+}
+</style>

+ 344 - 0
src/trace/TraceCommentEdit.vue

@@ -0,0 +1,344 @@
+<template>
+	<div>
+		<div>
+			<Navbar title="修改评论" :isGoBack="true"></Navbar>
+			<div>
+				<div class="form-group">
+					<label for="exampleInputEmail2">评论内容</label>
+					<div id="summernote"></div>
+					<!-- <textarea style="width: 100%;"
+                              id="hcqk"
+                              class="form-control"
+                              rows="5"
+                              v-model="traceComment.content"></textarea> -->
+				</div>
+				<div>
+					<input ref="fileInput" type="file" class="form-control file-input" @change="onFileChanges" />
+					<label for="attachment">
+						<a role="button" class="btn btn-primary btn-sm" @click="clickButton">
+							<i class="fa fa-upload" />
+							&nbsp;
+							上传附件
+						</a>
+					</label>
+				</div>
+
+				<div style="margin-top: 10px;">
+					<ul class="list-group">
+						<li v-for="item,index in files" class="list-group-item">
+							<span @click="download(item)" class="badge">下载</span>
+							<span @click="removeFile(item)" class="badge">删除</span>
+							{{item}}
+						</li>
+					</ul>
+				</div>
+
+				<div>
+					<div style="margin-top: 10px;">
+						<button @click="photograph" type="text" class="btn btn-default">确认</button>
+						<button @click="back" type="text" class="btn btn-default">取消</button>
+					</div>
+				</div>
+			</div>
+		</div>
+		<Loading ref="loading"></Loading>
+	</div>
+</template>
+<script>
+	var Uuid = require("pc-client-component").Uuid;
+	var Loading = require("pc-client-component").Loading;
+	var UpladFile = require("../widget/UpladFile.js");
+	var Notify = require("pc-client-component").Notify;
+	var Common = require("../common/Common.js");
+	var Navbar = require("pc-client-component").Navbar;
+	var DynamicJsLoader = require("../common/DynamicJsLoader.js");
+	var DownloadService = require("pc-client-component").DownloadService;
+
+	export default {
+		data: function() {
+			return {
+				traceCommentId: "",
+				traceComment: {},
+				className: "com.leanwo.prodog.trace.model.TraceComment",
+				summernoteInitSuccess: false, // summernote初始化成功
+				files: [],
+			}
+		},
+		components: {
+			Uuid,
+			Loading,
+			UpladFile,
+			Notify,
+			Common,
+			Navbar
+		},
+		methods: {
+			
+			/**
+			 * 点击上传按钮事件
+			 * @return {[type]} [description]
+			 */
+			clickButton: function() {
+				$(this.$refs.fileInput).click();
+			},
+
+			/**
+			 * 初始化数据
+			 * @author GuoZhiBo 20171201
+			 */
+			initData: function() {
+				var _self = this;
+				_self.traceCommentId = Number(this.$route.params.traceCommentId);
+				if (_self.traceCommentId != null && _self.traceCommentId != '') {
+					_self.uniqueById(_self.traceCommentId).then(successData => {
+						_self.traceComment = successData;
+						if(successData.attachments != null && successData.attachments != ''){
+							_self.files=(successData.attachments.split(','));
+						}
+						DynamicJsLoader.loadSummernote(function() {
+							$('#summernote').summernote({
+								toolbar:[
+							        ['insert', ['link', 'picture', 'video', 'audio', 'hr', 'table', //插件
+							        'fontname', 'fontsize', 'color', 'bold', 'italic', 'underline', 'strikethrough', 'clear',//字体样式
+							        'style', 'ol', 'ul', 'paragraph', 'height',//段落样式
+							        'fullscreen', 'codeview', 'undo', 'redo', 'help'//Misc
+							        ]],
+							    ],
+								height: 400,
+								minHeight: 300,
+								maxHeight: 500,
+								focus: true,
+								lang: 'zh-CN',
+								callbacks: {
+									onImageUpload: function(files, editor, $editable) {
+										_self.sendFile(files[0], editor, $editable);
+									}
+								}
+							});
+							if (_self.traceComment != null) {
+								$('#summernote').summernote('code', _self.traceComment.content);
+							}
+							_self.summernoteInitSuccess = true;
+						})
+					}, errorData => {
+						Common.processException(errorData);
+					});
+				}
+
+			},
+
+			/**
+			 * 查询Trace
+			 */
+			uniqueById: function(traceCommentId) {
+				return new Promise((resolve, reject) => {
+					$.ajax({
+						url: Common.getApiURL('TraceCommentResource/uniqueById'),
+						type: 'get',
+						dataType: 'json',
+						contentType: "application/json",
+						data: {
+							"traceCommentId": traceCommentId
+						},
+						beforeSend: function(request) {
+							Common.addTokenToRequest(request);
+						},
+						success: function(data) {
+							resolve(data);
+						},
+						error: function(XMLHttpRequest, textStatus, errorThrown) {
+							reject(XMLHttpRequest);
+						}
+					});
+				});
+			},
+
+			//----------文件------------
+			/**
+			 * 删除文件
+			 * @param  {String} item 文件Name
+			 */
+			removeFile: function(index) {
+				var _self = this;
+				_self.files.splice(index, 1);
+			},
+			/**
+			 * 选择文件发生改变
+			 * @param  {[type]} e [description]
+			 * @return {[type]}   [description]
+			 */
+			onFileChanges: function(e) {
+				var files = e.target.files || e.dataTransfer.files;
+				if (!files.length)
+					return;
+				this.uploadFile(files[0]);
+			},
+
+			/**
+			 * 上传文件
+			 * @param  {File} selectedFile 选择的文件
+			 */
+			uploadFile: function(selectedFile) {
+				var _self = this;
+				_self.showUploadDiv = false;
+				if (selectedFile == undefined) {
+					return;
+				}
+				if (selectedFile.size != undefined && selectedFile.size != null) {
+					if ((selectedFile.size / 1024) <= (1024 * 4)) {
+						var formData = new FormData();
+						formData.append("上传文件", selectedFile);
+						formData.append("className", _self.className);
+						_self.$refs.loading.show();
+						$.ajax({
+							url: Common.getApiURL("file/classFileUpload"),
+							type: "post",
+							beforeSend: function(request) {
+								Common.addTokenToRequest(request);
+							},
+							data: formData,
+							contentType: false,
+							processData: false,
+							success: function(data) {
+								_self.$refs.loading.hide();
+								if (data != "error") {
+									var fileName = data.substring(data.indexOf(":") + 1);
+									_self.files.push(fileName);
+								}
+							},
+							error: function(XMLHttpRequest, textStatus, errorThrown) {
+								_self.$refs.loading.hide();
+								Common.processException(XMLHttpRequest, textStatus, errorThrown);
+							}
+						});
+					} else {
+						Notify.error("提示", "文件大小不能超过4M!");
+					}
+				} else {
+					Notify.error("提示", "上传的文件为空!");
+				}
+			},
+
+			/**
+			 * 获取文件地址
+			 * @param  {String} item 图片名称
+			 * @return {String}      图片URL地址
+			 */
+			getFileSrc: function(item) {
+				var _self = this;
+				if (item != undefined && item != null) {
+					return Common.getResourceUrl('file', _self.className, item);
+				} else {
+					return "";
+				}
+			},
+
+			/**
+			 * 下载文件
+			 * @param  {String} fileName 文件名
+			 * @return {void}          
+			 */
+			download: function(fileName) {
+				var _self = this;
+				DownloadService.fileDownload(_self.className, fileName);
+			},
+			//图片上传
+			sendFile: function(file, editor, $editable) {
+				var _self = this;
+				UpladFile.photoCompress(file, {
+					quality: 0.2
+				}, function(base64Codes) {
+					var bl = UpladFile.convertBase64UrlToBlob(base64Codes);
+					var rst = new FormData();
+					rst.append("上传图片", bl, "file_" + Date.parse(new Date()) + ".jpg");
+					rst.append("className", _self.className);
+					$.ajax({
+						url: Common.getApiURL("file/imageUpload"),
+						type: "post",
+						data: rst,
+						contentType: false,
+						processData: false,
+						beforeSend : function(request){
+							Common.addTokenToRequest(request);
+						},
+						success: function(data) {
+							if (data != "error") {
+								var imageName = data.substring(data.indexOf(":") + 1);
+								$("#summernote").summernote('insertImage', Common.getResourceUrl("image", _self.className, imageName), 'image name');
+							}
+						},
+						error: function() {
+							Notify.error("提示", "上传失败!");
+							return;
+						}
+					});
+					return rst.file;
+				});
+			},
+
+			/**
+			 * 修改并保存评论表数据
+			 * @author GuoZhiBo 20180302
+			 */
+			photograph: function() {
+				var _self = this;
+				_self.traceComment.content = $('#summernote').summernote('code');
+				if (_self.traceComment.content != "" && _self.traceComment.content != null) {
+					_self.traceComment.attachments = _self.files.join(",");
+					_self.$refs.loading.show();
+					$.ajax({
+						url: Common.getApiURL("TraceCommentResource/updateTraceComment"),
+						type: "post",
+						contentType: "application/json",
+						beforeSend: function(request) {
+							Common.addTokenToRequest(request);
+						},
+						data: JSON.stringify(_self.traceComment),
+						success: function(data) {
+							_self.$refs.loading.hide();
+							_self.back();
+						},
+						error: function(XMLHttpRequest, textStatus, errorThrown) {
+							_self.$refs.loading.hide();
+							Common.processException(XMLHttpRequest, textStatus, errorThrown);
+						}
+					});
+				} else {
+					Notify.error("提示", "请填写评论内容!");
+				}
+			},
+
+			back: function() {
+				var _self = this;
+				history.back();
+			}
+
+		},
+		mounted: function() {
+			$(".input-tan").focus(function() {
+				document.activeElement.blur();
+			});
+			this.initData();
+		}
+	}
+</script>
+<style scoped>
+	.mui-divs3 {
+		margin-top: 10px;
+	}
+
+	.file-input {
+		width: 0.1px;
+		height: 0.1px;
+		opacity: 0;
+		overflow: hidden;
+		position: absolute;
+		z-index: -1;
+	}
+
+	.badge {
+		cursor: pointer;
+		background-color: #428bca;
+		color: white;
+	}
+</style>

+ 11 - 0
src/trace/TraceCommon.js

@@ -0,0 +1,11 @@
+export default {
+    //把时间格转换为y-m-d
+    formatDate: function (date) {
+        if (date != "" && date != null) {
+            var dateStr = date + "";
+            return dateStr.substr(0, 10);
+        } else {
+            return "";
+        }
+    },
+}

+ 97 - 0
src/trace/TraceConfig.vue

@@ -0,0 +1,97 @@
+<template>
+	<div class="container-fluid">
+		<Navbar title="任务配置" :isGoBack="true"></Navbar>
+		<div class="col-xs-12 form-horizontal">
+			<div class="form-group">
+			    <label for="exampleInputEmail1">任务附件大小(M)</label>
+			    <input class="form-control"  type="number" placeholder="任务附件大小" v-model="traceConfigDto.traceAttachmentSize">
+			</div>
+			<div class="form-group">
+			    <label for="exampleInputPassword1">评论附件大小(M)</label>
+			    <input class="form-control"  placeholder="评论附件大小" v-model="traceConfigDto.commentAttachmentSize">
+			</div>  
+		    <div class="row">
+				<div class="col-md-12 button-right">
+					<button class="btn btn-primary" @click="saveTraceConfig">确定</button>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	var Common = require("../common/Common.js");
+	var Notify = require("pc-client-component").Notify;
+	var Navbar = require("pc-client-component").Navbar;
+	module.exports = {
+		data: function() {
+			return {
+				traceConfigDto: {
+					traceAttachmentSize:0,
+					commentAttachmentSize:0
+				},
+			}
+		},
+
+		components: {
+			Common,
+			Navbar,
+		},
+
+		methods: {
+
+			/**
+			 * 获取任务管理配置
+			 */
+			getTraceConfig: function() {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceConfigResource/queryTraceConfigDto'),
+					type: "get",
+					dataType: "json",
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+
+					success: function(data) {
+						_self.traceConfigDto = data;
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+			/**
+			 * 保存任务管理配置
+			 */
+			saveTraceConfig:function(){
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceConfigResource/saveOrUpdate'),
+					type: "post",
+					contentType: "application/json",
+					data: JSON.stringify(_self.traceConfigDto),
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						Notify.success("提示", "已保存!",1500);
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+
+		},
+		mounted: function() {
+			this.getTraceConfig();
+		}
+	}
+</script>
+
+<style scoped>
+	.button-right{
+		text-align: right;
+	}
+</style>

+ 341 - 0
src/trace/TraceCreate.vue

@@ -0,0 +1,341 @@
+<template>
+    <div>
+        <div>
+            <Navbar title='新建任务'
+                    :isGoBack="true"></Navbar>
+
+            <div>
+                <div class="form-group">
+                    <label for="exampleInputEmail1">标题</label>
+                    <input type="text"
+                           class="form-control"
+                           id="exampleInputEmail1"
+                           placeholder="标题"
+                           v-model="traceDto.summary">
+                </div>
+                <div class="form-group">
+                    <label for="exampleInputPassword1">内容</label>
+                    <div id="summernote"></div>
+                </div>
+                <AudioField @handleStop="handelEndRecord" :className = "className" :srcArray = "srcArray" @deleteAudioSrc = "deleteAudioSrc" :isReadonly = "'false'"/>
+                <div class="form-group">
+                    <label for="trackUserId">责任人</label>
+                    <select id="trackUserId"
+                            class="form-control"
+                            v-model="userSelected">
+                        <option value="" ></option>
+                        <option :value="user.userId"
+                                v-for="user in userList">{{user.userName}}</option>
+                    </select>
+                </div>
+                <div class="form-group">
+                    <label for="projectName">项目</label>
+                    <select id="projectName"
+                            class="form-control"
+                            v-model="projectId"
+                            :disabled="'projectId != null'?'true':'false'">
+                        <option :value="project.projectItemId"
+                                v-for="project in projectUserList">{{project.projectItemName}}</option>
+                    </select>
+                </div>
+                <div class="form-group">
+                    <label for="projectName">时间节点</label>
+                    <Treeselect ref = "treeSelect" :multiple="false" :options="projectTaskDtos" />
+                </div>
+                <div class="form-group">
+                    <label>完成时间</label>
+                    <DateWidget @on-value-change="depreciationStartDateChanged" 
+                                :dateValue="planFinishedDate"
+                                class="form-control"
+                                :readonly="isdisable"></DateWidget>
+                              
+                 </div><!--style="float:right;position:relative;top: -46px; right: 5px -->
+                   <span class="glyphicon glyphicon-remove-circle"  style="float:right;top:-45px;left:-50px;position:relative;" @click="removeTime" ></span>  
+                <button style="width:100%;background-color: #007aff;color: white"
+                        @click="saveTrace()"
+                        type="button"
+                        class="btn btn-blue search-button">
+                    保存
+                </button>
+
+            </div>
+        </div>
+        <Loading ref="loading"></Loading>
+    </div>
+</template>
+<script type="text/javascript">
+var Notify = require("pc-client-component").Notify;
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+var DynamicJsLoader = require("../common/DynamicJsLoader.js");
+var UpladFile = require("../widget/UpladFile.js");
+
+var Navbar = require("pc-client-component").Navbar;
+var DateWidget = require("pc-client-component").Date;
+var Loading = require("pc-client-component").Loading;
+import Treeselect from "../widget/TreeSelect.vue";
+var AudioField = require("../widget/AudioField.vue").default;
+export default {
+    data: function () {
+        return {
+            traceDto: {},
+            className: "com.leanwo.prodog.Trace",
+            userList: [],
+            userSelected: '',
+            projectUserList: [],
+            planFinishedDate: '',
+            isdisable: false, //日期框是否可编辑
+            projectId: '', //项目Id
+            summernoteInitSuccess: false, // summernote初始化成功
+            projectTaskDtos:[],//项目任务时间点
+            srcArray:[]
+        }
+    },
+    components: {
+        Navbar,
+        Loading,
+        Common,
+        Notify,
+        DynamicJsLoader,
+        UpladFile,
+        DateWidget,
+        Treeselect,
+        AudioField
+    },
+    methods: {
+    	/**
+    	 * 删除语音
+    	 * @param {Object} index
+    	 */
+    	deleteAudioSrc:function(index){
+			var _self = this;
+			_self.srcArray.splice(index,1);
+			_self.traceDto.audioSrcs = JSON.stringify(_self.srcArray);
+		},
+    	/**
+    	 * 录音停止之后
+    	 * @param {Object} param
+    	 */
+    	handelEndRecord: function(param) {
+    		var _self = this;
+		     if(param.fileName != undefined){
+		     	_self.srcArray.push(param.fileName);
+			    _self.traceDto.audioSrcs = JSON.stringify(_self.srcArray);
+		     }
+		},
+        back: function () {
+            history.back();
+        },
+        removeTime: function(){
+           this.planFinishedDate = ''
+        },
+        saveTrace: function () {
+            var _self = this;
+            _self.traceDto.receiveUserId = _self.userSelected;//接收人Id
+			_self.traceDto.projectTaskId = _self.$refs.treeSelect.getValue();
+            if (_self.planFinishedDate != undefined && _self.planFinishedDate != "") {
+                _self.traceDto.planFinishedDate = _self.planFinishedDate; //完成时间
+            }
+            _self.traceDto.projectId = _self.projectId;//项目Id
+
+            _self.traceDto.detail = $('#summernote').summernote('code');
+            if (_self.traceDto.summary != "" && _self.traceDto.summary != undefined) {
+                _self.$refs.loading.show();
+                $.ajax({
+                    url: Common.getApiURL('TraceResource/saveTrace'),
+                    type: 'post',
+                    contentType: "application/json",
+                    data: JSON.stringify(_self.traceDto),
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    success: function (data) {
+                        Notify.success("提示", "追踪成功!");
+                        _self.$refs.loading.hide();
+                        _self.back();
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        _self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+                Notify.error("提示", "标题不能为空!");
+            }
+        },
+
+        //图片上传  
+        sendFile: function (file, editor, $editable) {
+            var _self = this;
+            UpladFile.photoCompress(file, {
+                quality: 1
+            }, function (base64Codes) {
+                var bl = UpladFile.convertBase64UrlToBlob(base64Codes);
+                var rst = new FormData();
+                rst.append("上传图片", bl, "file_" + Date.parse(new Date()) + ".jpg");
+                rst.append("className", _self.className);
+                $.ajax({
+                    url: Common.getApiURL("file/imageUpload"),
+                    type: "post",
+                    data: rst,
+                    contentType: false,
+                    processData: false,
+                    beforeSend : function(request){
+						Common.addTokenToRequest(request);
+					},
+                    success: function (data) {
+                        if (data != "error") {
+                        	var imageName = data.substring(data.indexOf(":") + 1);
+							$("#summernote").summernote('insertImage', Common.getResourceUrl("image", _self.className, imageName), 'image name');
+                        }
+                    },
+                    error: function () {
+                        Notify.error("提示", "上传失败!");
+                        return;
+                    }
+                });
+                return rst.file;
+            });
+        },
+
+        /**
+         * 初始化人员
+         * @author ZhangTeng 20190213
+         */
+        initData: function () {
+            var _self = this;
+            _self.userList.splice(0, _self.userList.length);
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/listByProjectItemId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: { "projectItemId": _self.projectId },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data != null) {
+                        for (var index = 0; index < data.length; index++) {
+                            _self.$set(_self.userList, index, data[index]);
+                            // if (index == 0) {
+                            //     _self.userSelected = data[index].userId;
+                            // }
+                        }
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 初始化项目
+         * @author ZhangTeng 20190213
+         */
+        initProject: function () {
+            var _self = this;
+            _self.projectUserList.splice(0, _self.projectUserList.length);
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/listByUserId'), //查询所有的项目用户
+                type: 'get',
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data != null) {
+                        for (var index = 0; index < data.length; index++) {
+                            _self.$set(_self.projectUserList, index, data[index]);
+                        }
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            })
+        },
+
+
+        //日期选择
+        depreciationStartDateChanged: function (value) {
+            this.planFinishedDate = value
+        },
+        /**
+         * 根据项目id查询项目任务时间节点
+         * @author GuoZhiBo 20191010
+         */
+        queryProjectTaskDtoByProjectId: function(){
+			var _self = this;
+			$.ajax({
+				url: Common.getApiURL('ProjectTaskResource/queryProjectTaskDtoByProjectId'),
+				type: 'get',
+				dataType: 'json',
+				contentType: 'application/json',
+				data: {
+					projectId: _self.projectId
+				},
+				beforeSend: function(request) {
+					Common.addTokenToRequest(request);
+				},
+				success: function(data) {
+					_self.projectTaskDtos = data;
+				},
+				error: function(XMLHttpRequest, textStatus, errorThrown) {
+					Common.processException(XMLHttpRequest, textStatus, errorThrown);
+				}
+			});
+		},
+    },
+
+
+    mounted: function () {
+        var _self = this;
+        this.projectId = this.$route.params.projectId;
+        this.initProject();
+        this.initData();
+        DynamicJsLoader.loadSummernote(function () {
+            $('#summernote').summernote({
+            	toolbar:[
+			        ['insert', ['link', 'picture', 'video', 'audio', 'hr', 'table', //插件
+			        'fontname', 'fontsize', 'color', 'bold', 'italic', 'underline', 'strikethrough', 'clear',//字体样式
+			        'style', 'ol', 'ul', 'paragraph', 'height',//段落样式
+			        'fullscreen', 'codeview', 'undo', 'redo', 'help'//Misc
+			        ]],
+			    ],
+                height: 400,
+                minHeight: 300,
+                maxHeight: 500,
+                focus: true,
+                lang: 'zh-CN',
+                callbacks: {
+                    onImageUpload: function (files, editor, $editable) {
+                        _self.sendFile(files[0], editor, $editable);
+                    },
+                }
+            });
+            _self.summernoteInitSuccess = true;
+        })
+    },
+
+    destroyed: function(){
+        if(this.summernoteInitSuccess == true){
+            $('#summernote').summernote('destroy');
+        }
+        //界面销毁时,修改插入连接的display属性
+        $('.note-link-popover').removeAttr('style');
+    },
+	watch: {
+		"projectId" : function(curVal, oldVal){
+			var _self = this;
+			_self.$refs.treeSelect.setValue(undefined);
+			_self.queryProjectTaskDtoByProjectId();
+		}
+	}
+
+}
+</script>
+
+<style scoped>
+</style>

+ 422 - 0
src/trace/TraceDynamic.vue

@@ -0,0 +1,422 @@
+<template>
+    <div>
+        <TraceHeader :type="'traceDynamic'">
+
+        </TraceHeader>
+
+        <div class="form-inline">
+            <div class="form-group">
+                <label for="exampleInputName2">任务内容</label>
+                <input type="text"
+                       class="form-control"
+                       id="exampleInputName2"
+                       placeholder="任务名称"
+                       style="width: 200px"
+                       v-model="searchText"
+                       v-on:keyup.enter="query">
+            </div>
+            <div class="form-group">
+                <label for="exampleInputEmail2">项目</label>
+                <div class="input-group">
+                    <VueSimpleSuggest :list="projects"
+                                      :filter-by-query="true"
+                                      :styles="suggestStyles"
+                                      :minLength="-1"
+                                      display-attribute="name"
+                                      value-attribute="id"
+                                      @suggestion-click="selectProject"
+                                      v-model="selectedProjectName">
+                    </VueSimpleSuggest>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="exampleInputEmail2">用户</label>
+                <div class="input-group">
+                    <VueSimpleSuggest :list="users"
+                                      :filter-by-query="true"
+                                      :styles="suggestStyles"
+                                      :minLength="-1"
+                                      display-attribute="name"
+                                      value-attribute="id"
+                                      @suggestion-click="selectUser"
+                                      v-model="selectedUserName">
+                    </VueSimpleSuggest>
+                </div>
+            </div>
+            <button class="btn btn-default"
+                    @click="query">查询</button>
+        </div>
+
+        <div class="row">
+            <div class="col-sm-12">
+                <div v-for="(items,key) in traceLogMap"
+                     :key="key">
+
+                    <h4>{{getDateFormat(key)}}</h4>
+
+                    <hr />
+
+                    <ul class="list-group">
+                        <li class="list-group-item"
+                            v-for="item in items"
+                            :key="item.id"
+                            @click="openLineTrace(item.traceId)">
+                            <span class="badge">{{formatDate(item.dateTime)}}</span>
+                            <span style="color: #000000;">{{item.projectName}}&nbsp;</span>
+                            {{item.operatorUserName}}&nbsp;
+                            {{getContent(item.content)}} {{item.traceContent}}
+                        </li>
+                    </ul>
+                </div>
+            </div>
+            <div class="col-sm-12">
+                <Pagination :pagination="pagination"
+                            :callback="getDatas"
+                            ></Pagination>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+var Notify = require("pc-client-component").Notify;
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+import TraceCommon from "./TraceCommon.js";
+
+import TraceHeader from "./TraceHeader.vue";
+var Pagination = require("vue-bootstrap-pagination").default;
+var Navbar = require("pc-client-component").Navbar;
+var VueSimpleSuggest = require("vue-simple-suggest").default;
+
+export default {
+    data: function () {
+        return {
+            searchText: "",
+            selectedUserId: "",
+            selectedUserName: "",
+            selectedProjectId: "",
+            selectedProjectName: "",
+            users: [],
+            projects: [],
+            pagination: {
+                total: 0,
+                per_page: Common.pageSize,    // required
+                current_page: 1, // required
+                last_page: 10,    // required
+            },
+            traceLogMap: {},
+            formatDate: TraceCommon.formatDate,
+            suggestStyles: {
+                "defaultInput": "form-control"
+            },
+        }
+    },
+    components: {
+        Pagination, Navbar, TraceHeader, VueSimpleSuggest
+    },
+
+    methods: {
+        query: function () {
+            this.pagination.total = 0;
+            this.pagination.per_page = Common.pageSize;   // required
+            this.pagination.current_page = 1; // required
+            this.pagination.last_page = 10;    // required
+            this.pagination.from = 1;
+            this.pagination.to = 10;           // required
+            this.getDatas();
+        },
+
+        /**
+         * 查询数据
+         * @param {Object} success
+         * @param {Object} error
+         */
+        getDatas: function () {
+            var _self = this;
+            var queryParam = {
+                range: {
+                    start: (_self.pagination.current_page - 1) * _self.pagination.per_page,
+                    length: _self.pagination.per_page,
+                },
+                condition: _self.searchText,
+                projectId: _self.selectedProjectId,
+                operatorUserId: _self.selectedUserId
+            }
+            $.ajax({
+                url: Common.getApiURL('TraceLogResource/queryByCondition'),
+                type: 'post',
+                dataType: 'json',
+                contentType: "application/json",
+                data: JSON.stringify(queryParam),
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.traceLogMap = data.map;
+                   
+                    _self.pagination.total = data.totalSize;
+                    _self.pagination.last_page = Math.ceil(data.totalSize / data.range.length);
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 选择了项目
+         */
+        selectProject: function (project) {
+            if (project) {
+                this.selectedProjectId = project.id;
+            } else {
+                this.selectedProjectId = '';
+            }
+        },
+        /**
+         * 选择了用户
+         */
+        selectUser: function (user) {
+            if (user) {
+                this.selectedUserId = user.id;
+            } else {
+                this.selectedUserId = '';
+            }
+        },
+
+        /**
+         * 打开明细
+         * @author GuoZhiBo 20180116
+         */
+        openLineTrace: function (traceId) {
+            var _self = this;
+            if (traceId != null && traceId != undefined) {
+                this.$router.push({
+                    path: '/desktop/trace/' + traceId
+                });
+            } else {
+                Notify.error("提示", "找不到这个任务了!");
+            }
+        },
+
+        /**
+         * 获取日期格式
+         */
+        getDateFormat: function (date1) {
+            var _self = this;
+            if (date1 == undefined && date1 == "") {
+                return;
+            }
+            var date2 = _self.getNowFormatDate();
+            if (date1 == date2) {
+                return "今天";
+            }
+            var day = _self.dateDifference(date1, date2);
+            if (day == 1) {
+                return "昨天";
+            }
+            var month = _self.convertDateFromString(date1);
+            return month;
+        },
+
+        /**
+         * 获取当前日期
+         */
+        getNowFormatDate: function () {
+            var date = new Date();
+            var seperator1 = "-";
+            var year = date.getFullYear();
+            var month = date.getMonth() + 1;
+            var strDate = date.getDate();
+            if (month >= 1 && month <= 9) {
+                month = "0" + month;
+            }
+            if (strDate >= 0 && strDate <= 9) {
+                strDate = "0" + strDate;
+            }
+            var currentdate = year + seperator1 + month + seperator1 + strDate;
+            return currentdate;
+        },
+
+        /**
+         * 计算时间差
+         * @param {Object} sDate1
+         * @param {Object} sDate2
+         */
+        dateDifference: function (sDate1, sDate2) {    //sDate1和sDate2是2006-12-18格式  
+            var dateSpan,
+                tempDate,
+                iDays;
+            sDate1 = Date.parse(sDate1);
+            sDate2 = Date.parse(sDate2);
+            dateSpan = sDate2 - sDate1;
+            dateSpan = Math.abs(dateSpan);
+            iDays = Math.floor(dateSpan / (24 * 3600 * 1000));
+            return iDays
+        },
+
+        /**
+         * 获取日期
+         * @param {Object} dateString
+         */
+        convertDateFromString: function (dateString) {
+            if (dateString) {
+                var date = new Date(dateString.replace(/-/, "/"));
+                var month = date.getMonth() + 1;
+                return month + "/" + date.getDate();
+            }
+        },
+
+        /**
+         * 初始化项目
+         */
+        initProject: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL("ProjectItemResource/projectItem"),
+                dataType: 'json',
+                type: "get",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data != null) {
+                        data.unshift({
+                            name: "全部项目",
+                        })
+                        _self.projects = data;
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        /**
+         * 初始化用户
+         */
+        initUser: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL("userResource/listByClientId"),
+                dataType: 'json',
+                type: "get",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    if (data != null) {
+                        data.unshift({
+                            name: "全部人员",
+                        })
+                        _self.users = data;
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+        getContent: function(content){
+            if(content == null){
+                return '';
+            }
+            var result = content.replace(/<[^>]+>/g,"");
+            result = result.replace(/&nbsp;/ig, "");
+            return result;
+        }
+    },
+
+    mounted: function () {
+        this.initUser();
+        this.initProject();
+        this.getDatas();
+    },
+
+    watch: {
+        selectedProjectName: function (newValue, oldValue) {
+            if (newValue == null) {
+                this.selectedProjectId = null;
+            }
+            if (newValue.length < oldValue.length) {
+                this.selectedProjectId = null;
+            }
+        },
+
+        selectedUserName: function (newValue, oldValue) {
+            if (newValue == null) {
+                this.selectedUserId = null;
+            }
+            if (newValue.length < oldValue.length) {
+                this.selectedUserId = null;
+            }
+        }
+    }
+}
+</script>
+
+<style scoped>
+.mui-card {
+    margin: 0px;
+    margin-top: 0px;
+}
+.mui-card-footer:before,
+.mui-card-header:after {
+    background-color: #ffffff;
+}
+.mui-card-content {
+    padding: 0px 10px;
+}
+.mui-card-content p {
+    margin: 0px;
+}
+p {
+    font-size: 14px !important;
+    padding: 2px 0px;
+    color: #000000;
+}
+.time {
+    /*color: #8f8f94;*/
+    font-weight: bold;
+}
+.index {
+    font-size: 16px;
+    font-weight: bold;
+}
+.div-statis {
+    margin-bottom: 5px;
+    font-size: 0.8em;
+    text-align: center;
+}
+.mui-card-header {
+    padding-bottom: 0px;
+}
+.mui-div {
+    word-wrap: break-word;
+    white-space: normal;
+}
+.mui-div1 {
+    padding-left: 8%;
+    color: blue;
+}
+.mui-div2 {
+    color: cadetblue;
+}
+
+.mui-divs {
+    width: 50px;
+    height: 50px;
+    line-height: 44px;
+    text-align: center;
+    border-radius: 50%;
+    border: 4px solid #d2e8ea;
+    float: left;
+    margin-top: -3%;
+}
+img {
+    width: 50px;
+}
+</style>

+ 154 - 0
src/trace/TraceHeader.vue

@@ -0,0 +1,154 @@
+<template>
+    <nav class="navbar navbar-default">
+        <div class="container-fluid">
+            <div class="navbar-header">
+                <button type="button"
+                        class="navbar-toggle collapsed"
+                        data-toggle="collapse"
+                        data-target="#bs-example-navbar-collapse-1"
+                        aria-expanded="false">
+                    <span class="sr-only">Toggle navigation</span>
+                    <span class="icon-bar"></span>
+                    <span class="icon-bar"></span>
+                    <span class="icon-bar"></span>
+                </button>
+                <a class="navbar-brand"
+                   href="javascript:void(0)"
+                   style="padding-top: 6px;">
+
+                   
+                <span class="glyphicon glyphicon-circle-arrow-left m-image"
+                    @click="goBack"></span>
+
+                </a>
+            </div>
+
+            <div class="collapse navbar-collapse"
+                 id="bs-example-navbar-collapse-1">
+                <ul class="nav navbar-nav">
+                    <li @click="openProject"
+                        :class="{'active' : type == 'traceProject'}">
+                        <a class="nav-item-a"
+                           href="javascript:void(0)">项目</a>
+                    </li>
+                    <li @click="openSelf"
+                        :class="{'active' : type == 'traceList'}">
+                        <a class="nav-item-a"
+                           href="javascript:void(0)">我自己</a>
+                    </li>
+                    <li @click="openDynamic"
+                        :class="{'active' : type == 'traceDynamic'}">
+                        <a class="nav-item-a"
+                           href="javascript:void(0)">动态</a>
+                    </li>
+                    <li @click="team"
+                        :class="{'active' : type == 'team'}">
+                        <a class="nav-item-a"
+                           href="javascript:void(0)">团队</a>
+                    </li>
+                    <li @click="goHome">
+                        <a class="nav-item-a"
+                           href="javascript:void(0)">主页</a>
+                    </li>
+                    <li @click="traceSetting" v-if="isShow">
+                        <a class="nav-item-a"
+                           href="javascript:void(0)">任务配置</a>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </nav>
+</template>
+
+<script>
+
+var Uuid = require("pc-client-component").Uuid;
+var Common = require("../common/Common.js");
+
+export default {
+    props: ["type"],
+
+    data: function () {
+        return {
+			isShow:"false",
+        }
+    },
+
+    methods: {
+        /**
+         * 打开项目界面
+         */
+        openProject() {
+            var _self = this;
+            this.$router.push("/desktop/projectList");
+        },
+
+        /**
+         * 打开动态界面
+         */
+        openDynamic() {
+            var _self = this;
+            this.$router.push("/desktop/traceDynamic");
+        },
+
+        /**
+         * 打开我自己界面
+         */
+        openSelf() {
+            this.$router.push("/desktop/traceList/I_LAUNCH");
+        },
+
+        /**
+         * 打开团队界面
+         */
+        team() {
+            this.$router.push("/desktop/teamList");
+        },
+
+        goBack() {
+            history.back();
+        },
+
+        goHome() {
+            this.$router.push("/desktop/dashboard");
+        },
+        
+        traceSetting(){
+        	this.$router.push("/desktop/traceConfig");
+        },
+        /**
+         * 查询配置信息
+         * @return {[type]} [description]
+         */
+        queryIsAdmin: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceResource/queryIsAdmin'),
+                type: "get",
+                dataType: "json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.isShow = data;
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+        
+        
+    },
+    mounted: function () {
+       
+        this.queryIsAdmin();
+    }
+}
+</script>
+<style scoped>
+.nav-item-a {
+    padding-top: 15px;
+    padding-bottom: 15px;
+}
+</style>

+ 301 - 0
src/trace/TraceList.vue

@@ -0,0 +1,301 @@
+<template>
+	<div>
+		<TraceHeader :type="'traceList'"></TraceHeader>
+		<div>
+			<div>
+				<ul class="nav nav-tabs">
+					<li @click="param.traceUserStatus='I_LAUNCH'" :class="{'active' : param.traceUserStatus == 'I_LAUNCH'}"><a>我负责的</a></li>
+					<li @click="param.traceUserStatus='I_ATTEND_PROJECT_ALL'" :class="{'active' : param.traceUserStatus == 'I_ATTEND_PROJECT_ALL'}"><a>我参与项目的</a></li>
+					<li @click="param.traceUserStatus='I_RESPONSE'" :class="{'active' : param.traceUserStatus == 'I_RESPONSE'}"><a>我发起的</a></li>
+				</ul>
+			</div>
+			<br />
+			<div class="form-group">
+				<label for="exampleInputEmail2">选择任务状态</label>
+				<select v-model="param.traceStatus" class="form-control">
+					<option value="RUNNING">追踪中</option>
+					<option value="FINISHED">完成的</option>
+				</select>
+			</div>
+			<div class="form-group">
+				<label for="exampleInputEmail2">查询条件</label>
+				<QueryWidget ref="queryWidget" @search="getDatas()" @valueChanged="getDatas()"></QueryWidget>
+			</div>
+		</div>
+
+		<div class="panel panel-default">
+			<div class="panel-body">
+				<vuedraggable class="wrapper" v-model="traceDtos" @change="end">
+					<transition-group>
+						<div v-for="item in traceDtos" :key="item.id" style="margin-top: 5px; cursor: pointer;">
+							<span>
+								<span>
+									<Checkbox :id="'trace-finish-' + item.id" class-name="terms" v-model="item.finished" class="trace-checkbox"
+									 @input="updateTracefinished(item)">
+									</Checkbox>
+								</span>
+								<span @click="openLine(item)">
+									<span v-html="item.summary" class="trace-summary" :class="{'font-color': item.timeLineCompletion==true}">
+									</span>
+									<span class="glyphicon glyphicon-option-vertical trace-icon" aria-hidden="true"></span>
+									<span class="label trace-user" :class="{'label-danger' : item.overdue == true, 'label-primary' : item.overdue != true}">
+										<span v-html="item.receiveUserName"></span>
+										<span>{{formatDate(item.planFinishedDate)}}</span>
+									</span>
+								</span>
+								<a class="fa-pull-right" @click="edit(item)" style="color: blue;">编辑</a>
+							</span>
+						</div>
+					</transition-group>
+				</vuedraggable>
+			</div>
+		</div>
+
+		<div class="row">
+			<div class="pull-left">
+				<span>第{{(pagination.current_page-1)*pagination.per_page+1}}-{{pagination.current_page*pagination.per_page}}条,共计{{pagination.total}}条,每页显示</span>
+				<PageSizeSelect  v-on:pageSizeChanged="gridSizeSelect"></PageSizeSelect>
+				<span>条</span>
+			</div>
+
+			<div class="pull-right">
+				<Pagination :pagination="pagination" :callback="getDatas" ></Pagination>
+			</div>
+		</div>
+
+<!-- 		<div>
+			<Pagination :pagination="pagination" :callback="getDatas" ></Pagination>
+		</div> -->
+		<h3>&nbsp;</h3>
+	</div>
+</template>
+
+<script>
+	var Common = require("../common/Common.js");
+	var Uuid = require("pc-client-component").Uuid;
+	import TraceCommon from "./TraceCommon.js";
+	import QueryWidget from "../widget/QueryWidget.vue";
+	import TraceHeader from "./TraceHeader.vue";
+	var Pagination = require("vue-bootstrap-pagination").default;
+	var PageSizeSelect = require("pc-client-component").PageSizeSelect;
+	var Navbar = require("pc-client-component").Navbar;
+	import TraceResource from "./TraceResource.js";
+	var Checkbox = require("pc-client-component").Checkbox;
+	import vuedraggable from 'vuedraggable';
+
+	export default {
+		data: function() {
+			return {
+				"param": {
+					"condition": "",
+					"traceStatus": "RUNNING",
+					"traceUserStatus": "I_ATTEND_PROJECT_ALL",
+					"range": {
+						start: 0,
+						length: 20
+					},
+				},
+				"totalSize": 1,
+				"traceDtos": [],
+				pagination: {
+					total: 0,
+					per_page: 20, // required
+					current_page: 1, // required
+					last_page: 10, // required
+				},
+				formatDate: TraceCommon.formatDate,
+			}
+		},
+		components: {
+			QueryWidget,
+			Pagination,
+			Navbar,
+			TraceHeader,
+			TraceResource,
+			Checkbox,
+			vuedraggable,
+			PageSizeSelect
+		},
+		methods: {
+			gridSizeSelect: function(newPageSize) {
+				this.pagination.per_page = newPageSize;
+				this.pagination.current_page = 1;
+				// 刷新界面
+				this.getDatas();
+			},
+			end(evt) {
+				var _self = this;
+				_self.param.condition = _self.$refs.queryWidget.getSearchText();
+				_self.param.id = evt.moved.element.id;
+				_self.param.oldIndex = evt.moved.oldIndex;
+				_self.param.newIndex = evt.moved.newIndex;
+				_self.param.range = {
+					start: (_self.pagination.current_page - 1) * _self.pagination.per_page,
+					length: _self.pagination.per_page,
+				};
+				$.ajax({
+					url: Common.getApiURL('TraceResource/sortTraceByTraceQueryParamDto'),
+					type: 'post',
+					dataType: 'json',
+					contentType: "application/json",
+					data: JSON.stringify(_self.param),
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						_self.getDatas();
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+			/**
+			 * 查询数据
+			 * @author GuoZhiBo 20180226
+			 */
+			getDatas: function() {
+				var _self = this;
+				_self.param.condition = _self.$refs.queryWidget.getSearchText();
+				_self.param.range = {
+					start: (_self.pagination.current_page - 1) * _self.pagination.per_page,
+					length: _self.pagination.per_page,
+				};
+				$.ajax({
+					url: Common.getApiURL('TraceResource/listByTraceQueryParamDto'),
+					type: 'post',
+					dataType: 'json',
+					contentType: "application/json",
+					data: JSON.stringify(_self.param),
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if (data != null && data.dataList != null) {
+							_self.traceDtos = data.dataList;
+						}
+						_self.pagination.total = data.totalSize;
+						_self.pagination.last_page = Math.ceil(data.totalSize / data.range.length);
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+
+
+
+
+			/**
+			 * 打开明细
+			 * @author GuoZhiBo 20171201
+			 */
+			openLine: function(obj) {
+				this.$router.push({
+					path: '/desktop/trace/' + obj.id
+				});
+			},
+
+
+			/**
+			 * 编辑
+			 */
+			edit: function(item) {
+				var _self = this;
+				this.$router.push("/desktop/traceUpdate/" + item.id);
+			},
+
+			/**
+			 * 修改追踪单的状态
+			 * @author YangZhiJie 20171201
+			 */
+			updateTracefinished: function(trace) {
+				var _self = this;
+				TraceResource.updateTraceFinished(trace.id).then(successData => {
+					_self.getDatas();
+
+				}, errorData => {
+					Common.processException(errorData);
+				});
+			},
+
+
+		},
+
+		mounted: function() {
+			TraceResource.getImg();
+			this.param.traceUserStatus = this.$route.params.traceState;
+			this.getDatas();
+		},
+
+		watch: {
+			"param.traceStatus": function(val) {
+				this.getDatas();
+			},
+			"param.traceUserStatus": function(val) {
+				this.$router.push("/desktop/traceList/" + val);
+				this.param.traceUserStatus = this.$route.params.traceState;
+				this.pagination.total = 0;
+				this.pagination.per_page = Common.pageSize;
+				this.pagination.current_page = 1;
+				this.pagination.last_page = 10;
+				this.pagination.from = 1;
+				this.pagination.to = 10;
+				this.getDatas();
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.active-div {
+		background-color: #007aff;
+		color: white !important;
+	}
+
+	.div-btn {
+		float: left;
+		height: 40px;
+		text-align: center;
+		line-height: 40px;
+		color: #007aff;
+	}
+
+	.mid-div {
+		border-left: 1px solid #007aff;
+		border-right: 1px solid #007aff;
+	}
+
+	.font-color {
+		color: red;
+	}
+
+	.divrow {
+		border: 1px solid #007aff;
+		height: 40px;
+	}
+
+	.select select {
+		width: 100%;
+		height: 30px;
+	}
+
+	.trace-summary {
+		font-size: large;
+		margin-left: 5px;
+		margin-right: 5px;
+	}
+
+	.trace-icon {
+		opacity: 0.5;
+	}
+
+	.trace-user {
+		font-size: 100%;
+	}
+
+	.trace-checkbox {
+		display: inline;
+		font-size: large;
+	}
+</style>

+ 99 - 0
src/trace/TraceLog.vue

@@ -0,0 +1,99 @@
+<!--
+	作者:yangzhijie1488@163.com
+	时间:2017-12-12
+	描述:追踪日志
+-->
+<template>
+	<div>
+		<div class="panel panel-default">
+            <div class="panel-heading dashboard-header"
+                 @click.self="collapse()">
+                &nbsp;&nbsp;动态信息
+                <div class="pull-left">
+                    <span class="glyphicon"
+                          :class="{'glyphicon-triangle-bottom' : !isCollapse, 'glyphicon glyphicon-triangle-top' : isCollapse}"></span>
+                </div>
+            </div>
+            <div v-if="isCollapse == true" class="container-fluid"
+                 id="trace-content">
+                 <div v-for="item in traceLogs" :key="item.id">
+					<h5>{{item.dateTime}} &nbsp;{{item.operatorUserName}}
+						<div v-html="item.content"></div>
+					</h5>
+				</div>
+            </div>
+        </div>
+	</div>
+</template>
+
+<script>
+	var Common = require("../common/Common.js");
+	export default {
+		props: ["traceId"],
+
+		data: function() {
+			return {
+				traceLogs: [],
+				isCollapse:false
+			}
+		},
+		components: {
+			Common
+		},
+		methods: {
+			
+			/**
+	         * 控制展开闭合
+	         * @return {void} 
+	         */
+	        collapse: function () {
+	            var _self = this;
+	            _self.isCollapse = !_self.isCollapse;
+				_self.getTraceLog();	            
+	        },
+			/**
+			 * 根据追踪单Id获取追踪日志信息
+			 * @author GuoZhiBo 20171201
+			 */
+			getTraceLog: function() {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceLogResource/listByTraceId'),
+					type: 'get',
+					dataType: 'json',
+					async: false,
+					data: {
+						traceId: _self.traceId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						_self.traceLogs = data;
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+		},
+		
+		mounted: function(){
+			if(this.traceId != undefined && this.isCollapse == true){
+				this.getTraceLog();
+			}
+		},
+		
+		watch: {
+			"traceId": function(curVal,oldVal){
+				if(curVal != undefined && this.isCollapse == true){
+					this.getTraceLog();
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 113 - 0
src/trace/TraceResource.js

@@ -0,0 +1,113 @@
+var Common = require("../common/Common.js");
+
+module.exports = {
+
+    /**
+     * 查询Trace
+     */
+    uniqueByTraceId: function (traceId) {
+        return new Promise((resolve, reject) => {
+            $.ajax({
+                url: Common.getApiURL('TraceResource/uniqueByTraceId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "traceId": traceId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    resolve(data);
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    reject(XMLHttpRequest);
+                }
+            });
+        });
+    },
+
+    /**
+     * 设置追踪单已完成
+     */
+    updateTraceFinished: function (traceId) {
+        return new Promise((resolve, reject) => {
+            $.ajax({
+                url: Common.getApiURL("TraceResource/updateTraceFinished"),
+                type: "post",
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                data: JSON.stringify({
+                    "id": traceId
+                }),
+                success: function (data) {
+                    resolve(data);
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    reject(XMLHttpRequest);
+                }
+            });
+        });
+    },
+
+    listFinishedByProjectId: function (projectId, pagination, content) {
+        return new Promise((resolve, reject) => {
+            $.ajax({
+                url: Common.getApiURL('TraceResource/listFinishedByProjectId'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "projectId": projectId,
+                    "currentPage": pagination.current_page,
+                    "pageSize": pagination.per_page,
+                    "content": content
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    resolve(data);
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    reject(XMLHttpRequest);
+                }
+            });
+        });
+    },
+    getImg:function(){
+    	var _self = this;
+    	Object.defineProperty(Image.prototype, 'authsrc', {
+            writable : true,
+            enumerable : true,
+            configurable : true
+        })
+        window.onload = () => {
+            var imgs = document.getElementsByTagName('img');
+            for(var i=0,len=imgs.length;i<len;i++){
+            	_self.requsetImg(imgs[i]);
+	        }
+        }
+    },
+    requsetImg:function(img){
+        var url = img.getAttribute('authsrc');
+        if(url != undefined && url != null){
+        	var request = new XMLHttpRequest();
+            request.responseType = 'blob';
+            request.open('get', url, true);
+            Common.addTokenToRequest(request);
+            request.onreadystatechange = e => {
+                if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
+                    img.src = URL.createObjectURL(request.response);
+                    img.onload = () => {
+                        URL.revokeObjectURL(img.src);
+                    }
+                }
+            };
+            request.send(null);
+        }
+    }
+}

+ 332 - 0
src/trace/TraceTimeLine.vue

@@ -0,0 +1,332 @@
+<!--
+	作者:yangzhijie1488@163.com
+	时间:2017-12-12
+	描述:追踪单时间节点
+-->
+<template>
+    <div>
+        <div>
+            <div class="page-header">
+                <h4>时间节点({{number}}/{{count}})({{trace.projectTaskName}})</h4>
+            </div>
+        </div>
+
+        <div class="panel panel-default"
+             v-if="timeLines != undefined && timeLines[0].id != undefined">
+            <div class="panel-body">
+                <div v-for="(item) in timeLines"
+                     :key="item.id"
+                     class="after-selection--columns"
+                     style="margin-top: 5px; cursor: pointer;">
+                    <span>
+                        <Checkbox :id="'trace-finish-' + item.id"
+                                  class-name="terms"
+                                  v-model="item.finished"
+                                  class="trace-checkbox"
+                                  @input="openCloseTraceTimeLine(item)">
+                        </Checkbox>
+                    </span>
+
+                    <span @click="editTraceTimeLine(item)"
+                          class="big-icon trace-summary">
+                        {{item.content == undefined ? "请填写" : item.content}}
+                    </span>
+
+                    <span @click="editTraceTimeLine(item)"
+                          class="trace-summary">
+                        <span class="label"
+                              :class="{'label-danger' : item.overdue == true, 'label-primary' : item.overdue != true}">
+                            {{item.userName == undefined ? "" : item.userName}}
+                            &nbsp;
+                            {{formatDate(item.endDate)}}
+                        </span>
+                    </span>
+                </div>
+            </div>
+        </div>
+
+        <button v-if="trace.finished != true"
+                @click="createTraceTimeLine"
+                type="button"
+                style="width: 100%;"
+                class="btn btn-default"
+                aria-label="Left Align">
+            <span class="glyphicon glyphicon-plus"
+                  aria-hidden="true"></span>添加时间节点
+        </button>
+		<Loading ref="loading"></Loading>
+    </div>
+</template>
+
+<script>
+var Uuid = require("pc-client-component").Uuid;
+var Common = require("../common/Common.js");
+import TraceCommon from "./TraceCommon.js";
+
+var Loading = require("pc-client-component").Loading;
+import MyProgress from "../widget/MyProgress.vue";
+var Checkbox = require("pc-client-component").Checkbox;
+
+export default {
+    props: ["traceId", "trace"],
+
+    data: function () {
+        return {
+            "uuid": Uuid.createUUID(),
+            "timeLines": [
+                {
+                    "id": undefined,
+                    "traceId": undefined,
+                    "userId": undefined,
+                    "userName": undefined,
+                    "content": undefined,
+                    "endDate": undefined
+                }
+            ],
+            "count": 0,
+            "number": 0,
+            "progress": 0,
+            'timeLine': {
+                "traceId": undefined,
+                "userId": undefined,
+                "userName": undefined,
+                "content": undefined,
+                "endDate": undefined
+            },
+            formatDate: TraceCommon.formatDate,
+        }
+    },
+
+    components: {
+        MyProgress, Checkbox, Loading
+    },
+    methods: {
+
+        /**
+         * 根据追踪单Id查询追踪时间节点数据
+         * @author GuoZhiBo 20171201
+         */
+        getTimeLine: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceTimeLineResource/listByTraceId'),
+                type: 'get',
+                dataType: 'json',
+                async: false,
+                data: {
+                    traceId: _self.traceId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.timeLines = data;
+                    _self.addEmtpyTraceTimeLine();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+
+        /**
+         * 根据追踪单ID查询时间节点总数量
+         * @author GuoZhiBo 20171201
+         */
+        getTimeLineCount: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceTimeLineResource/queryTraceTimeLineCount'),
+                type: 'get',
+                dataType: 'json',
+                async: false,
+                data: {
+                    traceId: _self.traceId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.count = data;
+                    _self.progresss();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+
+        /**
+         * 根据追踪单Id查询时间节点的完成数量
+         * @author GuoZhiBo 20171201
+         */
+        getTimeLineNumber: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL('TraceTimeLineResource/queryTraceTimeLineNumber'),
+                type: 'get',
+                dataType: 'json',
+                async: false,
+                data: {
+                    traceId: _self.traceId
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.number = data;
+                    _self.progresss();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+
+        /**
+         * 计算progress
+         * @author GuoZhiBo 20171201
+         */
+        progresss: function () {
+            var _self = this;
+            if (_self.number == 0 || _self.count == 0) {
+                _self.progress = 0;
+            } else {
+                _self.progress = ((_self.number / _self.count).toFixed(2)) * 100;
+            }
+        },
+
+        /**
+         * 添加空的时间节点
+         */
+        addEmtpyTraceTimeLine: function () {
+            var _self = this;
+            if (_self.timeLines == undefined) {
+                _self.timeLines = [];
+            }
+            for (var i = _self.timeLines.length; i < 1; i++) {
+                var emtpyTraceTimeLine = {
+                    "id": undefined,
+                    "traceId": _self.traceId,
+                    "userId": undefined,
+                    "userName": undefined,
+                    "content": undefined,
+                    "endDate": undefined
+                }
+                _self.timeLines.push(emtpyTraceTimeLine);
+            }
+        },
+
+        /**
+         * 编辑时间节点
+         * @param {Object} traceTimeLine
+         */
+        editTraceTimeLine: function (traceTimeLine) {
+            var _self = this;
+            this.$router.push({
+                path: "/desktop/traceTimeLineEdit/" + traceTimeLine.id,
+                query: {
+                    projectId: _self.trace.projectId
+                }
+            });
+        },
+
+        /**
+         * 创建时间节点
+         */
+        createTraceTimeLine: function(){
+            var _self = this;
+            this.$router.push({
+                path: "/desktop/traceTimeLineCreate/" + this.trace.id,
+                query: {
+                    projectId: _self.trace.projectId
+                }
+            });
+        },
+
+        /**
+         * 打开或关闭时间节点
+         * @param {Object} traceTimeLine
+         */
+        openCloseTraceTimeLine: function (traceTimeLine) {
+            var _self = this;
+            if (_self.trace.finished != true) {
+                _self.$refs.loading.show();
+                $.ajax({
+                    url: Common.getApiURL('TraceTimeLineResource/updateTraceTimeLineFinished'),
+                    type: 'post',
+                    contentType: 'application/json',
+                    data: JSON.stringify(traceTimeLine),
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    success: function (data) {
+                        _self.$refs.loading.hide();
+                        _self.refresh();
+                        _self.$emit("refreshTraceLog");
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        _self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+                Notify.error("提示", "已完成的订单不能修改!");
+            }
+        },
+
+		/**
+		 * 刷新界面
+		 */
+        refresh: function () {
+            this.getTimeLine();
+            this.getTimeLineCount();
+            this.getTimeLineNumber();
+        }
+    },
+
+
+    mounted: function () {
+        if (this.traceId != undefined) {
+            this.refresh();
+            this.timeLine.traceId = this.traceId;
+        }
+    },
+
+    watch: {
+        "traceId": function (curVal, oldVal) {
+            if (curVal != undefined) {
+            	this.refresh();
+            }
+        }
+    }
+}
+</script>
+
+<style scoped>
+.mui-checkbox-self {
+    left: 0px !important;
+    top: 0px !important;
+}
+.font-color {
+    color: red;
+}
+
+.big-icon {
+    font-size: large;
+}
+
+.trace-summary {
+    font-size: large;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.trace-checkbox {
+    display: inline;
+    font-size: large;
+}
+</style>

+ 209 - 0
src/trace/TraceTimeLineCreate.vue

@@ -0,0 +1,209 @@
+<!--
+	作者:yangzhijie1488@163.com
+	时间:2017-12-12
+	描述:创建追踪单时间节点
+-->
+
+<template>
+    <div>
+        <div>
+            <Navbar title="时间节点"
+                    :isGoBack="true"></Navbar>
+            <div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">添加检查项</label>
+                    <textarea style="width: 100%;"
+                              id="hcqk"
+                              class="form-control"
+                              rows="5"
+                              v-model="traceTimeLine.content"></textarea>
+                </div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">负责人</label>
+                    <select style="width: 100%;height: 30px;"
+                            id="userName"
+                            class="form-control js-data-example-ajax"></select>
+                </div>
+
+                <div class="form-group">
+                    <label for="exampleInputEmail2">截止时间</label>
+                    <DateWidget @on-value-change="finishAfterDateChanged"
+                                :dateValue="traceTimeLine.endDate"
+                                v-model="traceTimeLine.endDate"
+                                style="width: 100%;"></DateWidget>
+                </div>
+                <div style="margin-top: 10px;">
+                    <button @click="saveTraceTimeLine()"
+                            type="text"
+                            class="btn btn-default">确认</button>
+                    <button @click="returnBack()"
+                            type="text"
+                            class="btn btn-default">取消</button>
+                </div>
+            </div>
+        </div>
+        <Loading ref="loading"></Loading>
+    </div>
+</template>
+
+<script>
+var Navbar = require("pc-client-component").Navbar;
+var Uuid = require("pc-client-component").Uuid;
+var DateWidget = require("pc-client-component").Date;
+var Loading = require("pc-client-component").Loading;
+var Notify = require("pc-client-component").Notify;
+var Common = require("../common/Common.js");
+
+export default {
+    data: function () {
+        return {
+            traceId: '',			// 追踪单(输入参数)
+            projectId: '',		// 项目Id
+            traceTimeLine: {
+                "trackId": undefined,
+                "userId": undefined,
+                "userName": undefined,
+                "content": undefined,
+                "endDate": undefined
+            },	// 追踪单时间节点(输入参数)
+        }
+    },
+
+    components: {
+        Navbar,
+        Uuid,
+        DateWidget,
+        Loading,
+        Notify,
+        Common
+    },
+
+    methods: {
+        /**
+         * 初始化数据
+         */
+        initData: function () {
+            var _self = this;
+            this.traceId = Number(this.$route.params.traceId);
+            this.projectId = Number(this.$route.query.projectId);
+            this.loadSelectUserName();
+        },
+
+
+        /**@argument
+         * 保存当前时间节点数据
+         */
+        saveTraceTimeLine: function () {
+            var _self = this;
+            _self.traceTimeLine.traceId = _self.traceId;
+            var content = _self.traceTimeLine.content;
+            var dates = _self.traceTimeLine.endDate;
+
+            if (content != null && content != "" && dates != null && dates != undefined && dates != "") {
+                _self.$refs.loading.show();
+                $.ajax({
+                    url: Common.getApiURL("TraceTimeLineResource/saveTraceTimeLine"),
+                    type: "post",
+                    contentType: "application/json",
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    data: JSON.stringify(_self.traceTimeLine),
+                    success: function (data) {
+                        _self.$refs.loading.hide();
+                        _self.returnBack();
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        _self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+                _self.$refs.loading.hide();
+                Notify.error("提示", "请将界面内容填写完整!");
+            }
+        },
+
+
+        /**@argument
+         * 返回上一界面
+         */
+        returnBack: function () {
+            history.back();
+        },
+
+        /**@argument
+         * 时间改变触发事件
+         */
+        finishAfterDateChanged: function (value) {
+            var _self = this;
+            _self.traceTimeLine.endDate = value;
+        },
+        /**
+         * 加载用户
+         */
+        loadSelectUserName: function () {
+            var _self = this;
+            $("#userName").select2({
+                // 请求搜索框数据
+                //data: 
+                placeholder: "用户",
+                minimumInputLength: 0,
+                placeholderOption: "first",
+                quietMillis: 250,
+                allowClear: true,
+                language: "zh-CN",
+                width: '100%',
+                placeholder: { id: _self.traceTimeLine.userId, text: _self.traceTimeLine.userName },
+                // 请求搜索框数据
+                ajax: {
+                    url: function (params) {
+                        return Common.getApiURL('ProjectItemUserResource/listByProjectItemId?projectItemId=' + _self.projectId);//通过项目Id查找有权操作的项目的用户
+                    },
+                    dataType: 'json',
+                    type: "get",
+                    delay: 250,
+                    minimumInputLength: 1,
+                    transport: function (params, success, failure) {
+                        params.beforeSend = Common.addTokenToRequest;
+                        params.error = Common.processException;
+                        var $request = $.ajax(params);
+                        $request.then(success);
+                        $request.fail(failure);
+                        return $request;
+                    },
+                    data: function (params) {
+                        return {
+
+                        };
+                    },
+                    processResults: function (data, params) {
+                        var more = (params * 10) <= data.length;
+                        for (var i = 0; i < data.length; i++) {
+                            data[i].id = data[i].userId;
+                            data[i].text = data[i].userName;
+                        }
+                        return {
+                            results: data,
+                            more: more
+                        };
+                    }
+                },
+            }).on("change", function () {
+                _self.traceTimeLine.userId = $(this).val();
+                _self.traceTimeLine.userName = $(this).text();
+            });
+        }
+    },
+
+
+    mounted: function () {
+        $(".input-tan").focus(function () {
+            document.activeElement.blur();
+        });
+        this.initData();
+    }
+}
+</script>
+<style scoped>
+</style>

+ 275 - 0
src/trace/TraceTimeLineEdit.vue

@@ -0,0 +1,275 @@
+<!--
+	作者:yangzhijie1488@163.com
+	时间:2017-12-12
+	描述:创建追踪单时间节点
+	输入参数:从localstorage中输入参数
+	{
+		"canDelete": 是否可删除数据
+		"canSave": 是否可保存数据
+		"trace": 追踪单
+		"traceTimeLine": 追踪单时间节点
+	}
+-->
+
+<template>
+    <div>
+        <div>
+            <Navbar title="时间节点"
+                    :isGoBack="true"></Navbar>
+            <div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">添加检查项</label>
+                    <textarea style="width: 100%;"
+                              id="hcqk"
+                              class="form-control"
+                              rows="5"
+                              v-model="traceTimeLine.content"></textarea>
+                </div>
+                <div class="form-group">
+                    <label for="exampleInputEmail2">负责人</label>
+                    <select style="width: 100%;height: 30px;"
+                            id="userName"
+                            class="form-control js-data-example-ajax"></select>
+                </div>
+
+                <div class="form-group">
+                    <label for="exampleInputEmail2">截止时间</label>
+                    <DateWidget @on-value-change="finishAfterDateChanged"
+                                :dateValue="traceTimeLine.endDate"
+                                v-model="traceTimeLine.endDate"
+                                style="width: 100%;"></DateWidget>
+                </div>
+                <div style="margin-top: 10px;">
+                    <button @click="saveTraceTimeLine()"
+                            type="text"
+                            class="btn btn-default">确认</button>
+                    <button @click="returnBack()"
+                            type="text"
+                            class="btn btn-default">取消</button>
+                    <button @click="deleteById()"
+                            type="text"
+                            class="btn btn-default">删除</button>
+
+                </div>
+            </div>
+        </div>
+        <Loading ref="loading"></Loading>
+    </div>
+</template>
+
+<script>
+var Navbar = require("pc-client-component").Navbar;
+var Uuid = require("pc-client-component").Uuid;
+var DateWidget = require("pc-client-component").Date;
+var Loading = require("pc-client-component").Loading;
+var Notify = require("pc-client-component").Notify;
+var Common = require("../common/Common.js");
+
+export default {
+    data: function () {
+        return {
+            traceTimeLineId: '',			// 追踪单(输入参数)
+            projectId: '',		// 项目Id
+            traceTimeLine: {
+                "trackId": undefined,
+                "userId": undefined,
+                "userName": undefined,
+                "content": undefined,
+                "endDate": undefined
+            },	// 追踪单时间节点(输入参数)
+        }
+    },
+
+    components: {
+        Navbar,
+        Uuid,
+        DateWidget,
+        Loading,
+        Notify,
+        Common
+    },
+
+    methods: {
+        /**
+         * 初始化数据
+         */
+        initData: function () {
+            var _self = this;
+            this.traceTimeLineId = Number(this.$route.params.traceTimeLineId);
+            this.projectId = Number(this.$route.query.projectId);
+            this.loadTraceTimeLine();
+        },
+
+		/**
+		 * 加载时间节点Id
+		 */
+        loadTraceTimeLine: function () {
+            var _self = this;
+            $.ajax({
+                url: Common.getApiURL("TraceTimeLineResource/uniqueTraceTimeLineDto"),
+                type: "get",
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                data: {
+                    traceTimeLineId: _self.traceTimeLineId,
+                },
+                success: function (data) {
+					_self.traceTimeLine = data;
+            		_self.loadSelectUserName();
+                    _self.$refs.loading.hide();
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    _self.$refs.loading.hide();
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+
+        /**@argument
+         * 保存当前时间节点数据
+         */
+        saveTraceTimeLine: function () {
+            var _self = this;
+            _self.traceTimeLine.traceId = _self.traceId;
+            var content = _self.traceTimeLine.content;
+            var dates = _self.traceTimeLine.endDate;
+
+            if (content != null && content != "" && dates != null && dates != undefined && dates != "") {
+                _self.$refs.loading.show();
+                $.ajax({
+                    url: Common.getApiURL("TraceTimeLineResource/saveTraceTimeLine"),
+                    type: "post",
+                    contentType: "application/json",
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    data: JSON.stringify(_self.traceTimeLine),
+                    success: function (data) {
+                        _self.$refs.loading.hide();
+                        _self.returnBack();
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        _self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+                _self.$refs.loading.hide();
+                Notify.error("提示", "请将界面内容填写完整!");
+            }
+
+        },
+
+
+        /**@argument
+         * 根据时间节点ID删除时间节点信息
+         */
+        deleteById: function () {
+            var _self = this;
+            _self.$refs.loading.show();
+            $.ajax({
+                url: Common.getApiURL('TraceTimeLineResource/deleteById'),
+                type: 'get',
+                data: {
+                    traceTimeLineId: _self.traceTimeLine.id
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.$refs.loading.hide();
+                    _self.returnBack();
+                },
+                error: function (xmlHttpRequest, textStatus, errorThrown) {
+                    _self.$refs.loading.hide();
+                    Common.processException(xmlHttpRequest, textStatus, errorThrown);
+                }
+            });
+        },
+
+
+        /**@argument
+         * 返回上一界面
+         */
+        returnBack: function () {
+            history.back();
+        },
+
+        /**@argument
+         * 时间改变触发事件
+         */
+        finishAfterDateChanged: function (value) {
+            var _self = this;
+            _self.traceTimeLine.endDate = value;
+        },
+        /**
+         * 加载用户
+         */
+        loadSelectUserName: function () {
+            var _self = this;
+            $("#userName").select2({
+                // 请求搜索框数据
+                //data: 
+                placeholder: "用户",
+                minimumInputLength: 0,
+                placeholderOption: "first",
+                quietMillis: 250,
+                allowClear: true,
+                language: "zh-CN",
+                width: '100%',
+                placeholder: { id: _self.traceTimeLine.userId, text: _self.traceTimeLine.userName },
+                // 请求搜索框数据
+                ajax: {
+                    url: function (params) {
+                        return Common.getApiURL('ProjectItemUserResource/listByProjectItemId?projectItemId=' + _self.projectId);//通过项目Id查找有权操作的项目的用户
+                    },
+                    dataType: 'json',
+                    type: "get",
+                    delay: 250,
+                    minimumInputLength: 1,
+                    transport: function (params, success, failure) {
+                        params.beforeSend = Common.addTokenToRequest;
+                        params.error = Common.processException;
+                        var $request = $.ajax(params);
+                        $request.then(success);
+                        $request.fail(failure);
+                        return $request;
+                    },
+                    data: function (params) {
+                        return {
+
+                        };
+                    },
+                    processResults: function (data, params) {
+                        var more = (params * 10) <= data.length;
+                        for (var i = 0; i < data.length; i++) {
+                            data[i].id = data[i].userId;
+                            data[i].text = data[i].userName;
+                        }
+                        return {
+                            results: data,
+                            more: more
+                        };
+                    }
+                },
+            }).on("change", function () {
+                _self.traceTimeLine.userId = $(this).val();
+                _self.traceTimeLine.userName = $(this).text();
+            });
+        }
+    },
+
+
+    mounted: function () {
+        $(".input-tan").focus(function () {
+            document.activeElement.blur();
+        });
+        this.initData();
+    }
+}
+</script>
+<style scoped>
+</style>

+ 385 - 0
src/trace/TraceUpdate.vue

@@ -0,0 +1,385 @@
+<template>
+    <div>
+        <div>
+            <Navbar title='编辑任务'
+                    :isGoBack="true"></Navbar>
+            <div v-if="traceDto != null">
+                <div class="form-group">
+                    <label for="exampleInputEmail1">标题</label>
+                    <input type="text"
+                           class="form-control"
+                           id="exampleInputEmail1"
+                           placeholder="标题"
+                           v-model="traceDto.summary">
+                </div>
+                <div class="form-group">
+                    <label for="exampleInputPassword1">内容</label>
+                    <div id="summernote"></div>
+                </div>
+				
+                <div class="form-group">
+                    <label for="projectName">项目</label>
+                    <select id="projectName"
+                            class="form-control"
+                            v-model="traceDto.projectId"
+                            @change="changeProjectSelected()">
+                        <option :value="project.projectItemId"
+                                :key="project.id"
+                                v-for="project in projectItemUserListProject">
+                            {{project.projectItemName}}
+                        </option>
+                    </select>
+                </div>
+                <AudioField @handleStop="handelEndRecord" :className = "className" :srcArray = "srcArray" @deleteAudioSrc = "deleteAudioSrc" :isReadonly = "'false'"/>
+				<div class="form-group">
+                    <label for="projectName">时间节点</label>
+                    <Treeselect ref = "treeSelect" :multiple="false" :options="projectTaskDtos" />
+                </div>
+                <div class="form-group">
+                    <label for="receiveUserId">责任人</label>
+                    <select id="receiveUserId"
+                            class="form-control"
+                            v-model="traceDto.receiveUserId">
+                        <option value=""></option>
+                        <option :value="user.userId"
+                                :key="user.userId"
+                                v-for="user in projectItemUserListUser">
+                            {{user.userName}}
+                        </option>
+                    </select>
+                </div>
+
+                <div class="form-group">
+                    <label>完成时间</label>
+                    <DateWidget @on-value-change="planFinishedDateChanged"
+                                :dateValue="formatDate(traceDto.planFinishedDate)"
+                                class="form-control"></DateWidget>
+                </div>
+
+                <button style="width:100%;background-color: #007aff;color: white"
+                        @click="updateTrace()"
+                        type="button"
+                        class="btn btn-blue search-button">
+                    保存
+                </button>
+            </div>
+        </div>
+        <Loading ref="loading"></Loading>
+    </div>
+</template>
+<script>
+import TraceResource from "./TraceResource.js";
+var Notify = require("pc-client-component").Notify;
+var Common = require("../common/Common.js");
+var DynamicJsLoader = require("../common/DynamicJsLoader.js");
+var UpladFile = require("../widget/UpladFile.js");
+import TraceCommon from "./TraceCommon.js";
+var DateWidget = require("pc-client-component").Date;
+var Navbar = require("pc-client-component").Navbar;
+var Loading = require("pc-client-component").Loading;
+import Treeselect from "../widget/TreeSelect.vue";
+var AudioField = require("../widget/AudioField.vue").default;
+export default {
+    data: function () {
+        return {
+            traceId: "",
+            traceDto: {},
+            className: "com.leanwo.prodog.Trace",
+            projectItemUserListUser: [],
+            projectItemUserListProject: [],
+            formatDate: TraceCommon.formatDate,
+            summernoteInitSuccess: false, // summernote初始化成功
+            projectTaskDtos:[],//项目任务时间点
+            isflag:true,
+            srcArray:[]
+        }
+    },
+    components: {
+        Navbar,
+        Loading,
+        Common,
+        Notify,
+        DynamicJsLoader,
+        UpladFile,
+        DateWidget,
+        Treeselect,
+        AudioField
+    },
+    methods: {
+    	/**
+    	 * 删除语音
+    	 * @param {Object} index
+    	 */
+    	deleteAudioSrc:function(index){
+			var _self = this;
+			_self.srcArray.splice(index,1);
+			 _self.traceDto.audioSrcs = JSON.stringify(_self.srcArray);
+		},
+    	/**
+    	 * 录音停止之后
+    	 * @param {Object} param
+    	 */
+    	handelEndRecord: function(param) {
+    		var _self = this;
+		     if(param.fileName != undefined){
+		     	_self.srcArray.push(param.fileName);
+			    _self.traceDto.audioSrcs = JSON.stringify(_self.srcArray);
+		     }
+		},
+        back: function () {
+            history.back();
+        },
+
+        updateTrace: function () {
+            var _self = this;
+            _self.traceDto.detail = $('#summernote').summernote('code');
+            _self.traceDto.projectTaskId = _self.$refs.treeSelect.getValue();
+            if (_self.traceDto.summary != "" && _self.traceDto.summary != undefined) {
+                _self.$refs.loading.show();
+                $.ajax({
+                    url: Common.getApiURL('TraceResource/updateTrace'),
+                    type: 'post',
+                    contentType: "application/json",
+                    data: JSON.stringify(_self.traceDto),
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    success: function (data) {
+                        Notify.success("提示", "修改成功!");
+                        _self.$refs.loading.hide();
+                        _self.back();
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        _self.$refs.loading.hide();
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+            } else {
+                Notify.error("提示", "标题不能为空!");
+            }
+        },
+
+        //图片上传  
+        sendFile: function (file, editor, $editable) {
+            var _self = this;
+            UpladFile.photoCompress(file, {
+                quality: 0.2
+            }, function (base64Codes) {
+                var bl = UpladFile.convertBase64UrlToBlob(base64Codes);
+                var rst = new FormData();
+                rst.append("上传图片", bl, "file_" + Date.parse(new Date()) + ".jpg");
+                rst.append("className", _self.className);
+                $.ajax({
+                    url: Common.getApiURL("file/imageUpload"),
+                    type: "post",
+                    data: rst,
+                    contentType: false,
+                    processData: false,
+                    beforeSend : function(request){
+						Common.addTokenToRequest(request);
+					},
+                    success: function (data) {
+                        if (data != "error") {
+                            var imageName = data.substring(data.indexOf(":") + 1);
+                            // 方法1: 直接插入图片,由于图片不能加载,导致导致插入失败。
+                            // $("#summernote").summernote('insertImage', Common.getResourceUrl("image", _self.className, imageName), 'image name');
+
+                            // 方法2:创建img节点,然后插入<img>节点,可以插入,但是图片没有显示,点击一下code,然后就显示了。
+                            // var node = document.createElement('img') // 创建节点,用于包裹视频,再加这一层是方便调视频样式
+                            // node.setAttribute("authSrc", Common.getResourceUrl("image", _self.className, imageName));
+                            // node.setAttribute("is", "auth-img");
+                            // $("#summernote").summernote('insertNode', node);
+
+                            // 方法3:重新给code赋值,此时丢失了鼠标焦点,此时不满足要求。
+                            // var get_code = $('#summernote').summernote('code');
+                            // $('#summernote').summernote('code', get_code);
+
+                            // 方法4:插入文本(尝试失败)
+                            //let imageText = "<img is='auth-img' authSrc='" + Common.getResourceUrl("image", _self.className, imageName) + "'/>";
+                            //$('#summernote').summernote('insertText', imageText);
+
+                            // 方法5: 插入div,div中嵌入img,测试OK
+//                          var node = document.createElement('div') // 创建节点,用于包裹视频,再加这一层是方便调视频样式
+//                          node.innerHTML = "<img is='auth-img' authSrc='" + Common.getResourceUrl("image", _self.className, imageName) + "' />";
+//                          $("#summernote").summernote('insertNode', node);
+							$("#summernote").summernote('insertImage', Common.getResourceUrl("image", _self.className, imageName), 'image name');
+                        }
+                    },
+                    error: function () {
+                        Notify.error("提示", "上传失败!");
+                        return;
+                    }
+                });
+                return rst.file;
+            });
+        },
+
+        /**
+         * 初始化项目
+         */
+        initProject: function () {
+            var _self = this;
+            _self.projectItemUserListProject.splice(0, _self.projectItemUserListProject.length);
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/listByUserId'),//通过用户Id查找有权操作的项目
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    for (var index = 0; index < data.length; index++) {
+                        _self.$set(_self.projectItemUserListProject, index, data[index]);
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            })
+        },
+
+
+        /**
+         * 项目选择
+         * @author ZhangTeng 20190213
+         */
+        changeProjectSelected: function () {
+            var _self = this;
+            var count = 0;
+            _self.projectItemUserListUser.splice(0, _self.projectItemUserListUser.length);
+
+            if (_self.traceDto == null || _self.traceDto.projectId == null) {
+                return;
+            }
+
+            $.ajax({
+                url: Common.getApiURL('ProjectItemUserResource/listByProjectItemId'),//通过项目Id查找有权操作的项目的用户
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: { "projectItemId": _self.traceDto.projectId },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    for (var index = 0; index < data.length; index++) {
+                        _self.$set(_self.projectItemUserListUser, index, data[index]);
+                    }
+                },
+                error: function (XMLHttpRequest, textStatus, errorThrown) {
+                    Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                }
+            })
+        },
+
+
+        //日期选择
+        planFinishedDateChanged: function (value) {
+            if(value == ""){
+                this.traceDto.planFinishedDate = null;
+            }else{
+                this.traceDto.planFinishedDate = value
+            }            
+        },
+
+        //初始化数据
+        initContext: function () {
+            var _self = this;
+            _self.traceId = this.$route.params.traceId;
+            if (_self.traceId != null && _self.traceId != '') {
+                TraceResource.uniqueByTraceId(_self.traceId).then(successData => {
+                    _self.traceDto = successData;
+                    if(successData != undefined && successData.audioSrcs != undefined){
+                    	_self.srcArray = JSON.parse(successData.audioSrcs); 
+                    }
+                    _self.changeProjectSelected();
+                    DynamicJsLoader.loadSummernote(function () {
+                        $('#summernote').summernote({
+                        	toolbar:[
+						        ['insert', ['link', 'picture', 'video', 'audio', 'hr', 'table', //插件
+						        'fontname', 'fontsize', 'color', 'bold', 'italic', 'underline', 'strikethrough', 'clear',//字体样式
+						        'style', 'ol', 'ul', 'paragraph', 'height',//段落样式
+						        'fullscreen', 'codeview', 'undo', 'redo', 'help'//Misc
+						        ]],
+						    ],
+                            height: 400,
+                            minHeight: 300,
+                            maxHeight: 500,
+                            focus: true,
+                            lang: 'zh-CN',
+                            callbacks: {
+                                onImageUpload: function (files, editor, $editable) {
+                                    _self.sendFile(files[0], editor, $editable);
+                                }
+                            }
+                        });
+                        if (_self.traceDto != null) {
+                            $('#summernote').summernote('code', _self.traceDto.detail);
+                        }
+                        _self.summernoteInitSuccess = true;
+                    })
+                }, errorData => {
+                    Common.processException(errorData);
+                });
+            }
+        },
+        /**
+         * 根据项目id查询项目任务时间节点
+         * @author GuoZhiBo 20191010
+         */
+        queryProjectTaskDtoByProjectId: function(){
+			var _self = this;
+			$.ajax({
+				url: Common.getApiURL('ProjectTaskResource/queryProjectTaskDtoByProjectId'),
+				type: 'get',
+				dataType: 'json',
+				contentType: 'application/json',
+				data: {
+					projectId: _self.traceDto.projectId
+				},
+				beforeSend: function(request) {
+					Common.addTokenToRequest(request);
+				},
+				success: function(data) {
+					_self.projectTaskDtos = data;
+				},
+				error: function(XMLHttpRequest, textStatus, errorThrown) {
+					Common.processException(XMLHttpRequest, textStatus, errorThrown);
+				}
+			});
+		},
+    },
+
+    mounted: function () {
+        var _self = this;
+        this.initProject();
+        this.initContext();
+        this.isflag = false;
+    },
+
+    
+    destroyed: function(){
+        if(this.summernoteInitSuccess == true){
+            $('#summernote').summernote('destroy');
+        }
+        //界面销毁时,修改插入连接的display属性
+        $('.note-link-popover').removeAttr('style');
+    },
+    watch: {
+		"traceDto.projectId" : function(curVal, oldVal){
+			var _self = this;
+			_self.queryProjectTaskDtoByProjectId();
+			if(_self.isflag){
+				_self.$refs.treeSelect.setValue(undefined);
+			}else{
+				_self.$refs.treeSelect.setValue(_self.traceDto.projectTaskId);
+			}
+			_self.isflag = true;
+		}
+	}
+}
+</script>
+
+<style scoped>
+</style>

+ 166 - 0
src/trace/UseFinishedTrace.vue

@@ -0,0 +1,166 @@
+<template>
+    <div>
+        <Navbar :title='"团队-" + userName + " 已完成的任务" + "(" + pagination.total + ")"'
+                :isGoBack="true"></Navbar>
+
+        <p class="bg-danger"
+           style="padding: 15px;"
+           v-if="traces == null || traces.length == 0">未完成任何任务</p>
+
+        <div v-else
+             class="panel panel-default"
+             style="margin-bottom: 10px;">
+            <div class="panel-body">
+            	<input type="text" class="form-control" v-model="content" placeholder="任务名称、任务内容" @blur="initData" @keyup.enter="initData">
+                <div v-for="trace in traces"
+                     :key="trace.id"
+                     style="margin-top: 5px; cursor: pointer;">
+                    <span>
+                        <span>
+                            <Checkbox :id="'trace-finish-' + trace.id"
+                                      class-name="terms"
+                                      v-model="trace.finished"
+                                      class="trace-checkbox"
+                                      @input="updateTracefinished(trace)">
+                            </Checkbox>
+                        </span>
+                        <span @click="openLine(trace)">
+                            <span v-html="trace.summary"
+                                  class="trace-summary"
+                                  :class="{'font-color': trace.timeLineCompletion==true}">
+                            </span>
+                            <span class="glyphicon glyphicon-option-vertical trace-icon"
+                                  aria-hidden="true"></span>
+                            <span class="label label-primary trace-user">
+                                <span v-html="trace.receiveUserName"></span>
+                                <span>{{formatDate(trace.planFinishedDate)}}</span>
+                            </span>
+                            <span class="badge">
+                                <span>{{trace.projectName}}</span>
+                            </span>
+                            <span class="badge">
+                                <span>完成时间:{{formatDate(trace.finishedDate)}}</span>
+                            </span>
+                        </span>
+                    </span>
+                </div>
+            </div>
+        </div>
+        <Pagination :pagination="pagination"
+                    :callback="initData"
+                    
+                    v-if="traces.length">
+        </Pagination>
+    </div>
+</template>
+
+<script>
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+import TraceCommon from "./TraceCommon.js";
+var Checkbox = require("pc-client-component").Checkbox;
+import TraceResource from "./TraceResource.js";
+var Navbar = require("pc-client-component").Navbar;
+var Pagination = require("vue-bootstrap-pagination").default;
+
+export default {
+    data: function () {
+        return {
+            traceNumber: {},
+            uuid: Uuid.createUUID(),
+            userId: '',
+            userName: '',
+            traces: [],
+            formatDate: TraceCommon.formatDate,
+            pagination: {
+                total: 0,
+                per_page: 50,    // 每页10条信息
+                current_page: 1, // 当前页码
+                last_page: 10,    // 最后页码
+            },
+            content: undefined
+        }
+    },
+    components: {
+        Common, Uuid, Navbar, Checkbox, TraceResource, TraceCommon, Pagination
+    },
+    methods: {
+
+        /**
+         * 初始化人员
+         */
+        initData: function () {
+            var _self = this;
+            _self.traces.splice(0, _self.traces.length);
+            _self.userId = _self.$route.params.userId;
+            _self.userName = _self.$route.query.userName;
+            $.ajax({
+                url: Common.getApiURL('TraceResource/useFinishedTrace'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "receiveUserId": _self.userId,
+                    "currentPage": _self.pagination.current_page,
+                    "pageSize": _self.pagination.per_page,
+                    "content": _self.content 
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.pagination.total = data.totalSize;
+                    _self.pagination.last_page = Math.ceil(_self.pagination.total / _self.pagination.per_page);
+                    _self.traces = data.traceDtos;
+                },
+            });
+        },
+
+        /**
+         * 打开明细
+         * @author ZhangTeng 2019131
+         */
+        openLine: function (obj) {
+            this.$router.push({ 
+                path: '/desktop/trace/' + obj.id 
+            });
+        },
+        /**
+    * 修改追踪单的状态
+    * @author YangZhiJie 20171201
+    */
+        updateTracefinished: function (trace) {
+            var _self = this;
+            TraceResource.updateTraceFinished(trace.id).then(successData => {
+                _self.initData();
+            }, errorData => {
+                Common.processException(errorData);
+            });
+        },
+
+    },
+    mounted: function () {
+        this.initData();
+    }
+}
+</script>
+
+<style scoped>
+.trace-summary {
+    font-size: large;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.trace-icon {
+    opacity: 0.5;
+}
+
+.trace-user {
+    font-size: 100%;
+}
+.trace-checkbox {
+    display: inline;
+    font-size: large;
+}
+</style>

+ 238 - 0
src/trace/UserNotFinishedTrace.vue

@@ -0,0 +1,238 @@
+<template>
+    <div>
+        <Navbar :title='"团队-" + userName + " 待完成任务" + "(" + pagination.total + ")"'
+                :isGoBack="true">
+
+        </Navbar>
+
+        <div>
+            <button @click="finished"
+                    type="button"
+                    class="btn btn-link">
+                查看已完成的任务
+            </button>
+        </div>
+
+        <p class="bg-danger"
+           style="padding: 15px;"
+           v-if="traces == null || traces.length == 0">未安排任何任务
+        </p>
+
+        <div v-else
+             class="panel panel-default"
+             style="margin-bottom: 10px;">
+            <div class="panel-body">
+            	<input type="text" class="form-control" v-model="content" placeholder="任务名称、任务内容" @blur="initData" @keyup.enter="initData">
+                <div v-for="trace in traces"
+                     :key="trace.id"
+                     style="margin-top: 5px; cursor: pointer;">
+                    <span>
+                        <span>
+                            <Checkbox :id="'trace-finish-' + trace.id"
+                                      class-name="terms"
+                                      v-model="trace.finished"
+                                      class="trace-checkbox"
+                                      @input="updateTracefinished(trace)"
+                                      style=""></Checkbox>
+                        </span>
+                        <span @click="openLine(trace)">
+                            <span v-html="trace.summary"
+                                  class="trace-summary"
+                                  :class="{'font-color': trace.timeLineCompletion==true}">
+                            </span>
+                            <span class="glyphicon glyphicon-option-vertical trace-icon"
+                                  aria-hidden="true"></span>
+                            <span class="label trace-user"
+                                  :class="{'label-danger' : trace.overdue == true, 'label-primary' : trace.overdue != true}">
+                                <span v-html="trace.receiveUserName"></span>
+                                <span>{{formatDate(trace.planFinishedDate)}}</span>
+                            </span>
+                            <span class="badge">
+                                <span>{{trace.projectName}}</span>
+                            </span>
+                        </span>
+                    </span>
+                </div>
+            </div>
+        </div>
+
+        <Pagination :pagination="pagination"
+                    :callback="initData"
+                    
+                    v-if="traces.length">
+        </Pagination>
+
+    </div>
+</template>
+
+<script>
+var Common = require("../common/Common.js");
+var Uuid = require("pc-client-component").Uuid;
+import TraceCommon from "./TraceCommon.js";
+import TraceResource from "./TraceResource.js";
+
+var Checkbox = require("pc-client-component").Checkbox;
+var Navbar = require("pc-client-component").Navbar;
+var Pagination = require("vue-bootstrap-pagination").default;
+
+export default {
+    data: function () {
+        return {
+            uuid: Uuid.createUUID(),
+            userId: '',
+            userName: '',
+            traces: [],
+            formatDate: TraceCommon.formatDate,
+            pagination: {
+                total: 0,
+                per_page: 50,    // 每页10条信息
+                current_page: 1, // 当前页码
+                last_page: 10,    // 最后页码
+            },
+            content:undefined,
+        }
+    },
+    components: {
+        Common, Uuid, Navbar, TraceCommon, TraceResource, Checkbox, Pagination
+    },
+    methods: {
+
+        /**
+         * 打开明细
+         * @author ZhangTeng 2019131
+         */
+        openLine: function (obj) {
+            this.$router.push({ 
+                path: '/desktop/trace/' + obj.id 
+            });
+        },
+        
+        /**
+         * 初始化人员
+         */
+        initData: function () {
+            var _self = this;
+            _self.traces.splice(0, _self.traces.length);
+            _self.userId = _self.$route.params.userId;
+            _self.userName = _self.$route.query.userName;
+            $.ajax({
+                url: Common.getApiURL('TraceResource/userNotFinishedTrace'),
+                type: 'get',
+                dataType: 'json',
+                contentType: "application/json",
+                data: {
+                    "receiveUserId": _self.userId,
+                    "currentPage": _self.pagination.current_page,
+                    "pageSize": _self.pagination.per_page,
+                    "content": _self.content
+                },
+                beforeSend: function (request) {
+                    Common.addTokenToRequest(request);
+                },
+                success: function (data) {
+                    _self.pagination.total = data.totalSize;
+                    _self.pagination.last_page = Math.ceil(_self.pagination.total / _self.pagination.per_page);
+                    _self.traces = data.traceDtos;
+                },
+            });
+        },
+        /**
+		* 修改追踪单的状态
+		* @author YangZhiJie 20171201
+		*/
+        updateTracefinished: function (trace) {
+            var _self = this;
+            TraceResource.updateTraceFinished(trace.id).then(successData => {
+                _self.initData();
+            }, errorData => {
+                Common.processException(errorData);
+            });
+        },
+
+        finished: function () {
+            var _self = this;
+            this.$router.push({
+                path: '/desktop/useFinishedTrace/' + _self.userId,
+                query: {
+                    userName: _self.userName,
+                }
+            });
+        },
+    },
+
+    mounted: function () {
+        this.initData();
+
+    }
+}
+</script>
+
+<style scoped>
+.boxes .box {
+    display: block;
+    width: 90px;
+    height: 235px;
+    border-radius: 4px;
+    text-align: center;
+    color: #000;
+}
+.boxes .box__statistics {
+    display: block;
+    width: 90px;
+    height: 90px;
+    padding: 10px;
+    margin: 15px auto 0;
+    border-radius: 50%;
+    color: #fff;
+    font-size: 46px;
+}
+.boxes .box__statistics--upcoming2 {
+    background-color: #f6aa39;
+}
+a {
+    text-decoration: none;
+    margin: 10px;
+    font-size: 100%;
+    vertical-align: baseline;
+    background: transparent;
+}
+.boxes .box__title {
+    margin-top: 25px;
+    font-size: 24px;
+}
+.boxes .box__title__weeks {
+    font-size: 18px;
+}
+.box:hover {
+    background-color: ghostwhite;
+    width: 90px;
+    height: 235px;
+}
+.row {
+    height: 200px;
+    margin-bottom: 10px;
+}
+.dashboard-header {
+    cursor: pointer;
+    font-family: "\5FAE\8F6F\96C5\9ED1";
+    font-weight: bold;
+    font-size: 1.1em;
+}
+.trace-summary {
+    font-size: large;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.trace-icon {
+    opacity: 0.5;
+}
+
+.trace-user {
+    font-size: 100%;
+}
+.trace-checkbox {
+    display: inline;
+    font-size: large;
+}
+</style>

+ 294 - 0
src/widget/AudioField.vue

@@ -0,0 +1,294 @@
+<!--
+	作者:GuoZhiBo
+	时间:2020-05-19
+	描述:录音和播放语音插件
+-->
+<template>
+    <div>
+        <div v-if="isReadonly == 'false'" style="margin-bottom: 10px;">
+            <button type="button"
+                    class="btn btn-info"
+                    @mousedown.prevent="mouseStart">
+                <span class="glyphicon glyphicon-volume-up"
+                      aria-hidden="true"></span>
+                {{ tiptext }}
+            </button>
+        </div>
+        <template v-if="srcArray.length > 0">
+            <div style="margin-bottom: 10px; display: inline-flex; align-items: center"
+                 v-for="(item,index) in srcArray"
+                 :key="index">
+                <AuthAudio :audioSrc="getUrl(item)"
+                           :token="token"></AuthAudio>
+                <button type="button"
+                        v-if="isReadonly == 'false'"
+                        class="btn btn-link"
+                        style="flex: 1"
+                        @click="deleteAudioSrc(index)">
+                    <span class="glyphicon glyphicon-remove"
+                          aria-hidden="true">
+                    </span> 删除
+                </button>
+
+                <div class="clearfix"></div>
+            </div>
+        </template>
+    </div>
+</template>
+
+<script type="text/javascript">
+var Common = require("../common/Common.js");
+import recording from './recorder.js'
+var AuthAudio = require("./AuthAudio.vue").default;
+export default {
+    name: 'MRecorder',
+    props: ["className", "srcArray", "token", "isReadonly"],
+    data() {
+        return {
+            src: null,
+            mcounter: 0, // 累积时间
+            recording: false, // 标记是否在录音
+            intervaltimerid: '', // 间隔时间定时器编号
+            tiptext: '按住说话', // 提示文字
+            mediaRecorder: null, // 录音笔
+            mediaConstraints: {
+                audio: true
+            },
+            isTouchEnd: false, //是否移动结束事件
+            num: 60, // 按住说话时间
+            recorder: null,
+            interval: '',
+            audioFileList: [], // 上传语音列表
+            startTime: '', // 语音开始时间
+            endTime: '', // 语音结束
+            flag:true
+        }
+    },
+    components: {
+        AuthAudio
+    },
+    methods: {
+        getUrl: function (item) {
+            var _self = this;
+            var url = Common.getApiURL('file/audioDownload') + "?className=" + _self.className
+                + "&fileName=" + item;
+            return url;
+        },
+        /**
+         * 清除定时器
+         */
+        clearTimer: function () {
+            if (this.interval) {
+                this.num = 60;
+                clearInterval(this.interval);
+            }
+        },
+        /**
+         * 长按说话
+         */
+        mouseStart: function() {
+			var _self = this;
+			this.clearTimer();    
+			this.startTime = new Date().getTime();   
+			recording.get((rec) => {     // 当首次按下时,要获取浏览器的麦克风权限,所以这时要做一个判断处理
+				if(rec) {      
+					if(this.flag) {
+						this.recorder = rec;
+						this.interval = setInterval(() => {  
+							this.mcounter = this.mcounter + 1;
+							this.tiptext = '录音中 ' + this.mcounter + 's';     
+							if(this.num <= 0) {        
+								rec.stop();         
+								this.num = 60;         
+								this.clearTimer();       
+							} else {        
+								this.num--;        
+								this.time = '松开结束(' + this.num + '秒)';        
+								rec.start();       
+							}       
+						}, 1000);  
+						this.flag = false;  
+					} else { 
+						this.flag = true;   
+						this.clearTimer();   
+						this.endTime = new Date().getTime();  
+						this.recorder.stop();
+						rec.stop();  
+						// 重置说话时间
+						this.tiptext = '按住说话';
+						if(this.mcounter > 0){
+							this.num = 60;
+							// 获取语音二进制文件
+							let blob = this.recorder.getBlob();    
+							this.audioUpload(blob);   
+							this.mcounter = 0;
+						}   
+					}    
+				}   
+			})  
+		},
+        /**
+         * 松开时上传语音
+         */
+        mouseEnd: function () {
+            var _self = this;
+            this.clearTimer();
+            this.endTime = new Date().getTime();
+            if (this.recorder) {
+                this.recorder.stop();
+                this.num = 60;
+                // 重置说话时间
+                this.tiptext = '按住说话';
+                // 获取语音二进制文件
+                let blob = this.recorder.getBlob();
+                this.audioUpload(blob);
+            }
+        },
+        /**
+         * 删除语音
+         * @param {Object} index
+         */
+        deleteAudioSrc: function (index) {
+            var _self = this;
+            _self.$emit('deleteAudioSrc', index);
+        },
+        /**
+         * 长按
+         */
+        longTap: function () {
+            var _self = this;
+            _self.mouseStart();
+            console.log("开始录音");
+        },
+        /**
+         * 移动
+         */
+        touchMove: function () {
+            var _self = this;
+            console.log("取消录音");
+            _self.isTouchEnd = true;
+            _self.mouseEnd();
+        },
+        /**
+         * 结束
+         */
+        touchEnd: function () {
+            var _self = this;
+            console.log("结束录音");
+            _self.mouseEnd();
+        },
+        /**
+         * 上传语音
+         * @param {Object} blob
+         */
+        audioUpload: function (blob) {
+            var fileName;
+            var _self = this;
+            if (_self.isTouchEnd == false) {
+                // 将获取的二进制对象转为二进制文件流
+                let file = new File([blob], 'test.mp3', {
+                    type: 'audio/mp3',
+                    lastModified: Date.now()
+                })
+
+                // create FormData
+                var formData = new FormData();
+                formData.append('fileName', file.name);
+                formData.append('video-blob', file);
+                formData.append("className", _self.className);
+                $.ajax({
+                    url: Common.getApiURL("file/audioUpload"),
+                    type: "post",
+                    beforeSend: function (request) {
+                        Common.addTokenToRequest(request);
+                    },
+                    data: formData,
+                    contentType: false,
+                    processData: false,
+                    success: function (data) {
+                        if (data != "error") {
+                            fileName = data.substring(data.indexOf(":") + 1);
+                            const url = URL.createObjectURL(blob);
+                            _self.$emit('handleStop', {
+                                url: url,
+                                mblob: blob,
+                                fileName: fileName
+                            })
+                        }
+                    },
+                    error: function (XMLHttpRequest, textStatus, errorThrown) {
+                        Common.processException(XMLHttpRequest, textStatus, errorThrown);
+                    }
+                });
+                console.log("录音成功:" + fileName);
+            } else {
+                console.log("取消录音");
+            }
+            _self.isTouchEnd = false;
+        }
+    },
+    destroyed: function(){
+       if(this.recorder) {  
+			this.recorder.stop(); 
+			this.clearTimer();  
+		}  
+    },
+}
+</script>
+<style scoped>
+.wrapper {
+    text-align: center;
+}
+
+.mrecorder {
+    width: 40px;
+    height: 40px;
+    font-size: 40px;
+}
+
+.anirecorder {
+    position: relative;
+    animation: mymove 5s infinite;
+    -webkit-animation: mymove 5s infinite;
+    animation-direction: alternate;
+    animation-timing-function: ease-in-out;
+    /*safari & chrome*/
+    -webkit-animation-direction: alternate;
+    -webkit-transition-timing-function: ease-in-out;
+}
+
+@keyframes mymove {
+    0% {
+        transform: scale(1);
+        /*开始为原始大小*/
+    }
+    25% {
+        transform: scale(1.1);
+        /*放大1.1倍*/
+    }
+    50% {
+        transform: scale(0.9);
+    }
+    75% {
+        transform: scale(1.1);
+    }
+}
+
+@-webkit-keyframes mymove
+	/*Safari and Chrome*/ {
+    0% {
+        transform: scale(1);
+        /*开始为原始大小*/
+    }
+    25% {
+        transform: scale(1.1);
+        /*放大1.1倍*/
+    }
+    50% {
+        transform: scale(0.9);
+    }
+    75% {
+        transform: scale(1.1);
+    }
+}
+</style>

+ 51 - 0
src/widget/AuthAudio.vue

@@ -0,0 +1,51 @@
+<template>
+    <audio :src="src"
+           controls="controls"></audio>
+</template>
+<script type="text/javascript">
+
+module.exports = {
+    props: ['audioSrc', "token", "className"],
+
+    data: function () {
+        return {
+            "src": "",
+        }
+    },
+    methods: {
+        loadAudio: function () {
+            let _self = this;
+            let request = new XMLHttpRequest();
+            request.responseType = 'blob';
+            request.open('get', _self.audioSrc, true);
+            request.setRequestHeader('token', (_self.token == null) ? localStorage.getItem("token") : _self.token);
+            request.onload = function (e) {
+                if (this.status == 200) {
+                    _self.src = URL.createObjectURL(this.response);
+                }
+            };
+            request.send();
+        },
+
+        /**
+         * 语音加载完毕,调用本方法,释放通过URL.createObjectURL()创建的对象URL
+         */
+        onload: function () {
+            let _self = this;
+            URL.revokeObjectURL(_self.src);
+        }
+    },
+
+    watch: {
+        "audioSrc": function (newValue, oldValue) {
+            if (newValue != oldValue) {
+                this.loadAudio(newValue);
+            }
+        },
+    },
+
+    mounted: function () {
+        this.loadAudio(this.audioSrc);
+    }
+}
+</script>

+ 472 - 0
src/widget/GanttScale2.vue

@@ -0,0 +1,472 @@
+<template>
+	<div>
+		<div class="radio">
+			<label class="radio-inline"></label>
+			<div class="btn-group btn-link pull-left">
+				<button type="button" v-if="canEdit" class="btn btn-default fa fa-floppy-o" @click="runSaveGantt">&nbsp;保存</button>
+				<button type="button" class="btn btn-default fa fa-refresh" @click="showTaskDtos">&nbsp;刷新</button>
+				<button type="button" class="btn btn-default fa fa-search-plus" @click="scale++" :disabled="disableEnlarge">&nbsp;放大</button>
+				<button type="button" class="btn btn-default fa fa-search-minus" @click="scale--" :disabled="disableReduction">&nbsp;缩小</button>
+				<button type="button" class="btn btn-default fa fa-search" @click="setDefaultScale">&nbsp;默认</button>
+				<button v-if="hideButtons != true" type="button" class="btn btn-default" @click="showGanttChanged">
+					<span v-if="showGantt == 1">手动排程</span>
+					<span v-if="showGantt == 2">自动排程</span>
+				</button>
+				<button type="button" class="btn btn-default fa fa-undo" @click="undo">&nbsp;上一步</button>
+				<button type="button" class="btn btn-default fa fa-repeat" @click="redo">&nbsp;下一步</button>
+				<button type="button" class="btn btn-default fa fa-arrows-alt" id="fullscreen_button" @click="expand">&nbsp;全屏</button>
+				<button type="button" v-if="canEdit" class="btn btn-default" @click="importTemplate">导入模板</button>
+				<button type="button" class="btn btn-default" @click="exportFile">导出</button>
+			</div>
+			<!-- 			<div id="output">
+				选择的网站是: {{selected}}
+			</div> -->
+			<Modal ref="create" @cancel="$refs.create.show = false" large="true" @ok="importTest">
+				<template #header>
+					选择模板进行导入
+				</template>
+				<div>
+					<div class="form-horizontal">
+						<div class="form-group">
+							<label for="ganttName" class="col-xs-4 col-sm-3 col-md-2 col-lg-1 control-label">
+								<nobr>选择模板</nobr>
+							</label>
+							<div class="col-xs-8 col-sm-9 col-md-10 col-lg-11">
+								<select class="form-control" v-model="selected">
+									<option value="">选择一个模板</option>
+									<option v-for="template in templates">{{template}}</option>
+								</select>
+							</div>
+						</div>
+					</div>
+				</div>
+			</Modal>
+		</div>
+	</div>
+</template>
+
+
+<script>
+	var Modal = require("pc-client-component").Modal;
+	var Common = require("../common/Common.js");
+	var Notify = require("pc-client-component").Notify;
+
+
+	module.exports = {
+		/**
+		 * ganttDivId : 甘特图DIV的id
+		 */
+		props: ["ganttDivId", "hideIndex", "hideButtons"],
+
+		data: function() {
+			return {
+				disableEnlarge: false,
+				disableReduction: false,
+				scale: 1,
+				showGantt: 1, // 1:手动排版,2.自动排版
+				columnsTemp: [],
+				templates: [],
+				selected: '', //select选中的值
+				canEdit:false //是否可以编辑甘特图
+			};
+		},
+
+		components: {
+			Common,
+			Modal
+		},
+
+		methods: {
+			showGanttChanged: function() {
+				if (this.showGantt >= 2) {
+					this.showGantt = 1;
+				} else {
+					this.showGantt++;
+				}
+			},
+			importTemplate: function() {
+				this.$refs.create.show = true;
+
+			},
+			/**
+			 * 设置默认的标度
+			 */
+			setDefaultScale: function() {
+				this.setScaleConfig(1);
+			},
+			/**
+			 * 上一步
+			 */
+			undo: function() {
+				gantt.undo();
+			},
+			/**
+			 * 下一步
+			 */
+			redo: function() {
+				gantt.redo();
+			},
+
+			/**
+			 * 全屏
+			 */
+			expand: function() {
+				if (!gantt.getState().fullscreen) {
+					// expanding the gantt to full screen
+					gantt.expand();
+				} else {
+					// collapsing the gantt to the normal mode
+					gantt.collapse();
+				}
+			},
+			/**
+			 * 导出
+			 */
+			exportFile: function() {
+				gantt.exportToPDF();
+			},
+			/**
+			 * 设置标度
+			 */
+			setScaleConfig: function(value) {
+				if (value == 4) {
+					this.disableEnlarge = true;
+				} else {
+					this.disableEnlarge = false;
+				}
+				if (value == 0) {
+					this.disableReduction = true;
+				} else {
+					this.disableReduction = false;
+				}
+				switch (value) {
+					case 0:
+						gantt.config.scale_unit = "day";
+						gantt.config.date_scale = "%l, %F %d";
+						gantt.config.min_column_width = 20;
+						gantt.config.scale_height = 20 * 3;
+						gantt.templates.task_cell_class = function(task, date) {
+							if (date.getHours() == 8) {
+								return "day_start";
+							}
+							if (date.getHours() == 18) {
+								return "day_end";
+							}
+							return "";
+						};
+						var weekScaleTemplate = function(date) {
+							var dateToStr = gantt.date.date_to_str("%d %M");
+							var weekNum = gantt.date.date_to_str("(week %W)");
+							var endDate = gantt.date.add(
+								gantt.date.add(date, 1, "week"),
+								-1,
+								"day"
+							);
+							return (
+								dateToStr(date) + " - " + dateToStr(endDate) + " " + weekNum(date)
+							);
+						};
+						gantt.config.subscales = [{
+								unit: "week",
+								step: 1,
+								template: weekScaleTemplate
+							},
+							{
+								unit: "hour",
+								step: 1,
+								date: "%G"
+							}
+						];
+						break;
+					case 1:
+						gantt.config.scale_unit = "day";
+						gantt.config.step = 1;
+						gantt.config.date_scale = "%M %d日";
+						gantt.config.subscales = [];
+						gantt.config.scale_height = 27;
+						gantt.config.min_column_width = 50;
+						gantt.templates.date_scale = null;
+						break;
+					case 2:
+						var weekScaleTemplate = function(date) {
+							var dateToStr = gantt.date.date_to_str("%M %d日");
+							var startDate = gantt.date.week_start(new Date(date));
+							var endDate = gantt.date.add(
+								gantt.date.add(startDate, 1, "week"),
+								-1,
+								"day"
+							);
+							return dateToStr(startDate) + " - " + dateToStr(endDate);
+						};
+
+						gantt.config.scale_unit = "week";
+						gantt.config.step = 1;
+						gantt.templates.date_scale = weekScaleTemplate;
+						gantt.config.min_column_width = 50;
+						gantt.config.subscales = [{
+							unit: "day",
+							step: 1,
+							date: "%D"
+						}];
+						gantt.config.scale_height = 50;
+						break;
+					case 3:
+						gantt.config.scale_unit = "month";
+						gantt.config.date_scale = "%Y年%F";
+						gantt.config.subscales = [{
+							unit: "day",
+							step: 1,
+							date: "%j, 周%D"
+						}];
+						gantt.config.scale_height = 50;
+						gantt.config.min_column_width = 50;
+						gantt.templates.date_scale = null;
+						break;
+					case 4:
+						gantt.config.scale_unit = "year";
+						gantt.config.step = 1;
+						gantt.config.date_scale = "%Y年";
+						gantt.config.min_column_width = 50;
+						gantt.config.scale_height = 90;
+						gantt.templates.date_scale = null;
+						gantt.config.subscales = [{
+							unit: "month",
+							step: 1,
+							date: "%M"
+						}];
+						break;
+				}
+				gantt.render();
+			},
+			//获得登录用户权限
+			personnelJurisdictionSet: function() {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL('TraceResource/queryPersonnelJurisdiction'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": _self.$route.params.projectId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if (data == null) {
+							return;
+						}
+						if (data.levelOfPerson == 1) {
+							_self.canEdit = false
+						}
+						if (data.levelOfPerson == 2 || data.levelOfPerson == 3) {
+							_self.canEdit = true
+						}
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			},
+			//获得模板名
+			getTemplate: function() {
+				var _self = this;
+				$.ajax({
+					type: "get",
+					url: Common.getApiURL('ProjectTaskResource/queryAllTaskTemples'),
+					contentType: "application/json",
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(datas) {
+						_self.templates = datas;
+					},
+
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					},
+
+				});
+			},
+			//导入模板
+			importTest: function() {
+				var _self = this;
+				$.ajax({
+					url: Common.getApiURL("ProjectTaskResource/importTemplateByTimeJson"),
+					type: "get",
+					contentType: "application/json",
+					data: {
+						selected: _self.selected,
+						projectId: _self.$route.params.projectId
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						_self.showTaskDtos();
+						_self.selected = "";
+						Notify.success("提示", "导入成功", 1500);
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+			//保存数据
+			runSaveGantt: function() {
+				var _self = this;
+				var canEdit=_self.canEdit;
+				if(canEdit == false){
+					Notify.error("警告", "保存失败,该用户没有权限修改时间节点,请联系管理员分配权限", 1500);
+					return;
+				}
+				var ganttData = [];
+				var tasks = gantt.getTaskByTime();
+				for (var x = 0; x < tasks.length; x++) {
+					var globalTaskIndex = gantt.getGlobalTaskIndex(tasks[x].id);
+					var event = {
+						id: tasks[x].id,
+						parentId: tasks[x].parent == 0 ? undefined : tasks[x].parent,
+						name: tasks[x].text,
+						type: tasks[x].type,
+						projectItemId: _self.$route.params.projectId,
+						startDate: tasks[x].start_date,
+						endDate: tasks[x].end_date,
+						progress: tasks[x].progress,
+						sequenceNo: globalTaskIndex,
+						remarks: tasks[x].remarks,
+						owenUserId: tasks[x].owner_id
+					}
+					ganttData.push(event);
+				}
+				/* 				gantt.eachTask(item => {
+									
+								}); */
+				var links = gantt.getLinks();
+				var links2 = [];
+				for (var x = 0; x < links.length; x++) {
+					var event = {
+						id: links[x].id,
+						predecessorTaskId: links[x].source,
+						taskId: links[x].target,
+						type: links[x].type
+					}
+					links2.push(event);
+				}
+				var projectItemDto2 = {
+					projectId: _self.$route.params.projectId,
+					data: ganttData,
+					links: links2
+				}
+				$.ajax({
+					url: Common.getApiURL("ProjectTaskResource/saveTasksAndLinks"),
+					type: "post",
+					dataType: "json",
+					contentType: "application/json",
+					data: JSON.stringify(projectItemDto2),
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						Notify.success("提示", "保存成功", 1500);
+						_self.showTaskDtos();
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				});
+			},
+			//刷新数据
+			showTaskDtos: function() {
+				var _self = this;
+				var obj = _self.$route.params.projectId;
+				$.ajax({
+					url: Common.getApiURL('ProjectTaskResource/queryProjectAndTasks'),
+					type: 'get',
+					dataType: 'json',
+					contentType: "application/json",
+					data: {
+						"projectId": obj,
+					},
+					beforeSend: function(request) {
+						Common.addTokenToRequest(request);
+					},
+					success: function(data) {
+						if (data == null) {
+							return;
+						}
+						_self.title = data.projectname;
+						var projectTaskDtos = data.data;
+						var events = [];
+						var projectTaskRelationDtos = data.links;
+						var events2 = [];
+						for (var i = 0; i < projectTaskDtos.length; i++) {
+							var event = {
+								id: projectTaskDtos[i].id,
+								text: projectTaskDtos[i].name,
+								remarks: projectTaskDtos[i].remarks,
+								owner_id: projectTaskDtos[i].owenUserId,
+								start_date: projectTaskDtos[i].startDate,
+								duration: projectTaskDtos[i].duration,
+								parent: projectTaskDtos[i].parentId,
+								type: projectTaskDtos[i].type,
+								open: true,
+								progress: projectTaskDtos[i].progress,
+								index: projectTaskDtos[i].sequenceNo
+							}
+							events.push(event);
+						}
+						for (var i = 0; i < projectTaskRelationDtos.length; i++) {
+							var event = {
+								id: projectTaskRelationDtos[i].id,
+								source: projectTaskRelationDtos[i].predecessorTaskId,
+								target: projectTaskRelationDtos[i].taskId,
+								type: projectTaskRelationDtos[i].type
+							}
+							events2.push(event);
+						}
+						var tasks = {
+							data: events,
+							links: events2
+						}
+						gantt.clearAll();
+						gantt.parse(tasks);
+					},
+					error: function(XMLHttpRequest, textStatus, errorThrown) {
+						Common.processException(XMLHttpRequest, textStatus, errorThrown);
+					}
+				})
+			}
+		},
+		watch: {
+			scale: function(newValue, oldValue) {
+				this.setScaleConfig(newValue);
+			},
+			showGantt: function(newValue, oldValue) {
+				if (newValue == 1) {
+					gantt.config.undo = true;
+					gantt.config.redo = true;
+					gantt.config.auto_scheduling = true;
+					gantt.autoSchedule();
+				} else if (newValue == 2) {
+					gantt.config.undo = false;
+					gantt.config.redo = false;
+					gantt.config.auto_scheduling = false;
+				}
+			}
+		},
+		mounted: function() {
+			var _self = this;
+			_self.getTemplate();
+			_self.personnelJurisdictionSet();
+		}
+	};
+</script>
+
+<style scoped>
+.btn-focus {
+    background-color: #e6e6e6;
+    background-position: 0 32px;
+    color: #333;
+}
+</style>

+ 37 - 0
src/widget/MyProgress.vue

@@ -0,0 +1,37 @@
+<template>
+	<div class="box">
+		<div :style="{width: progress + '%'}">
+			{{progress + '%'}}
+		</div>
+	</div>
+</template>
+
+<script>
+	export default {
+		props: {
+			progress: {
+				type: Number,
+				default: 0
+			},
+		}
+
+	}
+</script>
+<style scoped>
+	.box {
+		height: 20px;
+		border-radius: 5px;
+		background-color: #f5f5f5;
+		box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+		overflow: hidden;
+	}
+	
+	.box div {
+		height: 100%;
+		background-color: #6666FF;
+		border-radius: 5px;
+		text-align: center;
+		color: #FFFFFF;
+		font-size: 0.9em;
+	}
+</style>

+ 66 - 0
src/widget/QueryWidget.vue

@@ -0,0 +1,66 @@
+<!--
+	作者:yangzhijie1488@163.com
+	时间:2017-12-07
+	描述:查询组件
+-->
+<template>
+	 <div class="m-container">
+	    <div class="input-group">
+	      <input type="text" v-model="searchText" class="form-control" @keyup.enter="search" :placeholder="placeholderText">
+	      <span class="input-group-btn">
+	        <button style="width:100%;"  @click="search()" type="button" class="btn btn-blue search-button">
+				查询
+			</button>
+	      </span>
+	    </div>
+	  </div>
+</template>
+
+<script>
+	export default {
+		props: ["placeholderText"],
+		
+		data: function(){
+			return {
+				"searchText": undefined
+			}
+		},
+		
+		methods: {
+			/**
+			 * 查询
+			 */
+			search: function(){
+				this.$emit("search", this.searchText);
+			},
+			
+			/**
+			 * 获取查询的文字
+			 */
+			getSearchText: function(){
+				return this.searchText;
+			},
+			
+			/**
+			 * 设置查询的文字
+			 * @param {Object} value
+			 */
+			setSearchText: function(value){
+				this.searchText = value;
+			}
+		},
+		
+		watch: {
+			"searchText" : function(curVal, oldVal){
+				this.$emit("valueChanged", curVal);
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	button{
+		background-color: #007aff;
+		color: white;
+	}
+</style>

+ 48 - 0
src/widget/TreeSelect.vue

@@ -0,0 +1,48 @@
+<template>
+	<div>
+		<treeselect v-model="value" :multiple="multiple" :options="options" placeholder = "请选择时间节点" noResultsText = "没有结果"/>
+	</div>
+</template>
+
+<script>
+	// import the component
+  	import Treeselect from '@riophae/vue-treeselect'
+    // import the styles
+    import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+	export default {
+		props: ["multiple","options"],
+		data: function(){
+			return {
+				"value": undefined
+			}
+		},
+		components: { 
+			Treeselect 
+		},
+		methods: {
+			/**
+			 * 获取选中的值
+			 */
+			getValue: function(){
+				return this.value;
+			},
+			
+			/**
+			 * 设置值
+			 * @param {Object} value
+			 */
+			setValue: function(value){
+				this.value = value;
+			}
+		},
+		
+		watch: {
+			"value" : function(curVal, oldVal){
+				this.$emit("valueChanged", curVal);
+			}
+		}
+	}
+</script>
+
+<style>
+</style>

+ 72 - 0
src/widget/UpladFile.js

@@ -0,0 +1,72 @@
+module.exports = {
+	/**
+	 *三个参数
+	 *  file:一个是文件(类型是图片格式),
+	 * w:一个是文件压缩的后宽度,宽度越小,字节越小
+	 *  objDiv:一个是容器或者回调函数
+	 *  photoCompress()
+	 */
+	photoCompress: function(file, w, objDiv) {
+		var _self = this;
+		var ready = new FileReader();
+		/*开始读取指定的Blob对象或File对象中的内容. 当读取操作完成时,readyState属性的值会成为DONE,如果设置了onloadend事件处理程序,则调用之.同时,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容.*/
+		ready.readAsDataURL(file);
+		ready.onload = function() {
+			var re = this.result;
+			_self.canvasDataURL(re, w, objDiv)
+		}
+	},
+	canvasDataURL: function(path, obj, callback) {
+		var img = new Image();
+		img.src = path;
+		img.onload = function() {
+			var that = this;
+			// 默认按比例压缩
+			var w = that.width,
+				h = that.height,
+				scale = w / h;
+			w = obj.width || w;
+			h = obj.height || (w / scale);
+			var quality = 0.7; // 默认图片质量为0.7
+			//生成canvas
+			var canvas = document.createElement('canvas');
+			var ctx = canvas.getContext('2d');
+			// 创建属性节点
+			var anw = document.createAttribute("width");
+			anw.nodeValue = w;
+			var anh = document.createAttribute("height");
+			anh.nodeValue = h;
+			canvas.setAttributeNode(anw);
+			canvas.setAttributeNode(anh);
+			ctx.drawImage(that, 0, 0, w, h);
+			// 图像质量
+			if(obj.quality && obj.quality <= 1 && obj.quality > 0) {
+				quality = obj.quality;
+			}
+			// quality值越小,所绘制出的图像越模糊
+			var base64 = canvas.toDataURL('image/jpeg', quality);
+			// 回调函数返回base64的值
+			callback(base64);
+		}
+	},
+
+	/**  
+	 * 将以base64的图片url数据转换为Blob  
+	 * @param urlData  
+	 *            用url方式表示的base64图片数据  
+	 */
+	convertBase64UrlToBlob: function(urlData) {
+		var arr = urlData.split(','),
+			mime = arr[0].match(/:(.*?);/)[1],
+			bstr = atob(arr[1]),
+			n = bstr.length,
+			u8arr = new Uint8Array(n);
+		while(n--) {
+			u8arr[n] = bstr.charCodeAt(n);
+		}
+		return new Blob([u8arr], {
+			type: mime
+		});
+	}
+
+}

+ 194 - 0
src/widget/recorder.js

@@ -0,0 +1,194 @@
+// 兼容
+window.URL = window.URL || window.webkitURL
+navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
+let HZRecorder = function (stream, config) {
+ config = config || {}
+ config.sampleBits = config.sampleBits || 8 // 采样数位 8, 16
+ config.sampleRate = config.sampleRate || (44100 / 6) // 采样率(1/6 44100)
+ let context = new (window.webkitAudioContext || window.AudioContext)()
+ let audioInput = context.createMediaStreamSource(stream)
+ let createScript = context.createScriptProcessor || context.createJavaScriptNode
+ let recorder = createScript.apply(context, [4096, 1, 1])
+ let audioData = {
+  size: 0, // 录音文件长度
+  buffer: [], // 录音缓存
+  inputSampleRate: context.sampleRate, // 输入采样率
+  inputSampleBits: 16, // 输入采样数位 8, 16
+  outputSampleRate: config.sampleRate, // 输出采样率
+  oututSampleBits: config.sampleBits, // 输出采样数位 8, 16
+  input: function (data) {
+   this.buffer.push(new Float32Array(data))
+   this.size += data.length
+  },
+  compress: function () { // 合并压缩
+   // 合并
+   let data = new Float32Array(this.size)
+   let offset = 0
+   for (let i = 0; i < this.buffer.length; i++) {
+    data.set(this.buffer[i], offset)
+    offset += this.buffer[i].length
+   }
+   // 压缩
+   let compression = parseInt(this.inputSampleRate / this.outputSampleRate)
+   let length = data.length / compression
+   let result = new Float32Array(length)
+   let index = 0; let j = 0
+   while (index < length) {
+    result[index] = data[j]
+    j += compression
+    index++
+   }
+   return result
+  },
+  encodeWAV: function () {
+   let sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate)
+   let sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits)
+   let bytes = this.compress()
+   let dataLength = bytes.length * (sampleBits / 8)
+   let buffer = new ArrayBuffer(44 + dataLength)
+   let data = new DataView(buffer)
+   let channelCount = 1// 单声道
+   let offset = 0
+   let writeString = function (str) {
+    for (let i = 0; i < str.length; i++) {
+     data.setUint8(offset + i, str.charCodeAt(i))
+    }
+   }
+   // 资源交换文件标识符
+   writeString('RIFF'); offset += 4
+   // 下个地址开始到文件尾总字节数,即文件大小-8
+   data.setUint32(offset, 36 + dataLength, true); offset += 4
+   // WAV文件标志
+   writeString('WAVE'); offset += 4
+   // 波形格式标志
+   writeString('fmt '); offset += 4
+   // 过滤字节,一般为 0x10 = 16
+   data.setUint32(offset, 16, true); offset += 4
+   // 格式类别 (PCM形式采样数据)
+   data.setUint16(offset, 1, true); offset += 2
+   // 通道数
+   data.setUint16(offset, channelCount, true); offset += 2
+   // 采样率,每秒样本数,表示每个通道的播放速度
+   data.setUint32(offset, sampleRate, true); offset += 4
+   // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
+   data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4
+   // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
+   data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2
+   // 每样本数据位数
+   data.setUint16(offset, sampleBits, true); offset += 2
+   // 数据标识符
+   writeString('data'); offset += 4
+   // 采样数据总数,即数据总大小-44
+   data.setUint32(offset, dataLength, true); offset += 4
+   // 写入采样数据
+   if (sampleBits === 8) {
+    for (let i = 0; i < bytes.length; i++ , offset++) {
+     let s = Math.max(-1, Math.min(1, bytes[i]))
+     let val = s < 0 ? s * 0x8000 : s * 0x7FFF
+     val = parseInt(255 / (65535 / (val + 32768)))
+     data.setInt8(offset, val, true)
+    }
+   } else {
+    for (let i = 0; i < bytes.length; i++ , offset += 2) {
+     let s = Math.max(-1, Math.min(1, bytes[i]))
+     data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
+    }
+   }
+   return new Blob([data], { type: 'audio/mp3' })
+  }
+ }
+ // 开始录音
+ this.start = function () {
+  audioInput.connect(recorder);
+  recorder.connect(context.destination);
+ }
+ // 停止
+ this.stop = function () {
+  recorder.disconnect();
+  var stream1 = stream.getTracks()[0];
+  stream1.stop();
+ }
+ // 获取音频文件
+ this.getBlob = function () {
+  this.stop();
+  return audioData.encodeWAV();
+ }
+ // 回放
+ this.play = function (audio) {
+  let downRec = document.getElementById('downloadRec')
+  downRec.href = window.URL.createObjectURL(this.getBlob())
+  downRec.download = new Date().toLocaleString() + '.mp3'
+  audio.src = window.URL.createObjectURL(this.getBlob())
+ }
+ // 上传
+ this.upload = function (url, callback) {
+  let fd = new FormData()
+  fd.append('audioData', this.getBlob())
+  let xhr = new XMLHttpRequest()
+  /* eslint-disable */
+  if (callback) {
+   xhr.upload.addEventListener('progress', function (e) {
+    callback('uploading', e)
+   }, false)
+   xhr.addEventListener('load', function (e) {
+    callback('ok', e)
+   }, false)
+   xhr.addEventListener('error', function (e) {
+    callback('error', e)
+   }, false)
+   xhr.addEventListener('abort', function (e) {
+    callback('cancel', e)
+   }, false)
+  }
+  /* eslint-disable */
+  xhr.open('POST', url)
+  xhr.send(fd)
+ }
+ // 音频采集
+ recorder.onaudioprocess = function (e) {
+  audioData.input(e.inputBuffer.getChannelData(0))
+  // record(e.inputBuffer.getChannelData(0));
+ }
+}
+// 抛出异常
+HZRecorder.throwError = function (message) {
+ alert(message)
+ throw new function () { this.toString = function () { return message } }()
+}
+// 是否支持录音
+HZRecorder.canRecording = (navigator.getUserMedia != null)
+// 获取录音机
+HZRecorder.get = function (callback, config) {
+ if (callback) {
+  if (navigator.getUserMedia) {
+   navigator.getUserMedia(
+    { audio: true } // 只启用音频
+    , function (stream) {
+     let rec = new HZRecorder(stream, config)
+     callback(rec)
+    }
+    , function (error) {
+     switch (error.code || error.name) {
+      case 'PERMISSION_DENIED':
+      case 'PermissionDeniedError':
+       HZRecorder.throwError('用户拒绝提供信息。')
+       break
+      case 'NOT_SUPPORTED_ERROR':
+      case 'NotSupportedError':
+       HZRecorder.throwError('浏览器不支持硬件设备。')
+       break
+      case 'MANDATORY_UNSATISFIED_ERROR':
+      case 'MandatoryUnsatisfiedError':
+       HZRecorder.throwError('无法发现指定的硬件设备。')
+       break
+      default:
+       HZRecorder.throwError('无法打开麦克风。异常信息:' + (error.code || error.name))
+       break
+     }
+    })
+  } else {
+   HZRecorder.throwErr('当前浏览器不支持录音功能。'); return
+  }
+ }
+}
+export default HZRecorder

File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/dhtmlxgantt.css


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/dhtmlxgantt.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/dhtmlxgantt.js.map


+ 1000 - 0
static/dhtmlxgantt/ext/api.js

@@ -0,0 +1,1000 @@
+(function(){
+
+var apiUrl = "https://export.dhtmlx.com/gantt";
+
+var templates = [ 
+	"leftside_text",
+	"rightside_text",
+	"task_text",
+	"progress_text",
+	"task_class"
+];
+
+function xdr(url, pack, cb){
+	if (gantt.env.isIE){
+		gantt.env.isIE = false;
+		gantt.ajax.post(url, pack, cb);		
+		gantt.env.isIE = true;
+	} else {
+		gantt.ajax.post(url, pack, cb);
+	}
+}
+
+function defaults(obj, std){
+	for (var key in std)
+		if (!obj[key])
+			obj[key] = std[key];
+	return obj;
+}
+
+//compatibility for new versions of gantt
+if(!gantt.ajax){
+	if(window.dhtmlxAjax){
+		gantt.ajax = window.dhtmlxAjax;
+	}else if(window.dhx4){
+		gantt.ajax = window.dhx4.ajax;
+	}
+}
+
+function mark_columns(base){
+	var columns = base.config.columns;
+	if (columns)
+		for (var i = 0; i < columns.length; i++) {
+			if (columns[i].template)
+				columns[i].$template = true;
+	}
+}
+
+
+function add_export_methods(gantt){
+	var color_box = null;
+	var color_hash = {};
+
+	function get_styles(css){
+		if (!color_box){
+			var color_box = document.createElement("DIV");
+			color_box.style.cssText = "position:absolute; display:none;";
+			document.body.appendChild(color_box);
+		}
+		if (color_hash[css])
+			return color_hash[css];
+
+		color_box.className = css;	
+		return (color_hash[css] = get_color(color_box, "color")+";"+get_color(color_box, "backgroundColor"));
+	}
+	
+	gantt._getWorktimeSettings = function() {
+
+		var defaultWorkTimes = {
+			hours : [0, 24],
+			dates : {0:true, 1:true, 2:true, 3:true, 4:true, 5:true, 6:true}
+		};
+
+		var time;
+		if(!gantt.config.work_time){
+			time = defaultWorkTimes;
+		}else{
+			var wTime = gantt._working_time_helper;
+			if (wTime && wTime.get_calendar) {
+				time = wTime.get_calendar();
+			} else if (wTime) {
+				time = {
+					hours : wTime.hours,
+					dates : wTime.dates
+				};
+			} else if (gantt.config.worktimes && gantt.config.worktimes.global) {
+				time = {
+					hours : gantt.config.worktimes.global.hours,
+					dates : gantt.config.worktimes.global.dates
+				};
+			} else {
+				time = defaultWorkTimes;
+			};
+		}
+
+		return time;
+	};
+
+	gantt.exportToPDF = function(config){
+		if (config && config.raw){
+			config = defaults(config, { 
+				name:"gantt.pdf", data:this._serialize_html()
+			});
+		} else {
+			config = defaults((config || {}), { 
+				name:"gantt.pdf",
+				data:this._serialize_all(),
+				config:this.config
+			});
+			fix_columns(gantt, config.config.columns);
+		}
+
+		config.version = this.version;
+		this._send_to_export(config, "pdf");
+	};
+	
+	gantt.exportToPNG = function(config){
+		if (config && config.raw){
+			config = defaults(config, { 
+				name:"gantt.png", data:this._serialize_html()
+			});
+		} else {
+			config = defaults((config || {}), { 
+				name:"gantt.png",
+				data:this._serialize_all(),
+				config:this.config
+			});
+			fix_columns(gantt, config.config.columns);
+		}
+
+		config.version = this.version;
+		this._send_to_export(config, "png");
+	};
+
+	gantt.exportToICal = function(config){
+		config = defaults((config || {}), { 
+			name:"gantt.ical",
+			data:this._serialize_plain().data,
+			version:this.version
+		});
+		this._send_to_export(config, "ical");
+	};
+	
+	function eachTaskTimed(start, end){
+		return function(code, parent, master){
+			parent = parent || this.config.root_id;
+			master = master || this;
+
+			var branch = this.getChildren(parent);
+			if (branch)
+				for (var i=0; i<branch.length; i++){
+					var item = this._pull[branch[i]];
+					if ((!start || item.end_date > start) && (!end || item.start_date < end))
+						code.call(master, item);
+					
+					if (this.hasChild(item.id))
+						this.eachTask(code, item.id, master);
+				}
+		};
+	}
+
+	gantt.exportToExcel = function(config){
+		config = config || {};
+
+		var tasks, dates;
+		var state, scroll;
+		if (config.start || config.end){
+			state = this.getState();
+			dates = [ this.config.start_date, this.config.end_date ];
+			scroll = this.getScrollState();
+			var convert = this.date.str_to_date(this.config.date_format);
+			tasks = this.eachTask;
+
+			if (config.start)
+				this.config.start_date = convert(config.start);
+			if (config.end)
+				this.config.end_date = convert(config.end);
+
+			this.render();
+			this.eachTask = eachTaskTimed(this.config.start_date, this.config.end_date);
+		}
+
+		this._no_progress_colors =config.visual === "base-colors";
+
+		config = defaults(config, { 
+			name:"gantt.xlsx",
+			title:"Tasks",
+			data:this._serialize_table(config).data,
+			columns:this._serialize_columns({ rawDates: true }),
+			version:this.version
+		});
+
+		if (config.visual)
+			config.scales = this._serialize_scales(config);
+
+		this._send_to_export(config, "excel");
+
+		if (config.start || config.end){
+			this.config.start_date = state.min_date;
+			this.config.end_date = state.max_date;
+			this.eachTask = tasks;
+
+			this.render();
+			this.scrollTo(scroll.x, scroll.y);
+
+			this.config.start_date = dates[0];
+			this.config.end_date = dates[1];
+		}
+	};
+
+	gantt.exportToJSON = function(config){
+		config = defaults((config || {}), { 
+			name:"gantt.json",
+			data:this._serialize_all(),
+			config: this.config,
+			columns:this._serialize_columns(),
+			worktime : gantt._getWorktimeSettings(),
+			version:this.version
+		});
+		this._send_to_export(config, "json");	
+	};
+
+	function sendImportAjax(config){
+		var url = config.server || apiUrl;
+		var store = config.store || 0;
+		var formData = config.data;
+		var callback = config.callback;
+
+		formData.append("type", "excel-parse");
+		formData.append("data", JSON.stringify({
+			sheet: config.sheet || 0
+		}));
+
+		if(store)
+			formData.append("store", store);
+
+		var xhr = new XMLHttpRequest();
+		xhr.onreadystatechange = function (e) {
+			if(xhr.readyState == 4 && xhr.status == 0){// network error
+				if(callback){
+					callback(null);
+				}
+			}
+		};
+
+		xhr.onload = function() {
+			var fail = xhr.status > 400;
+			var info = null;
+
+			if (!fail){
+				try{
+					info = JSON.parse(xhr.responseText);
+				}catch(e){}
+			}
+
+			if(callback){
+				callback(info);
+			}
+		};
+
+		xhr.open('POST', url, true);
+		xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+		xhr.send(formData);
+	}
+
+	gantt.importFromExcel = function(config){
+		var formData = config.data;
+
+		if(formData instanceof FormData){
+
+		}else if(formData instanceof File){
+			var data = new FormData();
+			data.append("file", formData);
+			config.data = data;
+		}
+		sendImportAjax(config);
+	};
+
+	gantt._msp_config = function(config){
+		
+		if (config.project)
+			for (var i in config.project){
+				if (!config._custom_data)
+					config._custom_data = {};
+				config._custom_data[i] = config.project[i](this.config);
+			}
+			
+		if (config.tasks)
+			for (var j = 0; j < config.data.length; j++){
+				var el = this.getTask(config.data[j].id);
+				if (!el._custom_data)
+					el._custom_data = {};
+				for (var i in config.tasks)
+					el._custom_data[i] = config.tasks[i](el, this.config);
+			}
+				
+		delete config.project;
+		delete config.tasks;
+
+		config.time = gantt._getWorktimeSettings();
+
+		var p_dates = this.getSubtaskDates();
+		var format = this.date.date_to_str("%d-%m-%Y %H:%i:%s");
+		config.start_end = {
+			start_date: format(p_dates.start_date),
+			end_date: format(p_dates.end_date)
+		};
+	};
+
+	gantt._msp_data = function(){
+		var old_xml_format = this.templates.xml_format;
+		var old_format_date = this.templates.format_date;
+		this.templates.xml_format = this.date.date_to_str("%d-%m-%Y %H:%i:%s");
+		this.templates.format_date = this.date.date_to_str("%d-%m-%Y %H:%i:%s");
+		
+		var data = this._serialize_all();
+
+		this.templates.xml_format = old_xml_format;
+		this.templates.format_date = old_format_date;
+		return data;
+	};
+
+	gantt._ajax_to_export = function(data, type, callback){
+		delete data.callback;
+
+		var url = data.server || apiUrl;
+		var pack = "type="+type+"&store=1&data="+encodeURIComponent(JSON.stringify(data));
+
+		var cb = function(loader){
+			var xdoc = loader.xmlDoc || loader;
+			var fail = xdoc.status > 400;
+			var info = null;
+
+			if (!fail){
+				try{
+					info = JSON.parse(xdoc.responseText);
+				}catch(e){}
+			}
+			callback(info);
+		};
+
+		xdr(url, pack, cb);
+	};
+
+	gantt._send_to_export = function(data, type){
+		var convert = this.date.date_to_str(this.config.date_format || this.config.xml_date);
+		if (data.config){
+			data.config = this.copy(data.config);
+			mark_columns(data, type);
+
+			if(data.config.start_date && data.config.end_date){
+				if(data.config.start_date instanceof Date){
+					data.config.start_date = convert(data.config.start_date)
+				}
+				if(data.config.end_date instanceof Date){
+					data.config.end_date = convert(data.config.end_date)
+				}
+			}
+		}
+
+		if (data.callback)
+			return gantt._ajax_to_export(data, type, data.callback);
+
+		var form = this._create_hidden_form();
+		form.firstChild.action = data.server || apiUrl;
+		form.firstChild.childNodes[0].value = JSON.stringify(data);
+		form.firstChild.childNodes[1].value = type;
+		form.firstChild.submit();
+	};
+
+	gantt._create_hidden_form = function(){
+		if (!this._hidden_export_form){
+			var t = this._hidden_export_form = document.createElement("div");
+			t.style.display = "none";
+			t.innerHTML = "<form method='POST' target='_blank'><textarea name='data' style='width:0px; height:0px;' readonly='true'></textarea><input type='hidden' name='type' value=''></form>";
+			document.body.appendChild(t);
+		}
+		return this._hidden_export_form;
+	};
+
+	//patch broken json serialization in gantt 2.1
+	var original = gantt.json._copyObject;
+	function copy_object_base(obj){
+		var copy = {};
+		for (var key in obj){
+			if (key.charAt(0) == "$")
+				continue;
+			copy[key] = obj[key];
+		}
+
+		var formatDate = gantt.templates.xml_format || gantt.templates.format_date;
+
+		copy.start_date = formatDate(copy.start_date);
+		if (copy.end_date)
+			copy.end_date = formatDate(copy.end_date);
+
+		return copy;
+	}
+
+	function copy_object_plain(obj){
+		var text = gantt.templates.task_text(obj.start_date, obj.end_date, obj);
+
+		var copy = copy_object_base(obj);
+		copy.text = text || copy.text;
+
+		return copy;
+	}
+
+	function get_color(node, style){
+		var value = node.currentStyle ? node.currentStyle[style] : getComputedStyle(node, null)[style];
+		var rgb = value.replace(/\s/g,'').match(/^rgba?\((\d+),(\d+),(\d+)/i);
+		return ((rgb && rgb.length === 4) ? 
+		("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
+		("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
+		("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : value).replace("#","");
+	}
+
+	// Excel interprets UTC time as local time in every timezone, send local time instead of actual UTC time.
+	// https://github.com/SheetJS/js-xlsx/issues/126#issuecomment-60531614
+	var toISOstring = gantt.date.date_to_str("%Y-%m-%dT%H:%i:%s.000Z");
+
+	// excel serialization
+	function copy_object_table(obj){
+		var copy = copy_object_columns(obj, copy_object_plain(obj));
+		if (copy.start_date)
+			copy.start_date = toISOstring(obj.start_date);
+		if (copy.end_date)
+			copy.end_date = toISOstring(obj.end_date);
+
+		// private gantt._day_index_by_date was replaced by public gantt.columnIndexByDate in gantt 5.0
+		var getDayIndex = gantt._day_index_by_date ? gantt._day_index_by_date : gantt.columnIndexByDate;
+
+		copy.$start = getDayIndex.call(gantt, obj.start_date);
+		copy.$end	= getDayIndex.call(gantt, obj.end_date);
+		copy.$level	= obj.$level;
+		copy.$type	= obj.$rendered_type;
+
+		var tmps = gantt.templates;
+		copy.$text = tmps.task_text(obj.start, obj.end_date, obj);
+		copy.$left = tmps.leftside_text ? tmps.leftside_text(obj.start, obj.end_date, obj) : "";
+		copy.$right = tmps.rightside_text ? tmps.rightside_text(obj.start, obj.end_date, obj) : "";
+	
+		return copy;
+	}
+
+	function copy_object_colors(obj){
+		var copy = copy_object_table(obj);
+
+		var node = gantt.getTaskNode(obj.id);
+		if (node && node.firstChild){
+			var color = get_color((gantt._no_progress_colors ? node : node.firstChild), "backgroundColor");
+			if (color == "363636")
+				color = get_color(node, "backgroundColor");
+
+			copy.$color = color;
+		} else if (obj.color)
+			copy.$color = obj.color;
+
+		return copy;
+	}
+
+	function copy_object_columns(obj, copy){
+		for(var i=0; i<gantt.config.columns.length; i++){
+			var ct = gantt.config.columns[i].template;
+			if (ct) {
+				var val = ct(obj);
+				if (val instanceof Date)
+					val = gantt.templates.date_grid(val, obj);
+				copy["_"+i] = val;
+			}
+		}
+		return copy;
+	}
+
+	function copy_object_all(obj){
+		var copy = copy_object_base(obj);
+
+		//serialize all text templates
+		for (var i = 0; i < templates.length; i++){
+			var template = gantt.templates[templates[i]];
+			if (template)
+				copy["$"+i]	= template(obj.start_date, obj.end_date, obj);
+		}
+
+		copy_object_columns(obj, copy);
+		copy.open = obj.$open;
+		return copy;
+	}
+
+	function fix_columns(gantt, columns){
+		for (var i = 0; i < columns.length; i++) {
+			columns[i].label = columns[i].label || gantt.locale.labels["column_"+columns[i].name];
+			if (typeof columns[i].width == "string") columns[i].width = columns[i].width*1;
+		}
+	}
+
+	gantt._serialize_html = function(){
+		var smartScales = gantt.config.smart_scales;
+		var smartRendering = gantt.config.smart_rendering;
+		if(smartScales || smartRendering){
+			gantt.config.smart_rendering = false;
+			gantt.config.smart_scales = false;
+			gantt.render();
+		}
+
+		var html = this.$container.parentNode.innerHTML;
+
+		if(smartScales || smartRendering){
+			gantt.config.smart_scales = smartScales;
+			gantt.config.smart_rendering = smartRendering;
+			gantt.render();
+		}
+
+		return html;
+	};
+
+	gantt._serialize_all = function(){
+		gantt.json._copyObject = copy_object_all;
+		var data = export_serialize();
+		gantt.json._copyObject = original;
+		return data;
+	};
+
+	gantt._serialize_plain = function(){
+		var oldXmlFormat = gantt.templates.xml_format;
+		var oldFormatDate = gantt.templates.format_date;
+		gantt.templates.xml_format = gantt.date.date_to_str("%Y%m%dT%H%i%s", true);
+		gantt.templates.format_date = gantt.date.date_to_str("%Y%m%dT%H%i%s", true);
+		gantt.json._copyObject = copy_object_plain;
+
+		var data = export_serialize();
+
+		gantt.templates.xml_format = oldXmlFormat;
+		gantt.templates.format_date = oldFormatDate;
+		gantt.json._copyObject = original;
+
+		delete data.links;
+		return data;
+	};
+
+	function get_raw() {
+		// support Gantt < 5.0
+		if (gantt._scale_helpers) {
+			var scales = gantt._get_scales(),
+				min_width = gantt.config.min_column_width,
+				autosize_min_width = gantt._get_resize_options().x ? Math.max(gantt.config.autosize_min_width, 0) : config.$task.offsetWidth,
+				height = config.config.scale_height - 1;
+			return gantt._scale_helpers.prepareConfigs(scales, min_width, autosize_min_width, height);
+		} else { // Gantt >= 5.0
+			var timeline = gantt.$ui.getView("timeline");
+			if (timeline) {
+				var availWidth = timeline.$config.width;
+				if (gantt.config.autosize == "x" || gantt.config.autosize == "xy") {
+					availWidth = Math.max(gantt.config.autosize_min_width, 0);
+				}
+				var state = gantt.getState(),
+					scales = timeline._getScales(),
+					min_width = gantt.config.min_column_width,
+					height = gantt.config.scale_height-1,
+					rtl = gantt.config.rtl;
+				return timeline.$scaleHelper.prepareConfigs(scales, min_width, availWidth, height, state.min_date, state.max_date, rtl);
+			}
+		}
+	}
+
+	gantt._serialize_table = function(config){
+		gantt.json._copyObject = config.visual ? copy_object_colors : copy_object_table;
+		var data = export_serialize();
+		gantt.json._copyObject = original;
+
+		delete data.links;
+
+		if (config.cellColors){
+			var css = this.templates.timeline_cell_class || this.templates.task_cell_class;
+			if (css){
+				var raw = get_raw();
+				var steps = raw[0].trace_x;
+				for (var i=1; i<raw.length; i++)
+					if (raw[i].trace_x.length > steps.length)
+						steps = raw[i].trace_x;
+
+				for (var i=0; i<data.data.length; i++){
+					data.data[i].styles = [];
+					var task = this.getTask(data.data[i].id);
+					for (var j = 0; j < steps.length; j++) {
+						var date = steps[j];
+						var cell_css = css(task, date);
+						if (cell_css)
+							data.data[i].styles.push({ index: j, styles: get_styles(cell_css) });
+					}
+				}
+			}
+		}
+		return data;
+	};
+
+	gantt._serialize_scales = function(config){
+		var scales = [];
+		var raw = get_raw();
+		
+		var min = Infinity;
+		var max = 0;
+		for (var i=0; i<raw.length; i++) min = Math.min(min, raw[i].col_width);
+
+		for (var i=0; i<raw.length; i++){
+			var start = 0;
+			var end = 0;
+			var row = [];
+
+			scales.push(row);
+			var step = raw[i];
+			max = Math.max(max, step.trace_x.length);
+			var template = step.format || step.template || (step.date ? gantt.date.date_to_str(step.date) :  gantt.config.date_scale);
+
+			for (var j = 0; j < step.trace_x.length; j++) {
+				var date = step.trace_x[j];
+				end = start + Math.round(step.width[j]/min);
+
+				var scale_cell = { text: template(date), start: start, end: end };
+
+				if (config.cellColors){
+					var css = step.css || this.templates.scale_cell_class;
+					if (css){
+						var scale_css = css(date);
+						if (scale_css)
+							scale_cell.styles=get_styles(scale_css);
+					}
+				}
+
+				row.push(scale_cell);
+				start = end;
+			}
+		}
+
+		return { width:max, height:scales.length, data:scales };
+	};
+
+	gantt._serialize_columns = function(config){
+		gantt.exportMode = true;
+
+		var columns = [];
+		var cols = gantt.config.columns;
+
+		var ccount = 0;
+		for (var i = 0; i < cols.length; i++){
+			if (cols[i].name == "add" || cols[i].name == "buttons") continue;
+
+			columns[ccount] = {
+				id:		((cols[i].template) ? ("_"+i) : cols[i].name),
+				header:	cols[i].label || gantt.locale.labels["column_"+cols[i].name],
+				width: 	(cols[i].width ? Math.floor(cols[i].width/4) : "")
+			};
+
+			if (cols[i].name == "duration")
+				columns[ccount].type = "number";
+			if (cols[i].name == "start_date" || cols[i].name == "end_date"){
+				columns[ccount].type = "date";
+				if (config && config.rawDates)
+					columns[ccount].id = cols[i].name;
+			}
+
+			ccount++;
+		}
+		
+		gantt.exportMode = false;
+		return columns;
+	};
+
+	function export_serialize(){
+		gantt.exportMode = true;
+		var data = gantt.serialize();
+		gantt.exportMode = false;
+		return data;
+	}
+}
+
+add_export_methods(gantt);
+if (window.Gantt && Gantt.plugin)
+	Gantt.plugin(add_export_methods);
+
+})();
+
+(function () {
+	var apiUrl = "https://export.dhtmlx.com/gantt";
+
+	function set_level(data) {
+		for (var i = 0; i < data.length; i++) {
+			if (data[i].parent == 0) {
+				data[i]._lvl = 1;
+			}
+			for (var j = i + 1; j < data.length; j++) {
+				if (data[i].id == data[j].parent) {
+					data[j]._lvl = data[i]._lvl + 1;
+				}
+			}
+		}
+	}
+
+	function clear_level(data) {
+		for (var i = 0; i < data.length; i++) {
+			delete data[i]._lvl
+		}
+	}
+
+	function clear_rec_links(data) {
+		set_level(data.data);
+		var tasks = {};
+		for (var i = 0; i < data.data.length; i++) {
+			tasks[data.data[i].id] = data.data[i];
+		}
+
+		var links = {};
+		for (i = 0; i < data.links.length; i++) {
+			var link = data.links[i];
+			if(gantt.isTaskExists(link.source) && gantt.isTaskExists(link.target)){
+				links[link.id] = link;
+			}
+		}
+
+		for (i in links) {
+			make_links_same_level(links[i], tasks);
+		}
+
+		var skippedLinks = {};
+		for (i in tasks) {
+			clear_circ_dependencies(tasks[i], links, tasks, {}, skippedLinks, null);
+		}
+
+		for (i in links) {
+			clear_links_same_level(links, tasks);
+		}
+
+		for (i = 0; i < data.links.length; i++) {
+			if (!links[data.links[i].id]) {
+				data.links.splice(i, 1);
+				i--;
+			}
+		}
+
+		clear_level(data.data);
+	}
+
+	function clear_circ_dependencies(task, links, tasks, usedTasks, skippedLinks, prevLink) {
+		var sources = task.$_source;
+		if (!sources) return;
+
+		if (usedTasks[task.id]) {
+			on_circ_dependency_find(prevLink, links, usedTasks, skippedLinks);
+		}
+
+		usedTasks[task.id] = true;
+
+		var targets = {};
+
+		for (var i = 0; i < sources.length; i++) {
+			if (skippedLinks[sources[i]]) continue;
+			var curLink = links[sources[i]];
+			var targetTask = tasks[curLink._target];
+			if (targets[targetTask.id]) { // two link from one task to another
+				on_circ_dependency_find(curLink, links, usedTasks, skippedLinks);
+			}
+			targets[targetTask.id] = true;
+			clear_circ_dependencies(targetTask, links, tasks, usedTasks, skippedLinks, curLink);
+		}
+		usedTasks[task.id] = false;
+	}
+
+	function on_circ_dependency_find(link, links, usedTasks, skippedLinks) {
+		if (link) {
+			if (gantt.callEvent("onExportCircularDependency", [link.id, link])) {
+				delete links[link.id];
+			}
+
+			delete usedTasks[link._source];
+			delete usedTasks[link._target];
+			skippedLinks[link.id] = true;
+		}
+	}
+
+	function make_links_same_level(link, tasks) {
+		var task,
+			targetLvl,
+			linkT = {
+				target: tasks[link.target],
+				source: tasks[link.source]
+			};
+
+		if (linkT.target._lvl != linkT.source._lvl) {
+			if (linkT.target._lvl < linkT.source._lvl) {
+				task = "source";
+				targetLvl = linkT.target._lvl
+			}
+			else {
+				task = "target";
+				targetLvl = linkT.source._lvl;
+			}
+
+			do {
+				var parent = tasks[linkT[task].parent];
+				if (!parent) break;
+				linkT[task] = parent;
+			} while (linkT[task]._lvl < targetLvl);
+
+			var sourceParent = tasks[linkT.source.parent],
+				targetParent = tasks[linkT.target.parent];
+
+			while (sourceParent && targetParent && sourceParent.id != targetParent.id) {
+				linkT.source = sourceParent;
+				linkT.target = targetParent;
+				sourceParent = tasks[linkT.source.parent];
+				targetParent = tasks[linkT.target.parent];
+			}
+		}
+
+		link._target = linkT.target.id;
+		link._source = linkT.source.id;
+
+		if (!linkT.target.$_target)
+			linkT.target.$_target = [];
+		linkT.target.$_target.push(link.id);
+
+		if (!linkT.source.$_source)
+			linkT.source.$_source = [];
+		linkT.source.$_source.push(link.id);
+	}
+
+	function clear_links_same_level(links, tasks) {
+		for (var link in links) {
+			delete links[link]._target;
+			delete links[link]._source;
+		}
+
+		for (var task in tasks) {
+			delete tasks[task].$_source;
+			delete tasks[task].$_target;
+		}
+	}
+
+
+	function customProjectProperties(data, config){
+		if (config && config.project) {
+			for (var i in config.project) {
+				if (!gantt.config.$custom_data)
+					gantt.config.$custom_data = {};
+				gantt.config.$custom_data[i] = typeof config.project[i] == "function" ? config.project[i](gantt.config) : config.project[i];
+			}
+			delete config.project;
+		}
+	}
+
+	function customTaskProperties(data, config){
+		if (config && config.tasks) {
+			data.data.forEach(function (el) {
+				for (var i in config.tasks) {
+					if (!el.$custom_data)
+						el.$custom_data = {};
+					el.$custom_data[i] = typeof config.tasks[i] == "function" ? config.tasks[i](el, gantt.config) : config.tasks[i];
+				}
+			});
+			delete config.tasks;
+		}
+	}
+
+	function exportConfig(data, config){
+		var p_name = config.name || 'gantt.xml';
+		delete config.name;
+
+		gantt.config.custom = config;
+		
+		var time = gantt._getWorktimeSettings();
+		
+		var p_dates = gantt.getSubtaskDates();
+		if (p_dates.start_date && p_dates.end_date) {
+			var formatDate = gantt.templates.xml_format || gantt.templates.format_date;
+			gantt.config.start_end = {
+				start_date: formatDate(p_dates.start_date),
+				end_date: formatDate(p_dates.end_date)
+			};
+		}
+
+		var manual = config.auto_scheduling === undefined ? false : !!config.auto_scheduling;
+
+		var res = {
+			callback: config.callback || null,
+			config: gantt.config,
+			data: data,
+			manual: manual,
+			name: p_name,
+			worktime: time
+		};
+		for(var i in config) res[i] = config[i];
+		return res;
+	}
+
+	function add_export_methods(gantt) {
+		gantt._ms_export = {};
+
+		gantt.exportToMSProject = function (config) {
+			config = config || {};
+			config.skip_circular_links = config.skip_circular_links === undefined ? true : !!config.skip_circular_links;
+
+			var old_xml_format = this.templates.xml_format;
+			var old_format_date = this.templates.format_date;
+			this.templates.xml_format = this.date.date_to_str("%d-%m-%Y %H:%i:%s");
+			this.templates.format_date = this.date.date_to_str("%d-%m-%Y %H:%i:%s");
+			var data = this._serialize_all();
+
+			customProjectProperties(data, config);
+
+			customTaskProperties(data, config);
+
+			if (config.skip_circular_links) {
+				clear_rec_links(data);
+			}
+
+			config = exportConfig(data, config);
+
+			this._send_to_export(config, config.type || "msproject");
+			this.templates.xml_format = old_xml_format;
+			this.templates.format_date = old_format_date;
+			this.config.$custom_data = null;
+			this.config.custom = null;
+		};
+
+		gantt.exportToPrimaveraP6 = function(config){
+			config.type = "primaveraP6";
+			return gantt.exportToMSProject(config);
+		};
+
+		function sendImportAjax(config){
+			var url = config.server || apiUrl;
+			var store = config.store || 0;
+			var formData = config.data;
+			var callback = config.callback;
+
+			var settings = {};
+			if(config.durationUnit) settings.durationUnit = config.durationUnit
+			if(config.projectProperties) settings.projectProperties = config.projectProperties;
+			if(config.taskProperties) settings.taskProperties = config.taskProperties;
+
+			formData.append("type", config.type || "msproject-parse");
+			formData.append("data", JSON.stringify(settings));
+
+			if(store)
+				formData.append("store", store);
+
+			var xhr = new XMLHttpRequest();
+			xhr.onreadystatechange = function (e) {
+				if(xhr.readyState == 4 && xhr.status == 0){// network error
+					if(callback){
+						callback(null);
+					}
+				}
+			};
+
+			xhr.onload = function() {
+
+				var fail = xhr.status > 400;
+				var info = null;
+
+				if (!fail){
+					try{
+						info = JSON.parse(xhr.responseText);
+					}catch(e){}
+				}
+
+				if(callback){
+					callback(info);
+				}
+
+			};
+
+			xhr.open('POST', url, true);
+			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+			xhr.send(formData);
+		}
+
+		gantt.importFromMSProject = function(config){
+			var formData = config.data;
+
+			if(formData instanceof FormData){
+
+			}else if(formData instanceof File){
+				var data = new FormData();
+				data.append("file", formData);
+				config.data = data;
+			}
+			sendImportAjax(config);
+		};
+
+		gantt.importFromPrimaveraP6 = function(config){
+			config.type = "primaveraP6-parse";
+			return gantt.importFromMSProject(config);
+		};
+	}
+
+	add_export_methods(gantt);
+	if (window.Gantt && Gantt.plugin)
+		Gantt.plugin(add_export_methods);
+
+})();

File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_auto_scheduling.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_auto_scheduling.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_critical_path.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_critical_path.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_csp.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_csp.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_fullscreen.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_fullscreen.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_grouping.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_grouping.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_keyboard_navigation.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_keyboard_navigation.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_marker.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_marker.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_multiselect.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_multiselect.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_quick_info.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_quick_info.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_smart_rendering.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_smart_rendering.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_tooltip.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_tooltip.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_undo.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/ext/dhtmlxgantt_undo.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_ar.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_ar.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_be.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_be.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_ca.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_ca.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_cn.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_cn.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_cs.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_cs.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_da.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_da.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_de.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_de.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_el.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_el.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_es.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_es.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_fa.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_fa.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_fi.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_fi.js.map


File diff ditekan karena terlalu besar
+ 8 - 0
static/dhtmlxgantt/locale/locale_fr.js


File diff ditekan karena terlalu besar
+ 0 - 0
static/dhtmlxgantt/locale/locale_fr.js.map


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini