A 2022-07-13 18:36:14 +08:00
commit bcd4aa9ed2
11 changed files with 313 additions and 77 deletions

11
package-lock.json generated
View File

@ -15,6 +15,7 @@
"echarts": "^5.3.3", "echarts": "^5.3.3",
"element-plus": "^2.2.6", "element-plus": "^2.2.6",
"moment": "^2.29.1", "moment": "^2.29.1",
"spark-md5": "^3.0.2",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-router": "^4.0.0-0", "vue-router": "^4.0.0-0",
"vuex": "^4.0.2", "vuex": "^4.0.2",
@ -12665,6 +12666,11 @@
"resolved": "https://registry.npmmirror.com/sourcemap-codec/download/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmmirror.com/sourcemap-codec/download/sourcemap-codec-1.4.8.tgz",
"integrity": "sha1-6oBL2UhXQC5pktBaOO8a41qatMQ=" "integrity": "sha1-6oBL2UhXQC5pktBaOO8a41qatMQ="
}, },
"node_modules/spark-md5": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz",
"integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
},
"node_modules/spdx-correct": { "node_modules/spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmmirror.com/spdx-correct/download/spdx-correct-3.1.1.tgz", "resolved": "https://registry.npmmirror.com/spdx-correct/download/spdx-correct-3.1.1.tgz",
@ -25999,6 +26005,11 @@
"resolved": "https://registry.npmmirror.com/sourcemap-codec/download/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmmirror.com/sourcemap-codec/download/sourcemap-codec-1.4.8.tgz",
"integrity": "sha1-6oBL2UhXQC5pktBaOO8a41qatMQ=" "integrity": "sha1-6oBL2UhXQC5pktBaOO8a41qatMQ="
}, },
"spark-md5": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz",
"integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
},
"spdx-correct": { "spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmmirror.com/spdx-correct/download/spdx-correct-3.1.1.tgz", "resolved": "https://registry.npmmirror.com/spdx-correct/download/spdx-correct-3.1.1.tgz",

View File

@ -14,6 +14,7 @@
"echarts": "^5.3.3", "echarts": "^5.3.3",
"element-plus": "^2.2.6", "element-plus": "^2.2.6",
"moment": "^2.29.1", "moment": "^2.29.1",
"spark-md5": "^3.0.2",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-router": "^4.0.0-0", "vue-router": "^4.0.0-0",
"vuex": "^4.0.2", "vuex": "^4.0.2",

View File

@ -15,17 +15,22 @@ export default {
chartData: [] chartData: []
} }
}, },
props: {
enable: false,
},
watch: {
enable: function (val) {
console.log("ENABLE"+val)
if(val)
this.initEcharts();
},
},
mounted() { mounted() {
this.initEcharts(); console.log("ENABLE"+this.enable)
}, if(this.enable)
computed: { this.initEcharts();
getTotalNumber() {
return this.dataList.reduce((prev, item) => {
prev += item.value
return prev
}, 0);
}
}, },
methods: { methods: {
// //
getOption() { getOption() {

View File

@ -0,0 +1,143 @@
<template>
<div id="projectechartsBar" ref="projectechartsBar">
<!-- 准备具有宽高的容器 -->
<div style="width: 100%; height: 100%" ref="chart"></div>
</div>
</template>
<script>
import request from "@/utils/request";
let Echarts = require('echarts/lib/echarts'); // 使import
require('echarts/lib/chart/bar'); // bar =
export default {
data() {
return {
chart: null,
}
},
props: {
task: null
},
emits: ['mounted'],
watch: {
task: function (val) {
console.log("initEchartsBar")
console.log(this.task)
this.initEcharts()
}
},
mounted() {
this.$emit("mounted");
},
methods: {
//
init() {
console.log(this.$refs.chart);
//
this.chart = Echarts.init(this.$refs.chart);
let option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow' // 'shadow' as default; can also be 'line' or 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: ['需求', '任务', '缺陷']
},
color: [ '#79bbff', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
series: [
{
name: '未完成',
type: 'bar',
stack: 'total',
label: {
show: true,
color:'#FFFFFF',
formatter:function (params) {
if(params.value==0){ //0
return ''
}else{
return params.value
}
}
},
emphasis: {
focus: 'series'
},
data: [this.task.require.totalNum - this.task.require.completeNum, this.task.task.totalNum - this.task.task.completeNum, this.task.flaw.totalNum - this.task.flaw.completeNum]
},
{
name: '已完成',
type: 'bar',
stack: 'total',
label: {
show: true,
color:'#FFFFFF',
formatter:function (params) {
if(params.value==0){ //0
return ''
}else{
return params.value
}
}
},
emphasis: {
focus: 'series'
},
data: [this.task.require.completeNum, this.task.task.completeNum, this.task.flaw.completeNum]
},
]
};
this.chart.setOption(option);
},
initEcharts() {
this.$nextTick(() => {
this.init()
});
const that = this
//this.chart.setOption(option);
window.addEventListener("resize", function () {
that.chart.resize();
});
}
}
};
</script>
<style scoped lang="scss">
#echartsBar {
width: 100%;
height: 100%;
display: flex;
.echarts_box {
flex-shrink: 0;
}
}
</style>

View File

@ -72,7 +72,7 @@ export default {
}, },
emphasis: { emphasis: {
label: { label: {
show: true, show: false,
fontSize: '40', fontSize: '40',
fontWeight: 'bold' fontWeight: 'bold'
} }
@ -88,7 +88,7 @@ export default {
type: 'scroll', type: 'scroll',
orient: 'vertical', orient: 'vertical',
// right: 10, // right: 10,
left:350, left: '80%',
top: 20, top: 20,
bottom: 20, bottom: 20,
} }

View File

@ -78,7 +78,7 @@
</div> </div>
<template #tip> <template #tip>
<div class="el-upload__tip"> <div class="el-upload__tip">
<el-link href="https://element.eleme.io" type="primary">下载模板</el-link> <el-link href="https://www.hammer-hfut.tk/api/public/账户导入模板.xlsx" type="primary">下载模板</el-link>
</div> </div>
</template> </template>
</el-upload> </el-upload>
@ -95,7 +95,6 @@ import {UploadFilled} from '@element-plus/icons'
import request from "@/utils/request"; import request from "@/utils/request";
import router from "../router"; import router from "../router";
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import md5 from 'blueimp-md5'
export default { export default {
name: 'Home', name: 'Home',
@ -126,20 +125,32 @@ export default {
}, },
methods: { methods: {
customUpload(options) { customUpload(options) {
const that = this
let param = new FormData() let param = new FormData()
param.append('fileDigest', md5(options.file)) const fileReader = new FileReader()
param.append('uploadFile', options.file) fileReader.readAsBinaryString(options.file);
request({ fileReader.onload = e => {
url: '/staff/import', console.log(e.target.result)
data: param, let SparkMD5 = require('spark-md5')
method: 'post', param.append('fileDigest', SparkMD5.hashBinary(e.target.result))
onUploadProgress: progressEvent => { param.append('uploadFile', options.file)
progressEvent.percent = progressEvent.loaded / progressEvent.total * 100 | 0 request({
options.onProgress(progressEvent) url: '/staff/import',
} data: param,
}).then((res) => { method: 'post',
console.log(res.data); onUploadProgress: progressEvent => {
}) progressEvent.percent = progressEvent.loaded / progressEvent.total * 100 | 0
options.onProgress(progressEvent)
}
}).then((res) => {
console.log(res.data);
ElMessage({
message: '导入成功',
type: 'success',
})
that.uploadDialogVisible = false
})
}
}, },
getStaffFullname() { getStaffFullname() {
if (this.$store && this.$store.state.staff) if (this.$store && this.$store.state.staff)

View File

@ -74,9 +74,9 @@ const tableHeight = ref();
const {proxy, ctx} = getCurrentInstance() const {proxy, ctx} = getCurrentInstance()
onMounted(() => { onMounted(() => {
// innerHeight-offsetTop-110 // innerHeight-offsetTop-110
tableHeight.value = window.innerHeight /*- tableRef.value.$el.offsetTop*/ - 400; tableHeight.value = window.innerHeight /*- tableRef.value.$el.offsetTop*/ - 300;
window.onresize = () => { window.onresize = () => {
tableHeight.value = window.innerHeight /*- tableRef.value.$el.offsetTop*/ - 400; tableHeight.value = window.innerHeight /*- tableRef.value.$el.offsetTop*/ - 300;
}; };
}); });
const router = useRouter() const router = useRouter()
@ -185,17 +185,19 @@ export default {
staffId: Number staffId: Number
} }
}, },
computed: {}, computed: {
canCreateProject() {
if (this.$store && this.$store.state.staff)
return this.$store.state.staff.staffGlobalLevel < 3
else
return false
},
},
mounted() { mounted() {
}, },
methods: { methods: {
canCreateProject() {
if (this.$store && this.$store.state.staff)
return this.$store.state.staff.staffGlobalLevel > 3
else
return false
},
showCloseProjectButton(row) { showCloseProjectButton(row) {
return this.$store.state.staff === null ? false : (this.$store.state.staff.staffId === row.projectCreator) return this.$store.state.staff === null ? false : (this.$store.state.staff.staffId === row.projectCreator)
}, },

View File

@ -60,6 +60,7 @@
</el-header> </el-header>
<el-main style="overflow: unset"> <el-main style="overflow: unset">
<router-view <router-view
:project="project"
:projectAccessLevel="projectAccessLevel" :projectAccessLevel="projectAccessLevel"
:projectGroup="projectGroup" :projectGroup="projectGroup"
@groupChanged="groupChanged" @groupChanged="groupChanged"
@ -88,6 +89,7 @@ export default {
projectStaffPosition: '', projectStaffPosition: '',
projectAccessLevel: 3, projectAccessLevel: 3,
menuDefaultActive: '1', menuDefaultActive: '1',
} }
}, },

View File

@ -5,7 +5,7 @@
<div style="margin: 0 10px 0 10px;display: flex;flex-direction: row;justify-content: space-between" <div style="margin: 0 10px 0 10px;display: flex;flex-direction: row;justify-content: space-between"
ref="left_title_Ref"> ref="left_title_Ref">
<p style="text-align:center; 'Segoe UI',sans-serif;font-size: 20px;font-weight: bold;color: #606266">项目团队</p> <p style="text-align:center; 'Segoe UI',sans-serif;font-size: 20px;font-weight: bold;color: #606266">项目团队</p>
<el-button type="primary" @click="onCreatePerson"></el-button> <el-button v-if="!project.completed&&projectAccessLevel<3" type="primary" @click="onCreatePerson"></el-button>
</div> </div>
<div style="flex: 1; margin: 30px 0 0 0; background-color: white; border-radius: 10px;padding: 20px; <div style="flex: 1; margin: 30px 0 0 0; background-color: white; border-radius: 10px;padding: 20px;
display: flex;flex-direction: column;justify-content: space-between"> display: flex;flex-direction: column;justify-content: space-between">
@ -18,7 +18,7 @@
<el-table-column prop="projectStaffPosition" label="职位" min-width="40%" :formatter="jobFormatter"/> <el-table-column prop="projectStaffPosition" label="职位" min-width="40%" :formatter="jobFormatter"/>
<el-table-column align="right" min-width="35%"> <el-table-column align="right" min-width="35%">
<template #default="scope"> <template #default="scope">
<div v-if="getOperationsVisible(scope.row)" <div v-if="!project.completed&&getOperationsVisible(scope.row)"
style="width: 100%;display: flex;flex-direction: row;justify-content: flex-end"> style="width: 100%;display: flex;flex-direction: row;justify-content: flex-end">
<el-button <el-button
@click.native="onEditClick(scope.row)" @click.native="onEditClick(scope.row)"
@ -149,6 +149,7 @@ export default {
}, },
emits: ['groupChanged'], emits: ['groupChanged'],
props: { props: {
project: {completed:true},
projectAccessLevel: Number, projectAccessLevel: Number,
projectGroup: Array projectGroup: Array
}, },

View File

@ -47,8 +47,17 @@
</div> </div>
<div style="flex: 1;margin-left: 30px;height: 100%;display: flex;flex-direction: column"> <div style="flex: 1;margin-left: 30px;height: 100%;display: flex;flex-direction: column">
<p class="p-title" style="margin-left: 10px">工作项统计</p> <p class="p-title" style="margin-left: 10px">工作项统计</p>
<div style="flex:1;margin-top: 30px;padding: 20px; <div
display: flex;flex-direction: column; justify-content: space-between;background-color: white; border-radius: 10px;"> style="flex:1;margin-top: 30px;padding: 10px;
display: flex;flex-direction:row; justify-content: space-between;background-color: white; border-radius: 10px;">
<div style="width: 50%;">
<p class="p-subtitle" style="margin-left: 10px;margin-top: 10px">项目</p>
<echartsBar @mounted="getTaskStats" :task="taskStat" style="height: 190px; weight: 100%"></echartsBar>
</div>
<div style="width: 50%;">
<p class="p-subtitle" style="margin-left: 10px;margin-top: 10px">个人</p>
<echartsBar @mounted="getselfTaskStats" :task="selftaskStat" style="height: 190px; weight: 100%"></echartsBar>
</div>
</div> </div>
</div> </div>
@ -59,7 +68,9 @@
<div style="margin: 0 10px 0 10px; <div style="margin: 0 10px 0 10px;
display: flex;flex-direction: row;justify-content: space-between"> display: flex;flex-direction: row;justify-content: space-between">
<p style="font-family: 'Segoe UI',sans-serif;font-size: 20px;font-weight: bold;color: #606266">详细信息</p> <p style="font-family: 'Segoe UI',sans-serif;font-size: 20px;font-weight: bold;color: #606266">详细信息</p>
<el-button v-if="projectAccessLevel===1" type="primary" @click.native="onEditProjectClick"></el-button> <el-button v-if="!project.completed&&projectAccessLevel===1" type="primary"
@click.native="onEditProjectClick">编辑
</el-button>
</div> </div>
<div style="flex:1;margin-top: 30px;padding: 20px; <div style="flex:1;margin-top: 30px;padding: 20px;
display: flex;flex-direction: column; justify-content: space-between;background-color: white; border-radius: 10px; "> display: flex;flex-direction: column; justify-content: space-between;background-color: white; border-radius: 10px; ">
@ -209,13 +220,13 @@
</div> </div>
</div> </div>
<div style="height: 500px;margin: 0 30px 30px 30px; <div v-if="!project.completed" style="height: 500px;margin: 0 30px 30px 30px;
display: flex;flex-direction: row;justify-content: space-between"> display: flex;flex-direction: row;justify-content: space-between">
<div style="flex: 1;height: 100%;display: flex;flex-direction: column"> <div style="flex: 1;height: 100%;display: flex;flex-direction: column">
<p class="p-title" style="margin-left: 10px">工作项统计</p> <p class="p-title" style="margin-left: 10px">工作项完成趋势</p>
<div style="flex:1;margin-top: 30px;padding: 20px; <div style="flex:1;margin-top: 30px;padding: 20px;
display: flex;flex-direction: column; justify-content: space-between;background-color: white; border-radius: 10px;align-items: stretch"> display: flex;flex-direction: column; justify-content: space-between;background-color: white; border-radius: 10px;align-items: stretch">
<TendencyChart style="flex: 1;"></TendencyChart> <TendencyChart :enable="!project.completed" style="flex: 1;"></TendencyChart>
</div> </div>
</div> </div>
</div> </div>
@ -254,7 +265,7 @@
title="提示" title="提示"
:model-value="dialogVisible" :model-value="dialogVisible"
width="30%" width="30%"
:before-close="handleClose"> >
<div>确认删除该公告?</div> <div>确认删除该公告?</div>
<template #footer> <template #footer>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
@ -276,37 +287,20 @@ import request from "@/utils/request";
import moment from "moment"; import moment from "moment";
import EditProjectDialog from "../components/EditProjectDialog"; import EditProjectDialog from "../components/EditProjectDialog";
import TendencyChart from "../components/TendencyChart"; import TendencyChart from "../components/TendencyChart";
import EchartsBar from "../components/echartsBar"
export default { export default {
name: "ProjectInfo", name: "ProjectInfo",
components: {EditProjectDialog, TendencyChart}, components: {EditProjectDialog, TendencyChart, EchartsBar},
data() { data() {
return { return {
project: {
projectName: '',
projectAbbreviation: '',
projectClassId: '',
projectSubclassId: '',
projectImportance: '',
contractAmount: '',
expectedCompletion: '',
projectManMonth: '',
projectStartDate: Number,
projectOnlineDate: Number,
projectFirstTestDate: Number,
projectFinalTestDate: Number,
projectEndDate: Number,
financialCode: '',
projectDepartment: '',
projectArea: '',
projectCompany: '',
projectDescription: '',
completed: '',
},
completeNum: 0, completeNum: 0,
totalNum: 0, totalNum: 0,
taskStat: {},
selftaskStat: {},
projectClasses: [], projectClasses: [],
projectSubClasses: [], projectSubClasses: [],
@ -339,6 +333,27 @@ export default {
} }
}, },
props: { props: {
project: {
projectName: '',
projectAbbreviation: '',
projectClassId: '',
projectSubclassId: '',
projectImportance: '',
contractAmount: '',
expectedCompletion: '',
projectManMonth: '',
projectStartDate: Number,
projectOnlineDate: Number,
projectFirstTestDate: Number,
projectFinalTestDate: Number,
projectEndDate: Number,
financialCode: '',
projectDepartment: '',
projectArea: '',
projectCompany: '',
projectDescription: '',
completed: '',
},
projectAccessLevel: Number, projectAccessLevel: Number,
projectGroup: Array projectGroup: Array
}, },
@ -388,14 +403,16 @@ export default {
}, },
project: function (val) { project: function (val) {
this.getProjectStats();
this.getProjectClass();
this.getannouncementList();
this.getTaskStats();
this.getselfTaskStats();
}, },
}, },
created() { created() {
this.getProjectInfo(); //this.getProjectInfo();
this.getProjectStats();
this.getProjectClass();
this.getannouncementList();
}, },
mounted() { mounted() {
const that = this const that = this
@ -405,6 +422,48 @@ export default {
}; };
}, },
methods: { methods: {
getTaskStats() {
const that = this
request({
url: 'project/' + this.$route.params.projectId + '/task/stats',
method: 'get'
}).then(response => {
if (response.data.code === 200) {
that.taskStat = {
require: response.data.data.records[0],
task: response.data.data.records[1],
flaw: response.data.data.records[2]
}
}
}).catch(function (error) {
console.log(error)
})
},
getselfTaskStats() {
const that = this
request({
url: 'project/' + this.$route.params.projectId + '/task/stats/' + this.$store.state.staff.staffId,
method: 'get'
}).then(response => {
if (response.data.code === 200) {
that.selftaskStat = {
require: response.data.data.records[0],
task: response.data.data.records[1],
flaw: response.data.data.records[2]
}
}
}).catch(function (error) {
console.log(error)
})
},
// //
delxtggsj() { delxtggsj() {
var that = this; var that = this;

View File

@ -52,7 +52,7 @@
<template #reference> <template #reference>
<el-button :style="{'visibility': ( projectAccessLevel < 3&&!mine)?'unset':'hidden'}" type="primary">新增工作项 <el-button :style="{'visibility': ( !project.completed&&projectAccessLevel < 3&&!mine)?'unset':'hidden'}" type="primary">新增工作项
</el-button> </el-button>
</template> </template>
</el-popover> </el-popover>
@ -157,7 +157,7 @@
<template #reference> <template #reference>
<el-button <el-button
:style="{'padding':'4px','margin-left': '5px','visibility': :style="{'padding':'4px','margin-left': '5px','visibility':
(scope.row.taskType!=='缺陷'&& (!project.completed&&scope.row.taskType!=='缺陷'&&
!statusDisabled(scope.row))? !statusDisabled(scope.row))?
'unset':'hidden'}" 'unset':'hidden'}"
text> text>
@ -350,6 +350,7 @@ import router from "../router";
export default { export default {
name: "ProjectWorkitem", name: "ProjectWorkitem",
props: { props: {
project: {completed: true},
projectAccessLevel: Number, projectAccessLevel: Number,
projectGroup: Array projectGroup: Array
}, },
@ -418,7 +419,7 @@ export default {
statusDisabled(row) { statusDisabled(row) {
return row.taskStatus === '已完成' || row.taskStatus === '关闭' || !(row.editable || return this.project.completed||row.taskStatus === '已完成' || row.taskStatus === '关闭' || !(row.editable ||
(this.$store.state.staff && row.taskHolderId === this.$store.state.staff.staffId)) (this.$store.state.staff && row.taskHolderId === this.$store.state.staff.staffId))
}, },
onRowClick(row, column, event) { onRowClick(row, column, event) {
@ -721,7 +722,7 @@ export default {
//console.log(response.data.records) //console.log(response.data.records)
that.workitems = response.data.data.records that.workitems = response.data.data.records
for (let workitem of that.workitems) { for (let workitem of that.workitems) {
workitem['editable'] = that.projectAccessLevel < 3 workitem['editable'] = !that.project.completed&& that.projectAccessLevel < 3
workitem['hasChildren'] = workitem.childrenCount !== 0 workitem['hasChildren'] = workitem.childrenCount !== 0
workitem['isRoot'] = true workitem['isRoot'] = true
//console.log(workitem) //console.log(workitem)
@ -745,7 +746,7 @@ export default {
let records = response.data.data.records let records = response.data.data.records
const editable = row.editable || row.taskHolderId === that.$store.state.staff.staffId const editable = !that.project.completed&&( row.editable || row.taskHolderId === that.$store.state.staff.staffId)
for (let workitem of records) { for (let workitem of records) {
workitem['editable'] = editable workitem['editable'] = editable
workitem['hasChildren'] = workitem.childrenCount !== 0 workitem['hasChildren'] = workitem.childrenCount !== 0
@ -770,7 +771,7 @@ export default {
}).then(response => { }).then(response => {
if (response.data.code === 200) { if (response.data.code === 200) {
let records = response.data.data.records let records = response.data.data.records
const editable = row.editable || row.taskHolderId === this.$store.state.staff.staffId const editable = !that.project.completed&&(row.editable || row.taskHolderId === this.$store.state.staff.staffId)
for (let workitem of records) { for (let workitem of records) {
workitem['editable'] = editable workitem['editable'] = editable
workitem['hasChildren'] = workitem.childrenCount !== 0 workitem['hasChildren'] = workitem.childrenCount !== 0