Compare commits

..

25 Commits
main ... v1.2.0

Author SHA1 Message Date
rdpnr_hemingxia bc1c74ce55 完善修改,版本完成 2025-04-21 17:54:57 +08:00
rdpnr_hemingxia 659108a7ac bug修改 2025-03-28 17:51:53 +08:00
rdpnr_hemingxia cf743c0dd6 日志完成 2025-03-27 15:47:59 +08:00
rdpnr_hemingxia da50ed138f 日志完成80% 2025-03-26 17:58:58 +08:00
rdpnr_hemingxia f9a402e991 需求页面基本完成,日志初步开始 2025-03-25 17:58:19 +08:00
rdpnr_hemingxia 6032c814e2 需求管理页面 2025-03-21 18:01:08 +08:00
rdpnr_hemingxia 338b1ef8bd 项目管理调整,需求管理起步编写 2025-03-21 18:00:39 +08:00
‘wangjiuyun 5327adc0ea 人员绩效表排序调整 2025-01-23 11:40:23 +08:00
‘wangjiuyun 6160798087 分数展示问题判断 2025-01-22 17:56:44 +08:00
‘wangjiuyun e7a69e87f8 人员绩效表排序问题,绩效详情状态判断问题 2025-01-22 11:13:08 +08:00
‘wangjiuyun 3a7e373394 选人调整。样式调整 2025-01-10 11:55:36 +08:00
‘wangjiuyun 829acc5857 上线完成 2025-01-09 18:06:19 +08:00
‘wangjiuyun 74de4ef76c 样式修改基本完成 2025-01-08 18:36:18 +08:00
‘wangjiuyun 8d02e76518 任务设置问题修改 2025-01-08 16:37:48 +08:00
‘wangjiuyun 7532d5cf47 接口调试基本完成,选人多选问题 2025-01-06 17:52:52 +08:00
‘wangjiuyun 434a20f0a6 除指标配置,调试基本完成 2025-01-06 12:35:35 +08:00
‘wangjiuyun 43492eb5d2 绩效考核调试基本完成
剩余任务编辑,指标配置
人员绩效表
2025-01-03 18:00:18 +08:00
‘wangjiuyun 4d62c1baba 考核静态开发完成80% 2025-01-02 17:57:15 +08:00
‘wangjiuyun 0392aab0e1 人员图标修改 2024-11-07 16:53:35 +08:00
‘wangjiuyun 35a32950a7 项目执行表滚动条调整 2024-11-07 14:21:54 +08:00
‘wangjiuyun bfa923b3ac 工作日志展示进度
项目执行表增加总计时间,增加状态筛选
2024-11-06 17:49:10 +08:00
‘wangjiuyun c8bb8ef089 1.1删除功能 2024-11-06 10:04:43 +08:00
‘wangjiuyun 90867530d3 人员项目表默认用户调整 2024-10-22 17:55:19 +08:00
‘wangjiuyun 5f860e2fed 基本完成1.0 2024-10-22 09:46:54 +08:00
‘wangjiuyun ecacb4dc02 样式调整 2024-10-17 18:00:03 +08:00
38 changed files with 6500 additions and 304 deletions

1
.gitignore vendored
View File

@ -21,4 +21,3 @@ selenium-debug.log
package-lock.json
yarn.lock
/npm

View File

@ -54,7 +54,6 @@
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"vue": "2.6.12",
"vue-cli": "^2.9.6",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-meta": "2.4.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

View File

@ -1,5 +1,4 @@
@use 'sass:math';
@use "sass:math";
* {
box-sizing: border-box;
@ -9,7 +8,7 @@
-webkit-user-select: text;
-ms-user-select: text;
user-select: text;
font-family: AliPuHui;
font-family: PingFang SC;
}
$lightThemColor: #3686ff;
html {
@ -105,11 +104,6 @@ body {
align-items: flex-start;
}
//
.ml10 {
margin-left: 0.1rem;
@ -179,7 +173,6 @@ body {
white-space: nowrap;
}
/* 竖向弹性盒子 */
.flex-col {
@include flex-row-vc;
@ -256,7 +249,6 @@ body {
}
}
// dialogbody,
.el-popup-parent--hidden {
width: 100% !important;
@ -286,7 +278,6 @@ body {
border-radius: 0.1rem;
}
:deep(.el-form-item__content) {
align-items: flex-start !important;
}
@ -306,7 +297,6 @@ body {
padding-right: 15px;
}
:deep(.el-button--primary:not(.is-text)) {
background-color: #5584ff !important ;
color: #fff !important;
@ -322,3 +312,8 @@ body {
:deep(.el-dialog--center .el-dialog__body) {
padding: 32px !important;
}
.search-buttons {
position: absolute;
right: 20px;
}

View File

@ -289,3 +289,5 @@
position: relative;
float: right;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -9,10 +9,19 @@
:border="border"
:highlight-current-row="highligt"
@row-click="rowClick"
@sort-change="sortChange"
:row-key="rowKey"
:height="tableHeight"
@select="selected"
@select-all="selectAll"
>
<el-table-column v-if="showSelection" type="selection" width="55" />
<el-table-column
reserve-selection
v-if="showSelection"
type="selection"
width="55"
:selectable="selectable"
/>
<el-table-column v-if="showIndex" type="index" width="50" label="序号" />
<template>
<el-table-column
@ -21,6 +30,7 @@
:label="column.label"
v-for="(column, index) in columns"
:key="index"
:sortable="column.sortable"
>
<template #default="scope">
<slot :name="column.prop" :row="scope.row">
@ -146,6 +156,10 @@ export default {
type: String,
default: "100%",
},
selectable:{
type: Function,
default: ()=>true,
}
},
data() {
return {
@ -170,7 +184,7 @@ export default {
this.$emit("current-change", val);
},
setCurrentRow(row) {
this.$refs.elTableRef.setCurrentRow(row);
this.$refs.elTableRef?.setCurrentRow(row);
},
clearSelection() {
this.$refs.elTableRef?.clearSelection();
@ -178,12 +192,22 @@ export default {
toggleRowSelection(row, selected) {
this.$refs.elTableRef?.toggleRowSelection(row, selected);
},
sortChange(data) {
this.$emit("sortChange", data);
},
selected(arr, row) {
this.$emit("selected", { arr, row });
},
selectAll(arr) {
this.$emit("selectAll", arr);
},
},
updated() {
if (this.$refs.elTableRef && this.$refs.elTableRef.doLayout) {
this.$refs.elTableRef.doLayout();
}
},
mounted() {},
beforeDestroy() {},
};
@ -208,12 +232,17 @@ export default {
--el-table-text-align: center;
width: 100%;
flex: none;
font-weight: 600;
font-size: 14px;
}
::v-deep .el-table th {
text-align: center;
}
::v-deep .operation-column .el-button-text {
font-weight: 600 !important;
font-size: 14px !important;
}
::v-deep .el-table .cell {
text-align: center;
}
@ -239,4 +268,7 @@ export default {
::v-deep .el-table {
max-height: 99%;
}
::v-deep .el-table .el-table__body .el-table__cell {
padding: 15px 0 !important;
}
</style>

View File

@ -1,8 +1,18 @@
<template>
<el-dialog title="选择人员" :visible="dialogVisible" width="50%" @close="handleClose">
<el-dialog
title="选择人员"
:visible="dialogVisible"
width="50%"
@close="handleClose"
>
<div class="select-user-container">
<div class="org-tree">
<el-tree :data="treeData" :props="defaultProps" @node-click="handleNodeClick" default-expand-all />
<el-tree
:data="treeData"
:props="defaultProps"
@node-click="handleNodeClick"
default-expand-all
/>
</div>
<div class="user-list">
<CustomTable
@ -10,20 +20,36 @@
:columns="columns"
:tableData="userData"
:total="total"
:show-selection="true"
:show-selection="showSelection"
:show-index="true"
:table-height="tableHeight"
:multiSelect="multiSelect"
:selectable="selectable"
@selection-change="handleSelectionChange"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@selected="selectRow"
@selectAll="selectAll"
rowKey="userId"
:rowClick="
(row) => {
rowClick(row);
}
"
maxHeight="380"
:highligt="highligt"
>
<template slot="operation" slot-scope="{ row }">
<div class="operation-buttons">
<el-button text type="primary" @click="handleEdit(row)"></el-button>
<el-button text type="primary" @click="showTimesheet(row)"></el-button>
<el-button text type="danger" @click="handleDelete(row)"></el-button>
<el-button text type="primary" @click="handleEdit(row)"
>编辑</el-button
>
<el-button text type="primary" @click="showTimesheet(row)"
>工作日志</el-button
>
<el-button text type="danger" @click="handleDelete(row)"
>删除</el-button
>
</div>
</template>
</CustomTable>
@ -39,8 +65,8 @@
</template>
<script>
import CustomTable from '@/components/CustomTable.vue'
import { systemApi } from '@/utils/api'
import CustomTable from "@/components/CustomTable.vue";
import { systemApi } from "@/utils/api";
export default {
components: {
@ -53,12 +79,28 @@ export default {
},
dialogVisible: {
type: Boolean,
required: true,
default: true,
},
currentSelectedUser: {
type: Array,
default: () => [],
},
currentSelectedUserName: {
type: Array,
default: () => [],
},
showSelection: {
type: Boolean,
default: false,
},
highligt: {
type: Boolean,
default: true,
},
selectable: {
type: Function,
default: () => true,
},
},
data() {
return {
@ -66,106 +108,209 @@ export default {
pageSize: 10,
total: 0,
selectedUsers: [],
currentDepartment: '',
tableHeight: 350, //
currentDepartment: "",
tableHeight: "350", //
treeData: [],
defaultProps: {
children: 'children',
label: 'label',
children: "children",
label: "label",
},
columns: [
{ prop: 'nickName', label: '姓名' },
{ prop: 'dept', label: '部门', type: 'status', callback: (data) => data?.deptName },
{ prop: 'roles', label: '角色', type: 'status', callback: (data) => data.map(ele => ele.roleName).join(',') },
{ prop: "nickName", label: "姓名" },
{
prop: "dept",
label: "部门",
type: "status",
callback: (data) => data?.deptName,
},
{
prop: "roles",
label: "角色",
type: "status",
callback: (data) => data.map((ele) => ele.roleName).join(","),
},
],
userData: [],
customTableRef: null,
isInternalChange: false,
}
selectAllData: [],
};
},
emits: [ 'close', 'confirm'],
emits: ["close", "confirm"],
methods: {
handleNodeClick(data) {
this.currentDepartment = data.id
this.currentPage = 1
this.fetchUserList()
this.currentDepartment = data.id;
this.currentPage = 1;
this.fetchUserList();
},
handleCurrentChange(val) {
this.currentPage = val
this.fetchUserList()
this.currentPage = val;
this.fetchUserList();
},
handleSizeChange(val) {
this.pageSize = val
this.fetchUserList()
this.pageSize = val;
this.fetchUserList();
},
handleClose() {
this.$emit('close')
this.$emit("close");
},
handleConfirm() {
this.$emit('confirm', this.selectedUsers)
this.handleClose()
if (!this.showSelection) this.$emit("confirm", this.selectedUsers);
else
this.$emit(
"confirm",
this.selectedUsers.map((ele, index) => ({
userId: ele.userId,
nickName: ele.nickName,
}))
);
this.handleClose();
},
handleSelectionChange(val) {
if (this.isInternalChange) return
if (this.isInternalChange) return;
if (!this.multiSelect) {
this.isInternalChange = true
this.isInternalChange = true;
this.$nextTick(() => {
if (val.length > 0) {
const lastSelected = val[val.length - 1]
this.selectedUsers = [lastSelected]
this.$refs.customTableRef.clearSelection()
this.$refs.customTableRef.toggleRowSelection(lastSelected, true)
const lastSelected = val[val.length - 1];
this.selectedUsers = [lastSelected];
this.$refs.customTableRef.clearSelection();
this.$refs.customTableRef.toggleRowSelection(lastSelected, true);
} else {
this.selectedUsers = []
this.selectedUsers = [];
}
this.isInternalChange = false
})
this.isInternalChange = false;
});
} else {
this.selectedUsers = val
// this.selectedUsers = val;
}
},
selectRow({ arr, row }) {
if (!row) return;
if (this.selectedUsers.filter((ele) => ele.userId == row.userId).length) {
this.selectedUsers = this.selectedUsers.filter(
(ele) => ele.userId != row.userId
);
} else {
this.selectedUsers.push({ userId: row.userId, nickName: row.nickName });
}
},
selectAll(arr) {
let filterArr = this.selectAllData.filter((ele) =>
!arr.some((item) => item.userId == ele.userId)
);
console.log(filterArr,11);
arr.forEach((ele) => {
if (
!this.selectedUsers.filter((item) => item.userId == ele.userId).length
)
this.selectRow({ row: ele });
});
filterArr.forEach((ele) => {
this.selectRow({ row: ele});
});
},
fetchUserList: async function () {
const response = await systemApi.getUserList({
pageNum: this.currentPage,
pageSize: this.pageSize,
deptId: this.currentDepartment,
})
this.userData = response.rows
this.total = response.total
});
this.userData = response.rows;
this.total = response.total;
},
fetchTreeData: async function () {
const response = await systemApi.getDeptTree()
this.treeData = response.data
const response = await systemApi.getDeptTree();
this.treeData = response.data;
},
currentRow(val) {
this.selectedUsers = [val];
},
rowClick(row) {
if (!this.showSelection) {
if (row.userId == this.selectedUsers[0]?.userId)
this.$refs.customTableRef.$refs.elTableRef.setCurrentRow();
else this.selectedUsers = [row];
}
},
},
watch: {
currentSelectedUser: {
handler(newVal) {
this.isInternalChange = true
this.$nextTick(() => {
this.selectedUsers = newVal
if (this.$refs.customTableRef) {
this.$refs.customTableRef.clearSelection()
newVal.forEach(user => {
const row = this.userData.find(item => item.userId === user.userId)
if (!this.showSelection) {
let row = this.userData.find(
(ele) => ele.userId == newVal[0]?.userId
);
if (row) {
this.$refs.customTableRef.toggleRowSelection(row, true)
this.$refs.customTableRef?.setCurrentRow(row);
this.selectedUsers = [row];
} else {
this.selectedUsers = [];
this.$refs.customTableRef?.setCurrentRow();
}
})
} else {
if (!newVal.length) {
this.selectedUsers = [];
this.$refs.customTableRef?.clearSelection();
} else {
// this.$refs.customTableRef?.clearSelection();
this.selectedUsers = [];
newVal.forEach((item, index) => {
this.selectedUsers.push({
userId: item,
nickName: this.currentSelectedUserName[index],
});
let row = this.userData.find((ele) => ele.userId == item);
if (row) {
this.selectAllData.push(row);
this.$refs.customTableRef?.toggleRowSelection(row, true);
}
this.isInternalChange = false
})
});
}
}
});
},
immediate: true,
deep: true,
},
userData: {
handler(newVal) {
this.$nextTick(() => {
if (!this.showSelection) {
let row = this.userData.find(
(ele) => ele.userId == this.currentSelectedUser[0]?.userId
);
if (row) {
this.selectedUsers = [row];
this.$refs.customTableRef?.setCurrentRow(row);
}
} else {
this.selectAllData = [];
this.currentSelectedUser.forEach((item) => {
let row = newVal.find((ele) => ele.userId == item);
if (row) {
this.selectAllData.push(row);
this.$refs.customTableRef?.toggleRowSelection(row, true);
}
});
}
});
},
immediate: true,
deep: true,
},
},
mounted() {
this.fetchTreeData()
this.fetchUserList()
this.fetchTreeData();
this.fetchUserList();
},
}
};
</script>
<style lang="scss" scoped>
@ -206,6 +351,10 @@ export default {
margin-left: 0;
}
::v-deep .el-table {
max-height:360px !important;
max-height: 360px !important;
}
::v-deep tr.el-table__row.current-row .el-table__cell {
background-color: #409eff !important;
color: #fff;
}
</style>

View File

@ -63,8 +63,8 @@ export default {
}
::-webkit-scrollbar {
width: 15px;
height: 15px;
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
@ -72,7 +72,7 @@ export default {
}
::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
background-color: #c8c8c8;
border-radius: 5px;
}
</style>

View File

@ -1,36 +1,60 @@
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
<scroll-pane
ref="scrollPane"
class="tags-view-wrapper"
@scroll="handleScroll"
>
<router-link
v-for="tag in visitedViews"
ref="tag"
:key="tag.path"
:class="isActive(tag)?'active':''"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
@contextmenu.prevent.native="openMenu(tag,$event)"
@click.middle.native="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent.native="openMenu(tag, $event)"
>
{{ tag.title }}
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
<span
v-if="!isAffix(tag)"
class="el-icon-close"
@click.prevent.stop="closeSelectedTag(tag)"
/>
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)"><i class="el-icon-refresh-right"></i> 刷新页面</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><i class="el-icon-close"></i> </li>
<li @click="closeOthersTags"><i class="el-icon-circle-close"></i> 关闭其他</li>
<li v-if="!isFirstView()" @click="closeLeftTags"><i class="el-icon-back"></i> </li>
<li v-if="!isLastView()" @click="closeRightTags"><i class="el-icon-right"></i> </li>
<li @click="closeAllTags(selectedTag)"><i class="el-icon-circle-close"></i> 全部关闭</li>
<ul
v-show="visible"
:style="{ left: left + 'px', top: top + 'px' }"
class="contextmenu"
>
<li @click="refreshSelectedTag(selectedTag)">
<i class="el-icon-refresh-right"></i> 刷新页面
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<i class="el-icon-close"></i> 关闭当前
</li>
<li @click="closeOthersTags">
<i class="el-icon-circle-close"></i> 关闭其他
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<i class="el-icon-back"></i> 关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<i class="el-icon-right"></i> 关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<i class="el-icon-circle-close"></i> 全部关闭
</li>
</ul>
</div>
</template>
<script>
import ScrollPane from './ScrollPane'
import path from 'path'
import ScrollPane from "./ScrollPane";
import path from "path";
export default {
components: { ScrollPane },
@ -40,201 +64,207 @@ export default {
top: 0,
left: 0,
selectedTag: {},
affixTags: []
}
affixTags: [],
};
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
return this.$store.state.tagsView.visitedViews;
},
routes() {
return this.$store.state.permission.routes
return this.$store.state.permission.routes;
},
theme() {
return this.$store.state.settings.theme;
}
},
},
watch: {
$route() {
this.addTags()
this.moveToCurrentTag()
this.addTags();
this.moveToCurrentTag();
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
document.body.addEventListener("click", this.closeMenu);
} else {
document.body.removeEventListener('click', this.closeMenu)
}
document.body.removeEventListener("click", this.closeMenu);
}
},
},
mounted() {
this.initTags()
this.addTags()
this.initTags();
this.addTags();
},
methods: {
isActive(route) {
return route.path === this.$route.path
return route.path === this.$route.path;
},
activeStyle(tag) {
if (!this.isActive(tag)) return {};
return {
"background-color": this.theme,
"border-color": this.theme
"border-color": this.theme,
};
},
isAffix(tag) {
return tag.meta && tag.meta.affix
return tag.meta && tag.meta.affix;
},
isFirstView() {
try {
return this.selectedTag.fullPath === '/index' || this.selectedTag.fullPath === this.visitedViews[1].fullPath
return (
this.selectedTag.fullPath === "/index" ||
this.selectedTag.fullPath === this.visitedViews[1].fullPath
);
} catch (err) {
return false
return false;
}
},
isLastView() {
try {
return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath
return (
this.selectedTag.fullPath ===
this.visitedViews[this.visitedViews.length - 1].fullPath
);
} catch (err) {
return false
return false;
}
},
filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach(route => {
filterAffixTags(routes, basePath = "/") {
let tags = [];
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
const tagPath = path.resolve(basePath, route.path);
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
meta: { ...route.meta },
});
}
if (route.children) {
const tempTags = this.filterAffixTags(route.children, route.path)
const tempTags = this.filterAffixTags(route.children, route.path);
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
tags = [...tags, ...tempTags];
}
}
})
return tags
});
return tags;
},
initTags() {
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
const affixTags = (this.affixTags = this.filterAffixTags(this.routes));
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
this.$store.dispatch('tagsView/addVisitedView', tag)
this.$store.dispatch("tagsView/addVisitedView", tag);
}
}
},
addTags() {
const { name } = this.$route
const { name } = this.$route;
if (name) {
this.$store.dispatch('tagsView/addView', this.$route)
this.$store.dispatch("tagsView/addView", this.$route);
if (this.$route.meta.link) {
this.$store.dispatch('tagsView/addIframeView', this.$route)
this.$store.dispatch("tagsView/addIframeView", this.$route);
}
}
return false
return false;
},
moveToCurrentTag() {
const tags = this.$refs.tag
const tags = this.$refs.tag;
this.$nextTick(() => {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
this.$refs.scrollPane.moveToTarget(tag)
this.$refs.scrollPane.moveToTarget(tag);
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
this.$store.dispatch("tagsView/updateVisitedView", this.$route);
}
break
break;
}
}
})
});
},
refreshSelectedTag(view) {
this.$tab.refreshPage(view);
if (this.$route.meta.link) {
this.$store.dispatch('tagsView/delIframeView', this.$route)
this.$store.dispatch("tagsView/delIframeView", this.$route);
}
},
closeSelectedTag(view) {
this.$tab.closePage(view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
this.toLastView(visitedViews, view);
}
})
});
},
closeRightTags() {
this.$tab.closeRightPage(this.selectedTag).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
this.toLastView(visitedViews)
this.$tab.closeRightPage(this.selectedTag).then((visitedViews) => {
if (!visitedViews.find((i) => i.fullPath === this.$route.fullPath)) {
this.toLastView(visitedViews);
}
})
});
},
closeLeftTags() {
this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
this.toLastView(visitedViews)
this.$tab.closeLeftPage(this.selectedTag).then((visitedViews) => {
if (!visitedViews.find((i) => i.fullPath === this.$route.fullPath)) {
this.toLastView(visitedViews);
}
})
});
},
closeOthersTags() {
this.$router.push(this.selectedTag.fullPath).catch(()=>{});
this.$router.push(this.selectedTag.fullPath).catch(() => {});
this.$tab.closeOtherPage(this.selectedTag).then(() => {
this.moveToCurrentTag()
})
this.moveToCurrentTag();
});
},
closeAllTags(view) {
this.$tab.closeAllPage().then(({ visitedViews }) => {
if (this.affixTags.some(tag => tag.path === this.$route.path)) {
return
if (this.affixTags.some((tag) => tag.path === this.$route.path)) {
return;
}
this.toLastView(visitedViews, view)
})
this.toLastView(visitedViews, view);
});
},
toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
const latestView = visitedViews.slice(-1)[0];
if (latestView) {
this.$router.push(latestView.fullPath)
this.$router.push(latestView.fullPath);
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
if (view.name === "Dashboard") {
// to reload home page
this.$router.replace({ path: '/redirect' + view.fullPath })
this.$router.replace({ path: "/redirect" + view.fullPath });
} else {
this.$router.push('/')
this.$router.push("/");
}
}
},
openMenu(tag, e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 15 // 15: margin right
const menuMinWidth = 105;
const offsetLeft = this.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = this.$el.offsetWidth; // container width
const maxLeft = offsetWidth - menuMinWidth; // left boundary
const left = e.clientX - offsetLeft + 15; // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
this.left = maxLeft;
} else {
this.left = left
this.left = left;
}
this.top = e.clientY
this.visible = true
this.selectedTag = tag
this.top = e.clientY;
this.visible = true;
this.selectedTag = tag;
},
closeMenu() {
this.visible = false
this.visible = false;
},
handleScroll() {
this.closeMenu()
}
}
}
this.closeMenu();
},
},
};
</script>
<style lang="scss" scoped>
@ -243,7 +273,7 @@ export default {
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
.tags-view-wrapper {
.tags-view-item {
display: inline-block;
@ -269,7 +299,7 @@ export default {
color: #fff;
border-color: #42b983;
&::before {
content: '';
content: "";
background: #fff;
display: inline-block;
width: 8px;
@ -292,7 +322,7 @@ export default {
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
li {
margin: 0;
padding: 7px 16px;
@ -315,10 +345,10 @@ export default {
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all .3s cubic-bezier(.645, .045, .355, 1);
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%;
&:before {
transform: scale(.6);
transform: scale(0.6);
display: inline-block;
vertical-align: -3px;
}

View File

@ -87,6 +87,10 @@ export const workLogApi = {
method: 'put',
data: data,
}),
delLog: (id) => request({
url: `/business/work/hour/${id}`,
method: 'delete',
}),
}
@ -130,3 +134,113 @@ export const systemApi = {
method: 'get',
}),
}
// 任务考核板块
export const taskApi = {
getTaskUserList: (data) => request({
url: '/examine/user',
method: 'get',
params: data,
}),
getTaskScoreDetail: (data) => request({
url: '/examine/detail',
method: 'get',
params: data,
}),
saveTaskUserScore: (data) => request({
url: '/examine/detail/batch',
method: 'post',
data: data,
}),
getTaskListSelf: (data) => request({
url: '/task/list',
method: 'get',
params: data,
}),
getTaskListSelfNormal: (data) => request({
url: '/task/listSelf',
method: 'get',
params: data,
}),
getTaskList: (data) => request({
url: '/task/get',
method: 'get',
params: data,
}),
addTask: (data) => request({
url: '/task/add',
method: 'post',
data: data,
}),
upDateTask: (data) => request({
url: '/task/update',
method: 'put',
data: data,
}),
delTask: (id) => request({
url: `/task/${id}`,
method: 'delete',
}),
getTaskSet: (id) => request({
url: `/task/target/${id}`,
method: 'get',
}),
setTaskSet: (data) => request({
url: `/task/config/update`,
method: 'put',
data: data,
}),
}
export const demandApi = {
getVersionTree: (data) => request({
url: `/projectVersion/tree`,
method: 'get',
params: data,
}),
addVersion: (data) => request({
url: '/projectVersion/insert',
method: 'post',
data: data,
}),
// 删除版本号
delVersion: (data) => request({
url: `/projectVersion/${data}`,
method: 'delete',
}),
editVersion: (data) => request({
url: '/projectVersion/update',
method: 'put',
data: data,
}),
getDemandList: (data) => request({
url: `/demand/list`,
method: 'get',
params: data,
}),
addDemand: (data) => request({
url: '/demand/insert',
method: 'post',
data: data,
}),
eidtDemand: (data) => request({
url: '/demand/update',
method: 'put',
data: data,
}),
delDemand: (data) => request({
url: `/demand/${data}`,
method: 'delete',
}),
delDemandBatch: (data) => request({
url: `/demand/remove/batch/${data}`,
method: 'delete',
}),
}

View File

@ -79,7 +79,7 @@
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2024 ruoyi.vip All Rights Reserved.</span>
<span>unissense.tech</span>
</div>
</div>
</template>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,425 @@
<template>
<div
class="sidebarTree"
:class="{
hidenLeft: !showFlag,
}"
>
<i
class="el-icon-arrow-right"
style="font-size: 24px; margin-top: 20px; cursor: pointer"
v-show="!showFlag"
@click.stop="changeShow()"
></i>
<!-- 树形菜单 -->
<div class="treeTitle">版本列表</div>
<div class="topBox" v-show="showFlag">
<div class="topText" @click.stop="changeOpen">
<i v-show="openFlag" class="el-icon-caret-bottom"></i>
<i v-show="!openFlag" class="el-icon-caret-right"></i>
<img src="@/assets/demand/list.png" />
<span>全部版本</span>
</div>
<div class="topBtn">
<i
class="el-icon-plus"
@click.stop="dialogVisible = true"
style="font-size: 20px"
></i>
<i
class="el-icon-arrow-left"
style="font-size: 20px"
@click.stop="changeShow()"
></i>
</div>
</div>
<el-tree
v-show="showFlag"
:data="treeData"
:props="defaultProps"
:default-expanded-keys="defaultExpend"
node-key="nodeId"
class="tree"
ref="treeRef"
@node-click="handleNodeClick"
:expand-on-click-node="false"
>
<template #default="{ node, data }">
<div
class="treeNode"
@mousemove="data.hover = true"
@mouseout="data.hover = false"
>
<!-- 展开/收起图标 -->
<!-- 节点图标 -->
<img
v-if="data.type == 0 && data.nodeId === selectedId"
src="@/assets/demand/treeIcon.png"
class="nodeIcon"
/>
<img
v-if="data.type == 0 && data.nodeId !== selectedId"
src="@/assets/demand/treeIcon1.png"
class="nodeIcon"
/>
<!-- 节点文本 -->
<span
:class="['nodeLabel', data.nodeId === selectedId ? 'selected' : '']"
>
{{ data.title }}
</span>
<!-- 右侧数字标记 -->
<span
v-if="data.type == 0 && !data.hover"
:class="[
'count',
data.nodeId === selectedId ? 'selectedCount' : '',
]"
>
{{ data.childrenList.length }}
</span>
<el-dropdown
v-show="data.type == 0 && data.hover"
trigger="click"
placement="bottom"
@command="
(val) => {
changeRow(val, data);
}
"
>
<i class="el-icon-more" style="font-size: 20px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="add">新建需求</el-dropdown-item>
<el-dropdown-item command="edit">编辑</el-dropdown-item>
<el-dropdown-item command="del" style="color: #dd242a"
>删除</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
<!-- 右侧操作图标 -->
<img
v-if="data.actionIcon"
:src="data.actionIcon"
class="actionIcon"
/>
</div>
</template>
</el-tree>
<el-dialog title="添加版本号" :visible.sync="dialogVisible" width="30%">
<el-form>
<el-form-item label="版本号">
<el-input v-model="demandData.name"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmAddNode"></el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { demandApi } from "@/utils/api";
export default {
name: "SidebarTree",
props: {
projectId: {
type: String,
default: "",
},
},
data() {
return {
selectedId: "",
treeData: [],
defaultProps: {
children: "childrenList",
label: "title",
},
dialogVisible: false,
demandData: {
name: "",
},
openFlag: false,
showFlag: true,
defaultExpend: [],
};
},
methods: {
handleNodeClick(data) {
this.selectedId = data.nodeId;
this.changeVersion(data);
},
getVersionTree(defaultId) {
demandApi
.getVersionTree({
projectId: this.projectId,
demandStatusList: [],
})
.then((res) => {
if (!res.data.length) {
this.treeData=[]
return;
}
res.data = res.data.map((ele) => {
ele.nodeId = ele.id + "_" + ele.type;
ele.childrenList.map((item) => {
item.nodeId = item.id + "_" + item.type;
});
ele.hover = false;
return ele;
});
this.initTableData(defaultId, res.data);
this.treeData = res.data;
});
},
changeVersion(data) {
if (!data) {
data = {
id: this.projectId,
type: 2,
};
}
this.$emit("changeVersion", data);
},
initTableData(defaultId, tableList) {
if (defaultId && defaultId != "all") {
let searchNode = {};
this.defaultExpend = [defaultId];
searchNode = tableList.find((ele) => ele.nodeId == defaultId);
this.changeVersion(searchNode);
this.selectedId = this.defaultExpend[0];
} else if (defaultId == "all") {
this.selectedId = "";
this.changeVersion({
id: this.projectId,
type: 2,
});
} else {
this.defaultExpend = [tableList[0].nodeId];
this.selectedId = this.defaultExpend[0];
this.setVersionList(tableList.filter((ele) => ele.type == 0));
this.changeVersion(
tableList.find((ele) => ele.nodeId == this.defaultExpend[0])
);
}
},
setVersionList(data) {
this.$emit("setVersionList", data);
},
confirmAddNode() {
if (!this.demandData.name) {
this.$message({
message: "请填写版本号",
type: "warning",
});
return;
}
let param = {
projectId: this.projectId,
versionNumber: this.demandData.name,
};
if (this.demandData.id) {
param.id = this.demandData.id;
demandApi.editVersion(param).then((res) => {
this.resetAdd(0);
});
} else {
demandApi.addVersion(param).then((res) => {
this.resetAdd(1);
});
}
},
resetAdd(add) {
this.$message({
message: add ? "添加成功" : "修改成功",
type: "success",
});
this.dialogVisible = false;
this.demandData.name = "";
this.demandData.id = "";
this.getVersionTree();
},
editDemand(data) {
this.demandData = {
name: data.title,
id: data.id,
};
this.dialogVisible = true;
},
delVersion(data) {
this.$confirm("此操作将永久删除该版本号, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
demandApi.delVersion(data.id).then((res) => {
this.$message({
type: "success",
message: "删除成功!",
});
this.getVersionTree();
});
})
.catch(() => {});
},
changeOpen() {
this.openFlag = !this.openFlag;
if (this.openFlag) {
this.defaultExpend = this.treeData.map((ele) => ele.nodeId);
} else {
this.defaultExpend = [];
}
this.getVersionTree("all");
},
changeShow() {
this.showFlag = !this.showFlag;
},
changeRow(type, row) {
if (type == "add") {
this.$emit("addDemand");
} else if (type == "edit") {
this.editDemand(row);
} else if (type == "del") {
this.delVersion(row);
}
},
},
watch: {
projectId(newVal) {
this.$nextTick(() => {
this.getVersionTree();
});
},
},
};
</script>
<style scoped lang="scss">
.sidebarTree {
width: 100%;
max-width: 300px;
min-height: 100vh;
background-color: inherit;
padding-left: 20px;
}
.hidenLeft {
width: 40px;
}
.tree {
width: 100%;
height: 100%;
}
.treeNode {
display: flex;
align-items: center;
width: 100%;
height: 48px;
padding: 0 20px;
border-radius: 2px;
}
::v-deep .el-tree-node__children {
padding-left: 15px !important;
}
.expandIcon {
width: 10px;
height: 10px;
margin-right: 15px;
}
.nodeIcon {
width: 18px;
height: 18px;
margin-right: 15px;
}
.nodeLabel {
flex: 1;
font-family: "PingFang SC";
font-size: 16px;
font-weight: 600;
line-height: 36px;
color: #333;
}
.nodeLabel.selected {
color: #4096ff;
}
.count {
font-family: "PingFang SC";
font-size: 16px;
font-weight: 600;
line-height: 26px;
color: #999;
margin-right: 15px;
}
.selectedCount {
color: #4096ff;
}
.actionIcon {
width: 18px;
height: 18px;
}
.treeTitle {
padding: 20px;
font-size: 18px;
font-weight: 600;
color: #333;
}
/* 选中状态背景色 */
// .treeNode:has(.selected) {
// background-color: #f6faff;
// }
::v-deep .el-tree-node {
min-height: 42px;
.el-tree-node__content {
height: 42px;
}
:not(.is-leaf.el-tree-node__expand-icon) {
font-size: 16px;
// color: #333;
}
}
.topBox {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.topText {
gap: 15px;
font-size: 16px;
font-weight: 600;
display: flex;
cursor: pointer;
img {
width: 18px;
height: 18px;
}
}
.topBtn {
display: flex;
gap: 10px;
i {
cursor: pointer;
}
}
}
// /* <EFBFBD><EFBFBD> */
// .treeNode:hover {
// background-color: #f5f5f5;
// }
</style>

View File

@ -0,0 +1,94 @@
<template>
<div class="layout">
<!-- 左侧树形菜单 -->
<SidebarTree
:projectId="projectId"
class="sidebar"
@changeVersion="changeVersion"
@setVersionList="setVersionList"
@addDemand="addDemand"
ref="treeRef"
/>
<!-- 右侧表格和筛选 -->
<MainContentTable
:projectId="projectId"
class="main-content"
:version="version"
:projectName="projectName"
@refreshTree="refreshTree"
:versionList="versionList"
:minDate="minDate"
:maxDate="maxDate"
ref="rightRef"
/>
</div>
</template>
<script>
//
import SidebarTree from "./components/SidebarTree.vue";
import MainContentTable from "./components/MainContentTable.vue";
export default {
name: "demandManage",
components: {
SidebarTree,
MainContentTable,
},
data() {
return {
projectId: "",
projectName: "",
versionList: [],
version: {},
minDate: "",
maxDate: "",
};
},
methods: {
changeVersion(data) {
this.version = data;
},
setVersionList(data) {
this.versionList = data;
},
refreshTree(defaultId) {
this.$refs.treeRef.getVersionTree(defaultId);
},
addDemand() {
this.$refs.rightRef.handleAdd();
},
init() {
this.projectId = this.$route.query.id;
this.projectName = this.$route.query.projectName;
this.minDate = this.$route.query.startDate;
this.maxDate = this.$route.query.endDate;
},
},
mounted() {
this.init();
},
};
</script>
<style scoped lang="scss">
.layout {
display: flex;
width: 100%;
min-height: 100vh;
background-color: #f5f7fa;
}
.sidebar {
flex-grow: 1;
max-width: 300px;
height: 100%;
background-color: #fff;
}
.main-content {
flex-grow: 3;
background-color: #ffffff;
padding: 20px;
}
</style>

View File

@ -37,15 +37,21 @@
readonly
:disabled="isEditing"
@click.native="openProjectManagerSelect"
/>
>
<el-button
size="mini"
slot="append"
icon="el-icon-s-custom"
></el-button
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="项目状态" prop="projectState">
<el-form-item label="数据状态" prop="dataState" style="display: none">
<el-select
v-model="formData.projectState"
v-model="formData.dataState"
placeholder="根据时间自动生成"
disabled
class="full-width"
@ -55,6 +61,20 @@
<el-option label="已完成" value="2" />
</el-select>
</el-form-item>
<el-form-item label="项目状态" prop="projectState">
<el-select
v-model="formData.projectState"
placeholder="请选择项目状态"
class="full-width"
>
<el-option
v-for="item in statusList"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预计人天" prop="budgetDate">
@ -123,7 +143,7 @@
:show-pagination="false"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="height: 100%;"
style="height: 100%"
>
<template slot="userName" slot-scope="{ row }">
<el-input
@ -230,10 +250,9 @@
/>
<SelectUser
v-if="showProjectManagerSelect"
:dialogVisible="showProjectManagerSelect"
:multi-select="false"
:selected-users="projectManagerSelectedUser"
:currentSelectedUser="projectManagerSelectedUser"
@confirm="handleProjectManagerConfirm"
@close="closeProjectManagerSelect"
/>
@ -268,8 +287,10 @@ export default {
endDate: "",
budgetDate: 0,
state: 0,
dataState: 0,
},
postOptions: [],
statusList: [],
columns: [
{ prop: "userName", label: "姓名" },
{ prop: "post", label: "项目职位" },
@ -287,8 +308,12 @@ export default {
projectLeader: [
{ required: true, message: "项目负责人为必填", trigger: "change" },
],
projectState: [
{ required: true, message: "请选择项目状态", trigger: "change" },
],
startDate: [
{ required: true, message: "开始日期为必填", trigger: "change" },
{ validator: this.validateDates, trigger: "change" },
],
endDate: [
{ required: true, message: "结束日期为必填", trigger: "change" },
@ -296,7 +321,6 @@ export default {
],
budgetDate: [
{ required: true, message: "预算天数为必填", trigger: "blur" },
{ validator: this.validateDates, trigger: "change" },
],
},
};
@ -319,11 +343,11 @@ export default {
if (start && end) {
if (now < start) {
this.formData.projectState = "0"; //
this.formData.dataState = "0"; //
} else if (now >= start && now <= end) {
this.formData.projectState = "1"; //
this.formData.dataState = "1"; //
} else {
this.formData.projectState = "2"; //
this.formData.dataState = "2"; //
}
}
},
@ -351,10 +375,10 @@ export default {
const projectDataToSave = {
...this.formData,
startDate: this.formData.startDate
? new Date(this.formData.startDate+' 00:00:00').getTime()
? new Date(this.formData.startDate + " 00:00:00").getTime()
: null,
endDate: this.formData.endDate
? new Date(this.formData.endDate+' 23:59:59').getTime()
? new Date(this.formData.endDate + " 23:59:59").getTime()
: null,
};
@ -384,7 +408,7 @@ export default {
}
} else {
this.$modal.msgError("请检查表单填写是否正确");
this.$modal.msgSuccess("操作成功");
// this.$modal.msgSuccess("");
}
});
},
@ -405,10 +429,10 @@ export default {
isNew: true,
userId: "",
isEditing: true,
index: this.tableData.length,
index: this.tableData.length - 1,
};
this.currentEditingRow = newUser;
this.tableData.push(newUser);
this.tableData.unshift(newUser);
},
confirmAddUser(row) {
if (!this.formData.projectId) {
@ -439,13 +463,15 @@ export default {
});
},
cancelAddUser(row) {
const index = this.tableData.findIndex(
(item) => item.userId === row.userId
);
if (index !== -1) {
if (row.index != undefined) {
let index = this.tableData.findIndex((item) => item.index == row.index);
this.tableData.splice(index, 1);
} else {
this.tableData.splice(row.index, 1);
var index = this.tableData.findIndex(
(item) => item.userId === row.userId
);
this.tableData.splice(index, 1);
}
},
cancelUserEdit(row) {
@ -457,6 +483,8 @@ export default {
this.currentSelectedUser = row.userId
? [{ userId: row.userId, userName: row.userName }]
: [];
console.log(111, this.currentSelectedUser);
this.showSelectUser = true;
},
handleUserConfirm(selectedUsers) {
@ -478,8 +506,8 @@ export default {
this.projectManagerSelectedUser = this.formData.projectLeader
? [
{
id: this.formData.projectLeaderId,
name: this.formData.projectLeader,
userId: this.formData.projectLeader,
nickName: this.formData.projectLeaderName,
},
]
: [];
@ -566,7 +594,9 @@ export default {
},
async getDictData() {
const res = await systemApi.getDictData("business_positions");
const res2 = await systemApi.getDictData("business_projectstate");
this.postOptions = res.data;
this.statusList = res2.data;
},
async getProjectUser() {
const res = await projectApi.getProjectUser(this.formData.projectId);
@ -608,6 +638,7 @@ export default {
} else {
this.updateProjectState(); //
}
//
},
};
</script>
@ -662,7 +693,9 @@ export default {
height: 42px; /* 调高输入框高度 */
width: 80%;
}
.custom-form ::v-deep .el-input__inner {
height: 100%;
}
.custom-form ::v-deep .el-select {
width: 100%;
}

View File

@ -1,5 +1,15 @@
<template>
<div class="project-list">
<div class="table-actions mb10" style="text-align: right">
<el-button
type="primary"
size="mini"
@click="addProject"
v-hasPermi="['project:list:add']"
style="height: 30px"
>+ 新建项目</el-button
>
</div>
<div class="search-bar">
<el-form
:inline="true"
@ -16,32 +26,32 @@
placeholder="负责人"
readonly
@click.native="openUserSelectDialog"
/>
><el-button slot="append" icon="el-icon-s-custom"></el-button
></el-input>
</el-form-item>
<el-form-item label="项目状态" class="form-item">
<el-select v-model="searchForm.projectState" placeholder="项目状态">
<el-option label="全部" value="" />
<el-option label="未启动" value="0" />
<el-option label="进行中" value="1" />
<el-option label="已完成" value="2" />
<el-select
v-model="searchForm.projectState"
placeholder="项目状态"
clearable
>
<el-option
v-for="item in statusList"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item>
<el-form-item class="search-buttons">
<el-button type="primary" @click="onSearch"></el-button>
<el-button @click="onReset"></el-button>
<el-form-item class="formBtn">
<el-button type="primary" size="medium" @click="onSearch"
>查询</el-button
>
<el-button @click="onReset" size="medium">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="table-actions mb10">
<el-button
type="primary"
size="mini"
@click="addProject"
v-hasPermi="['project:list:add']"
>+ 新建项目</el-button
>
</div>
<div class="f1 df">
<CustomTable
:columns="columns"
@ -51,24 +61,31 @@
:show-index="true"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
tableHeight="600"
tableHeight="510px"
ref="customTableRef"
>
<template slot="operation" slot-scope="{ row }">
<div class="operation-buttons">
<el-button
@click="handleDemand(row)"
type="text"
size="mini"
v-hasPermi="['project:list:demand']"
>需求管理</el-button
>
<el-button
@click="handleEdit(row)"
type="text"
size="mini"
icon="el-icon-edit"
v-hasPermi="['project:list:eidt']"
>编辑</el-button
>
<el-button
type="text"
size="mini"
icon="el-icon-delete"
@click="handleDelete(row)"
v-hasPermi="['project:list:delete']"
style="color: #666"
>删除</el-button
>
</div>
@ -88,7 +105,7 @@
<script>
import CustomTable from "@/components/CustomTable.vue";
import SelectUser from "@/components/SelectUser.vue";
import { projectApi } from "@/utils/api";
import { projectApi, systemApi } from "@/utils/api";
export default {
components: {
@ -104,52 +121,57 @@ export default {
projectState: "",
},
columns: [
{ prop: "projectName", label: "项目名称" },
{ prop: "projectCode", label: "项目编号" },
{ prop: "projectName", label: "项目名称", width: 300 },
{ prop: "projectCode", label: "项目编号", width: 200 },
{ prop: "projectLeaderName", label: "负责人" },
{ prop: "budgetDate", label: "预计工时(天)" },
{
prop: "startDate",
label: "开始时间",
type: "status",
callback: (data) => data.split(" ")[0],
callback: (data) => data?.split(" ")[0],
},
{
prop: "endDate",
label: "结束时间",
type: "status",
callback: (data) => data.split(" ")[0],
callback: (data) => data?.split(" ")[0],
},
{
prop: "projectState",
label: "项目状态",
type: "status",
callback: (value) => {
let status = "未知";
let color = "";
switch (value) {
case "0":
status = "未启动";
color = "#909399"; //
let status =
this.statusList.find((ele) => ele.dictValue == value)
?.dictLabel || "";
let color = "#333";
switch (status) {
case "待启动":
color = "#999999"; //
break;
case "1":
status = "进行中";
color = "#409EFF"; //
case "进行中":
color = "#FF7D00"; //
break;
case "2":
status = "已完成";
color = "#67C23A"; // 绿
case "已完成":
color = "#50B6AA"; // 绿
break;
case "#50B6AA":
color = "#999999"; //
break;
default:
color = "#333"; //
break;
}
return `<span style="color: ${color}">${status}</span>`;
},
},
{ prop: "teamNum", label: "参与项目人数" },
{ prop: "teamNum", label: "参与项目人数", width: 100 },
{ prop: "createByName", label: "项目创建人" },
{
prop: "operation",
label: "操作",
width: "150",
width: "250",
fixed: "right",
className: "operation-column",
},
@ -160,17 +182,19 @@ export default {
currentSelectedUser: [],
pageNum: 1, //
pageSize: 10, //
statusList: [],
};
},
methods: {
onSearch() {
console.log("Search with:", this.searchForm);
this.fetchProjectList();
},
onReset() {
Object.keys(this.searchForm).forEach((key) => {
this.searchForm[key] = "";
});
this.currentSelectedUser = [];
this.$refs.customTableRef.handleCurrentChange(1);
this.fetchProjectList();
},
addProject() {
@ -178,6 +202,17 @@ export default {
path: "/project/detail",
});
},
handleDemand(row) {
this.$router.push({
path: "/demandManage",
query: {
id: row.projectId,
projectName: row.projectName,
startDate: new Date(row.startDate).getTime(),
endDate: new Date(row.endDate).getTime(),
},
});
},
handleEdit(row) {
this.$router.push({
path: "/project/detail",
@ -228,8 +263,13 @@ export default {
handleUserClose() {
this.userSelectDialogVisible = false;
},
async getDictData() {
const res = await systemApi.getDictData("business_projectstate");
this.statusList = res.data;
},
},
mounted() {
this.getDictData();
this.fetchProjectList();
},
};
@ -259,8 +299,16 @@ export default {
.demo-form-inline .el-form-item {
// margin-right: 50px; /* 30px */
margin-bottom: 0;
border: 1px solid #ccc;
padding-left: 10px;
border-radius: 4px;
::v-deep .el-form-item__label {
color: #999 !important;
}
}
.formBtn {
border: none !important;
}
.demo-form-inline .el-form-item:last-child {
margin-right: 0; /* 移除最后一个元素的右边距 */
}
@ -276,6 +324,10 @@ export default {
.form-item ::v-deep .el-input,
.form-item ::v-deep .el-select {
// width: 100%;
input,
select {
border: none !important;
}
}
.search-buttons {
@ -285,11 +337,13 @@ export default {
::v-deep .operation-buttons .el-button {
padding: 4px 8px;
margin: 0 2px;
font-weight: 600;
font-size: 14px;
}
::v-deep .operation-column {
background-color: #fff;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
box-shadow: -2px 0 5px rgba(241, 112, 6, 0.1);
}
.el-button.is-text {
@ -327,4 +381,8 @@ export default {
left: 50%;
transform: translate(-50%, -50%);
}
.search-buttons ::v-deep .el-button {
width: 90px !important;
height: 36px;
}
</style>

View File

@ -5,6 +5,22 @@
<div class="shadowBox"></div>
<div class="flex-row aic mb20">
<h2 class="textC">项目执行表</h2>
<div class="ml20">
项目状态
<el-select
v-model="projectState"
placeholder="项目状态"
@change="getProjectProgress"
clearable
>
<el-option
v-for="item in statusList"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</div>
<div class="date-range-container">
<span class="date-range-label">统计时间</span>
<el-date-picker
@ -20,7 +36,7 @@
/>
</div>
</div>
<div class="f1">
<div class="f1" style="position: relative">
<CustomTable
:columns="fixedColumns"
:tableData="executionData"
@ -28,19 +44,22 @@
:showSummary="true"
:summaryMethod="getFixedColumnsSummaries"
tableHeight="600"
:border="true"
></CustomTable>
<div id="scrollBox">
<div id="scrollBoxContent"></div>
</div>
</div>
</div>
<!-- 右侧滚动列表格 -->
</div>
</template>
<script>
import { projectBank } from "@/utils/api";
import { projectBank, systemApi } from "@/utils/api";
import CustomTable from "@/components/CustomTable.vue";
export default {
name: "ProjectProgress",
components: {
CustomTable,
},
@ -50,6 +69,8 @@ export default {
dateRange: this.getDefaultDateRange(),
fixedColumns: [],
executionData: [],
statusList: [],
projectState: "",
};
},
methods: {
@ -66,9 +87,25 @@ export default {
const res = await projectBank.porjectProgress({
startDate: this.dateRange[0] + " 00:00:00",
endDate: this.dateRange[1] + " 00:00:00",
projectState: this.projectState,
});
this.timeRange = [this.dateRange[0], this.dateRange[1]];
this.executionData = res.data;
this.$nextTick(() => {
setTimeout(() => {
let table = document.querySelector(
".el-table__body-wrapper .el-table__body"
);
let width = table.offsetWidth;
let tableBox = document.querySelector(".el-table__body-wrapper");
let box = document.getElementById("scrollBoxContent");
let scroll = document.getElementById("scrollBox");
box.style.width = width + "px";
scroll.addEventListener("scroll", (event) => {
tableBox.scrollLeft = scroll.scrollLeft;
});
},500);
});
},
getFixedColumnsSummaries(param) {
const { columns, data } = param;
@ -80,7 +117,7 @@ export default {
}
let values;
if (column.property == "detailList") {
values = data.map((item) => Number(item[column.property][index - 4]));
values = data.map((item) => Number(item[column.property][index - 5]));
} else if (column.property == "projectState") {
values = [];
} else {
@ -117,6 +154,10 @@ export default {
},
});
},
async getDictData() {
const res = await systemApi.getDictData("business_projectstate");
this.statusList = res.data;
},
},
watch: {
timeRange: {
@ -171,24 +212,38 @@ export default {
label: "项目状态",
type: "button",
fixed: "left",
width: 100,
width: 150,
callback: (value) => {
let status = "未知";
let color = "";
switch (value) {
case "0":
status = "未启动";
color = "#909399"; //
break;
case "1":
status = "进行中";
color = "#409EFF"; //
break;
case "2":
status = "已完成";
color = "#67C23A"; // 绿
break;
}
let status = this.statusList.find(
(ele) => ele.dictValue == value
)?.dictLabel;
let color = "#333";
// switch (status) {
// case "":
// color = "#fa721d"; //
// break;
// case "-":
// color = "#dd242a"; //
// break;
// case "-":
// color = "#1686d8"; // 绿
// break;
// case "-":
// color = "#5cb85c"; //
// break;
// case "-":
// color = "#f7c731"; //
// break;
// case "-":
// color = "#8C33FF"; //
// break;
// case "-":
// color = "#08fb9e"; // 绿
// break;
// default:
// color = "#000"; //
// break;
// }
return `<span style="color: ${color}">${status}</span>`;
},
},
@ -198,6 +253,12 @@ export default {
width: 100,
fixed: "left",
},
{
prop: "allDateWorkTime",
label: "总计工时\n",
width: 100,
fixed: "left",
},
{
prop: "allWorkTime",
label: "统计工时\n",
@ -210,6 +271,7 @@ export default {
},
},
mounted() {
this.getDictData();
this.getProjectProgress();
},
beforeDestroy() {},
@ -245,7 +307,7 @@ export default {
text-align: center;
}
::v-deep .el-table__footer td {
background-color: #c0c4cc !important;
background-color: #e0e1e3 !important;
font-weight: bold;
text-align: center; /* 确保合计行内容居中 */
}
@ -298,7 +360,7 @@ export default {
display: flex;
align-items: center;
width: 500px;
margin-left: 350px;
margin-left: 180px;
}
.date-range-label {
white-space: nowrap;
@ -322,8 +384,6 @@ export default {
}
::v-deep .el-table__fixed {
box-shadow: 5px 20px 20px rgba(7, 7, 7, 0.5) !important;
}
.shadowBox {
// position: absolute;
@ -333,4 +393,18 @@ export default {
// left: 450px;
// z-index: 100;
}
::v-deep .el-table__body-wrapper::-webkit-scrollbar {
display: none;
}
#scrollBox {
width: 100%;
overflow: auto;
position: absolute;
top: 590px;
#scrollBoxContent {
height: 20px;
}
}
</style>

View File

@ -60,6 +60,8 @@
:columns="scrollableColumns"
:tableData="executionData"
:showPagination="false"
:border="true"
tableHeight="600"
></CustomTable>
</div>
</div>
@ -70,6 +72,7 @@ import CustomTable from "@/components/CustomTable.vue";
import { projectBank, projectApi } from "@/utils/api";
export default {
name: "ProjectUser",
components: {
CustomTable,
},
@ -146,7 +149,6 @@ export default {
newEndDate.setMonth(startDate.getMonth() + 3);
this.dateRange[1] = newEndDate.toISOString().split("T")[0]; // YYYY-MM-DD
}
},
async handleProjectChange(projectId, time) {
const res = await projectApi.getProjectDetail(projectId);
@ -165,8 +167,6 @@ export default {
endDate: this.dateRange[1] + " 23:59:59",
projectId: this.projectInfo.projectId,
});
console.log(123,this.dateRange);
const start = new Date(this.timeRange[0]);
const end = new Date(this.timeRange[1]);
let index = 0;
@ -180,7 +180,11 @@ export default {
goToDetail(row) {
this.$router.push({
path: "/",
query: { userId: row.userId, projectId: this.projectInfo.projectId,nickName:row.userName },
query: {
userId: row.userId,
projectId: this.projectInfo.projectId,
nickName: row.userName,
},
});
},
},
@ -222,6 +226,15 @@ export default {
this.scrollableColumns = days;
},
},
'$route.query': {
deep: true,
handler(newVal) {
if (newVal.projectId != this.projectInfo.projectId&&newVal.projectId) {
this.selectedProject = Number(newVal.projectId);
this.handleProjectChange(this.selectedProject)
}
},
},
},
mounted() {
this.getProjectList();
@ -366,4 +379,15 @@ export default {
width: 100px;
}
}
::v-deep .el-table__body {
height: 100%;
}
::v-deep td.el-table__cell {
border-bottom: none;
border-top: none;
vertical-align: top;
}
::v-deep tr.el-table__row.current-row .el-table__cell {
background-color: #fff !important;
}
</style>

View File

@ -11,6 +11,7 @@
placeholder="请选择用户"
readonly
@click.native="openUserSelectDialog"
suffix-icon="el-icon-s-custom"
></el-input>
</div>
<div class="date-range-container">
@ -34,7 +35,8 @@
:showPagination="false"
:showSummary="true"
:summaryMethod="getFixedColumnsSummaries"
:tableHeight="600"
tableHeight="600"
:border="true"
></CustomTable>
</div>
</div>
@ -55,6 +57,7 @@ import SelectUser from "@/components/SelectUser.vue";
import { projectBank } from "@/utils/api";
export default {
name: "UserProject",
components: {
CustomTable,
SelectUser,
@ -64,11 +67,11 @@ export default {
fixedColumns: [],
executionData: [],
dateRange: [],
selectedUserName: "超级管理员",
selectedUserId: 1,
selectedUserName: this.$store.state.user.nickName,
selectedUserId: this.$store.state.user.id,
selectedUser: {
userId: 1,
selectedUserName: "超级管理员",
userId: this.$store.state.user.id,
selectedUserName: this.$store.state.user.nickName,
},
userSelectDialogVisible: false,
};
@ -229,7 +232,7 @@ export default {
}
.selectBox {
width: 200px;
margin-left: 35px;
margin-left: 15px;
}
.selectBox span {
width: 120px;
@ -286,7 +289,7 @@ export default {
}
/* 调整合计行的样式 */
::v-deep .el-table__footer td {
background-color: #c0c4cc !important;
background-color: #e0e1e3 !important;
font-weight: bold;
color: #606266;
@ -312,4 +315,7 @@ export default {
bottom: 0;
position: absolute;
}
::v-deep .el-table__footer td {
border: none !important;
}
</style>

View File

@ -0,0 +1,392 @@
<template>
<div class="project-list flex-row jcsb">
<div class="left">
<div style="margin-bottom: 20px; font-weight: bold">组织架构</div>
<el-tree
:data="deptOptions"
:expand-on-click-node="false"
ref="treeRef"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick"
style="margin-left: 50px"
/>
</div>
<div class="right f1">
<div class="search-bar">
<el-form
:inline="true"
:model="searchForm"
class="demo-form-inline"
size="small"
>
<el-form-item label="统计任务" class="form-item">
<el-select
v-model="taskId"
placeholder="选择任务"
@change="getTaskUserList"
>
<el-option
v-for="item in taskList"
:key="item.id"
:label="item.taskName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item class="search-buttons">
<el-button @click="onReset"></el-button>
</el-form-item>
</el-form>
</div>
<div class="f1 df">
<CustomTable
:columns="columns"
:tableData="tableData"
:total="total"
:show-selection="false"
:show-index="true"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
tableHeight="495px"
@sortChange="sortChange"
:default-sort="{
prop: 'score',
order: 'descending',
}"
>
<template slot="operation" slot-scope="{ row }">
<div class="operation-buttons">
<el-button type="text" size="mini" @click="handleEdit(row, 0)"
>查看详情</el-button
>
</div>
</template>
</CustomTable>
</div>
</div>
</div>
</template>
<script>
import CustomTable from "@/components/CustomTable.vue";
import SelectUser from "@/components/SelectUser.vue";
import { taskApi } from "@/utils/api";
import { deptTreeSelect } from "@/api/system/user";
export default {
name: "UserScore",
components: {
CustomTable,
SelectUser,
},
data() {
return {
deptOptions: undefined,
taskId: "",
searchForm: {
userIdList: [],
userName: "",
isAsc: "desc",
},
columns: [
{ prop: "userName", label: "考核人员" },
{
prop: "score",
label: "考核评分",
sortable: "custom",
type: "status",
callback: (value, row) => {
if (row.score) {
return row.score;
} else if (row.examineStatus == 1) {
return row.manageScore;
} else if (row.examineStatusSelf == 1) {
return row.selfScore;
}
},
},
{
prop: "examineStatus",
label: "状态",
type: "status",
callback: (value, row) => {
if (row.examineStatusSelf != 0 && row.examineStatus != 0)
var color = "#333";
else var color = "#FF7D00";
if (row.examineStatusSelf == 0 && row.examineStatus == 0) {
return `<span style="color: ${color}">待个人自评/组长评分</span>`;
} else if (row.examineStatusSelf == 0) {
return `<span style="color: ${color}">待个人自评</span>`;
} else if (row.examineStatus == 0) {
return `<span style="color: ${color}">待组长评分</span>`;
} else {
return `<span style="color: ${color}">已完成</span>`;
}
},
},
{
prop: "operation",
label: "操作",
width: "250",
className: "operation-column",
},
],
tableData: [],
total: 0,
currentSelectedUser: [],
pageNum: 1, //
pageSize: 10, //
taskList: [],
};
},
methods: {
onSearch() {
this.getTaskUserList();
},
onReset() {
Object.keys(this.searchForm).forEach((key) => {
this.searchForm[key] = "";
});
this.$refs.treeRef.setCurrentKey(null);
this.searchForm.isAsc = "desc"
this.initSort()
// this.searchForm.taskId = this.taskList[0]?.id;
this.getTaskUserList();
},
handleNodeClick(data) {
this.searchForm.deptId = data.id;
this.getTaskUserList();
},
handleEdit(row, edit) {
let score = "";
if (row.score) {
score = row.score;
} else if (row.manageScore) {
score = row.manageScore;
} else {
score = row.selfScore;
}
this.$router.push({
path: "/projectBank/userScoreDetail",
query: { taskId: row.taskId, examineId: row.id, score },
});
},
getTaskUserList(init) {
if (!this.taskId) return;
taskApi
.getTaskUserList({
...this.searchForm,
taskId: this.taskId,
sortFiled: "all",
pageNum: this.pageNum,
pageSize: this.pageSize,
})
.then((res) => {
this.tableData = res.rows;
this.total = res.total;
if (init === 1) {
this.$nextTick(() => {
this.initSort();
});
}
});
},
getTaskList(init) {
taskApi
.getTaskList({
pageNum: 1,
pageSize: 100000,
})
.then((res) => {
this.taskList = res.rows;
this.taskId = res.rows[0]?.id;
this.getTaskUserList(init);
});
},
handleSizeChange(size) {
this.pageSize = size;
this.pageNum = 1; //
this.getTaskUserList();
},
handleCurrentChange(page) {
this.pageNum = page;
this.getTaskUserList();
},
getDeptTree() {
deptTreeSelect().then((response) => {
this.deptOptions = response.data;
});
},
sortChange({ order }) {
let ele = document.getElementsByClassName("is-sortable")[0];
let className = ele.getAttribute("class");
if (order == "descending") {
ele.setAttribute(
"class",
className.replace("ascending", "") + " descending"
);
} else if (order == "ascending") {
ele.setAttribute(
"class",
className.replace("descending", "") + " ascending"
);
} else {
ele.setAttribute(
"class",
className.replace("ascending", "").replace("descending", "")
);
}
this.searchForm.isAsc =
order == "descending" ? "desc" : order == "ascending" ? "asc" : "";
this.getTaskUserList();
},
initSort() {
let ele = document.getElementsByClassName("is-sortable")[0];
if(!ele) return
let className = ele.getAttribute("class");
ele.setAttribute("class", className.replace("ascending", "") + " descending");
},
},
mounted() {
this.getDeptTree();
this.getTaskList(1);
},
};
</script>
<style lang="scss" scoped>
.project-list {
padding: 0 20px;
background-color: white;
height: 88vh;
box-sizing: border-box;
overflow: hidden;
align-items: flex-start;
gap: 20px;
.left {
padding-top: 20px;
width: 300px;
height: 100%;
// box-shadow: 5px 0 5px rgba(0, 0, 0, 0.5); /* */
border-right: 1px solid #eeeeee;
margin-right: 10px;
}
.right {
padding-top: 20px;
}
}
.search-bar {
margin-bottom: 20px;
}
.demo-form-inline {
// display: flex;
// flex-wrap: nowrap;
// align-items: flex-start;
}
.demo-form-inline .el-form-item {
// margin-right: 50px; /* 30px */
margin-bottom: 0;
}
.demo-form-inline .el-form-item:last-child {
margin-right: 0; /* 移除最后一个元素的右边距 */
}
.form-item {
flex: 1;
}
.form-item ::v-deep .el-form-item__content {
// width: 100%;
}
.form-item ::v-deep .el-input,
.form-item ::v-deep .el-select {
// width: 100%;
}
.search-buttons {
white-space: nowrap;
}
::v-deep .operation-buttons .el-button {
padding: 4px 8px;
margin: 0 2px;
font-weight: 600;
font-size: 14px;
}
::v-deep .operation-column {
background-color: #ffffff;
box-shadow: -2px 0 5px rgba(241, 112, 6, 0.1);
}
.el-button.is-text {
min-width: 32px !important;
}
.dialog-footer {
display: flex;
justify-content: center;
align-items: center;
}
.dialog-footer .el-button {
margin: 0 10px;
}
.delete-content {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.warning-icon {
font-size: 24px;
color: #e6a23c;
margin-right: 10px;
}
/* 添加以下样式来使对话框垂直居中 */
::v-deep .delete-dialog.el-dialog {
margin-top: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
::v-deep .el-table th {
text-align: center;
}
::v-deep .el-table .cell {
text-align: center;
}
.search-buttons ::v-deep .el-button {
width: 90px !important;
height: 36px;
}
::v-deep .el-table {
border: 1px solid #eee;
border-bottom: none;
}
::v-deep .el-tree-node__content {
font-weight: bold;
}
::v-deep .is-current > .el-tree-node__content:first-child .el-tree-node__label {
color: #4096ff !important;
}
::v-deep .el-tree-node__content {
height: 36px;
}
</style>

View File

@ -0,0 +1,565 @@
<template>
<div class="conetentBox">
<div class="titleBox flex-row aic jcsb">
<div class="flex-row aic">
<div class="block"></div>
评分详情
</div>
</div>
<div class="flex-row jcsb aic userBox">
<div>
<el-form :inline="true" class="demo-form-inline" size="small">
<el-form-item label="人员姓名" class="form-item">
<el-select
v-model="examineId"
placeholder="请选择"
@change="userChange"
style="width: 300px"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.userName"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="任务名称" class="form-item">
<el-select
v-model="examineTaskId"
placeholder="请选择"
@change="getUserList"
style="width: 300px"
>
<el-option
v-for="item in taskList"
:key="item.id"
:label="item.taskName"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
</div>
<div class="flex-row jcsb aic userBox headerBox">
<div class="flex-row aic" style="width: 200px">
<i
class="el-icon-user-solid"
style="color: #4096ff; font-size: 24px; margin-right: 5px"
></i
>{{ (userList.find((ele) => ele.id == examineId) || {}).userName }}
</div>
<div class="totalBox aic">
<div>考核评分</div>
<div class="scoreTotal">{{ score }}</div>
</div>
</div>
<el-tabs v-model="activeName">
<el-tab-pane label="组长评估" name="first">
<div class="tableBox">
<div
class="tableRow"
v-for="(table, index) in tableData1"
:key="index"
style="margin-bottom: 20px"
>
<div class="userBox">{{ table[0].reviewCategory }}</div>
<el-table :data="table" style="width: 100%">
<el-table-column
v-for="(header, hIndex) in headers"
:key="hIndex"
:label="header.label"
:prop="header.prop"
:width="header.width"
:minWidth="header.minWidth"
>
</el-table-column>
<el-table-column
label="评分"
prop="score"
:minWidth="420"
class-name="sorceTableCell"
>
<template slot-scope="scope">
<div style="width: 88%; position: relative">
<div>
<div
class="flex-row jcsb"
style="
margin-left: 10px;
width: 90%;
color: #999;
font-weight: 500;
font-size: 14px;
"
>
<div>0</div>
<div v-show="scope.row.score != 10">10</div>
</div>
<div
class="scoreText aic"
v-show="scope.row.score != 0"
:style="{
left: scope.row.score * 9 - 5 + '%',
}"
>
<img
src="@/assets/task/score.png"
:style="{
height: '28px',
width: '34px',
zIndex: 0,
position: 'absolute',
top: '0',
}"
alt=""
/>
<div
:style="{
zIndex: 10,
paddingLeft: scope.row.score != 10 ? '13px' : '9px',
}"
>
{{ scope.row.score }}
</div>
</div>
<div
v-for="item in scope.row.score == 0
? 0
: scope.row.score - 1"
:key="item"
class="stepBox"
:style="{
left: item * 9 + 1.5 + '%',
}"
></div>
<el-slider
v-model="scope.row.score"
:min="0"
:max="10"
:disabled="true"
style="width: 90%"
show-stops
show-tooltip
></el-slider>
</div>
<div class="statusText" v-show="scope.row.score == 0">
暂未打分
</div>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div>
<div>
<div class="userBox">总体评价</div>
<div>
<el-input
type="textarea"
:autosize="{ minRows: 4 }"
placeholder="0/300"
v-model="judgeContent"
:readonly="true"
maxlength="300"
show-word-limit
>
</el-input>
</div>
<div style="margin-top: 20px;font-weight: bold;">组长{{ manageUserName }}</div>
</div>
</div>
</div></el-tab-pane
>
<el-tab-pane label="个人自评" name="second">
<div class="tableBox">
<div
class="tableRow"
v-for="(table, index) in tableData2"
:key="index"
style="margin-bottom: 20px"
>
<div class="userBox">{{ table[0].reviewCategory }}</div>
<el-table :data="table" style="width: 100%">
<el-table-column
v-for="(header, hIndex) in headers"
:key="hIndex"
:label="header.label"
:prop="header.prop"
:width="header.width"
:minWidth="header.minWidth"
>
</el-table-column>
<el-table-column
label="评分"
prop="score"
:minWidth="320"
class-name="sorceTableCell"
>
<template slot-scope="scope">
<div style="width: 88%; position: relative">
<div>
<div
class="flex-row jcsb"
style="
margin-left: 10px;
width: 90%;
color: #999;
font-weight: 500;
font-size: 14px;
"
>
<div>0</div>
<div v-show="scope.row.score != 10">10</div>
</div>
<div
class="scoreText aic"
v-show="scope.row.score != 0"
:style="{
left: scope.row.score * 9 - 5 + '%',
}"
>
<img
src="@/assets/task/score.png"
:style="{
height: '28px',
width: '34px',
zIndex: 0,
position: 'absolute',
top: '0',
}"
alt=""
/>
<div
:style="{
zIndex: 10,
paddingLeft: scope.row.score != 10 ? '13px' : '9px',
}"
>
{{ scope.row.score }}
</div>
</div>
<div
v-for="item in scope.row.score == 0
? 0
: scope.row.score - 1"
:key="item"
class="stepBox"
:style="{
left: item * 9 + 1.5 + '%',
}"
></div>
<el-slider
v-model="scope.row.score"
:min="0"
:max="10"
@change="updateScore(scope.row)"
:disabled="true"
style="width: 90%"
show-stops
show-tooltip
></el-slider>
</div>
<div class="statusText" v-show="scope.row.score == 0">
暂未打分
</div>
</div>
</template>
</el-table-column>
<el-table-column label="自评总结" prop="score" width="150">
<template slot-scope="scope">
<div>
<el-button
@click="handleEdit(scope.row)"
type="text"
size="mini"
:class="{ hasEdit: !scope.row.remark }"
style="font-weight: 600"
>{{ scope.row.remark ? "查看" : "暂未评价" }}</el-button
>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div></div>
</div>
</el-tab-pane>
</el-tabs>
<el-dialog title="自评总结" :visible.sync="dialogVisible" width="30%">
<div>
<el-input
type="textarea"
:autosize="{ minRows: 4 }"
placeholder="0/200"
v-model="remark"
readonly
>
</el-input>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { taskApi } from "@/utils/api";
export default {
data() {
return {
examineTaskId: "",
examineId: "",
userList: [],
dialogVisible: false,
remark: "",
headers: [
{ label: "考核项", prop: "reviewItem", minWidth: 200 },
{
label: "评分标准",
prop: "remarks",
minWidth: 300,
},
],
//
tableData1: [],
tableData2: [],
taskList: [],
judgeContent: "",
score: "",
activeName: "first",
manageUserName: "",
};
},
methods: {
handleEdit(row) {
this.dialogVisible = true;
this.remark = row.remark;
},
getTaskList(first) {
taskApi
.getTaskList({
pageNum: 1,
pageSize: 100000,
})
.then((res) => {
this.taskList = res.rows;
this.$nextTick(() => {
this.examineTaskId = Number(this.$route.query.taskId);
this.getUserList(first);
});
});
},
getUserList(first) {
if (!this.examineTaskId) return;
taskApi
.getTaskUserList({
taskId: this.examineTaskId,
pageNum: 1,
pageSize: 100000,
})
.then((res) => {
this.userList = res.rows;
this.$nextTick(() => {
if (first !== 1) this.examineId = this.userList[0].id;
else this.examineId = Number(this.$route.query.examineId);
this.getSocreDetail(0);
this.getSocreDetail(1);
});
});
},
userChange() {
this.getSocreDetail(0);
this.getSocreDetail(1);
},
//
getSocreDetail(type) {
let param = {
examineTaskId: this.examineTaskId,
reviewType: type,
examineId: this.examineId,
};
taskApi.getTaskScoreDetail(param).then((res) => {
let objData = {};
res.data.examineConfigDetailVoList.forEach((ele) => {
if (!objData[ele.reviewCategory]) objData[ele.reviewCategory] = [];
objData[ele.reviewCategory].push(ele);
});
if (type == 0) this.tableData1 = Object.values(objData);
else this.tableData2 = Object.values(objData);
this.judgeContent = res.data.examineUser.judgeContent;
this.manageUserName = res.data.examineUser.manageUserName;
if (res.data.examineUser.score) {
this.score = res.data.examineUser.score;
} else if (res.data.examineUser.manageScore) {
this.score = res.data.examineUser.manageScore;
} else {
this.score = res.data.examineUser.selfScore;
}
});
},
},
created() {
this.getTaskList(1);
},
};
</script>
<style scoped>
.conetentBox {
padding: 40px 30px 30px;
background-color: #fff;
height: 90vh; /* 设置整体高度 */
overflow: auto;
}
.el-table {
margin-top: 20px;
}
.userBox {
margin: 0 0 20px;
font-size: 16px;
font-weight: bold;
}
::v-deep .el-slider__runway {
height: 14px;
border-radius: 10px;
margin: 10px !important;
/* width: 95%; */
}
::v-deep .el-slider__runway.disabled .el-slider__bar {
height: 14px;
border-radius: 10px;
background-color: #ff5722;
}
::v-deep .el-slider__stop {
height: 14px;
border-radius: 0;
z-index: 1000;
}
::v-deep .el-slider__bar {
height: 14px;
border-radius: 10px;
background: linear-gradient(to right, #ffb144, #ff7d00);
}
::v-deep .el-slider__button {
display: none;
}
.scoreText {
color: #fff;
position: absolute;
top: 2px;
font-weight: 500;
display: flex;
flex-direction: row;
}
::v-deep .el-table th {
text-align: left;
font-size: 14px;
font-weight: bold;
}
::v-deep .el-table .cell {
text-align: left;
font-size: 14px;
font-weight: bold;
}
.statusText {
color: #ff7d00;
position: absolute;
right: -50px;
top: 26px;
font-size: 14px;
font-weight: 600;
}
.hasEdit {
color: #999999;
}
::v-deep .el-dialog {
margin-top: 15% !important;
}
.tableBox {
height: 75%;
overflow: auto;
padding: 20px;
background-color: #fff;
}
.tableBox ::v-deep .el-table {
border-left: 1px solid #eeeeee;
border-right: 1px solid #eeeeee;
}
.tableRow {
padding: 20px;
box-shadow: 0 0 20px #0000000f;
}
.titleBox {
font-size: 20px;
font-weight: bold;
margin-bottom: 40px;
}
.headerBox {
padding: 20px;
background-color: #f9f9f9;
border-radius: 2px;
font-size: 16px;
}
.block {
width: 4px;
height: 24px;
background-color: #4096ff;
margin-right: 10px;
}
.stepBox {
position: absolute;
top: 33px;
height: 14px;
width: 3px;
background-color: #fff;
z-index: 100;
}
.totalBox {
width: 180px;
display: flex;
flex-direction: row;
font-size: 16px;
color: #333333;
}
.scoreTotal {
font-size: 28px;
font-weight: 700;
color: #ff7d00;
}
::v-deep .sorceTableCell .cell {
margin-bottom: 20px;
}
::v-deep .el-table__body .el-table__cell {
padding: 20px 40px;
}
::v-deep .el-table__header .el-table__cell {
padding: 14px 30px;
}
::v-deep .el-tabs__item.is-active {
color: #4096ff;
}
::v-deep .el-tabs__item {
font-size: 18px;
font-weight: bold;
color: #999999;
}
::v-deep .el-tabs__active-bar {
height: 3px;
width: 32px !important;
left: 20px;
}
</style>

View File

@ -61,7 +61,7 @@
</el-form>
<!-- 底部 -->
<div class="el-register-footer">
<span>Copyright © 2018-2024 ruoyi.vip All Rights Reserved.</span>
<span>unissense.tech</span>
</div>
</div>
</template>

View File

@ -139,7 +139,7 @@
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="handleDataScope" icon="el-icon-circle-check"
v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item>
<el-dropdown-item command="handleAuthUser" icon="el-icon-user"
<el-dropdown-item command="handleAuthUser" icon="el-icon-s-custom"
v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>

View File

@ -0,0 +1,544 @@
<template>
<div class="conetentBox">
<div class="titleBox flex-row aic jcsb">
<div class="flex-row aic">
<div class="block"></div>
{{ examineTask.taskName }}
</div>
<div class="flex-row jcfs f1" style="gap: 20px" v-if="isEdit">
<el-button type="default" style="width: 90px" @click="saveScoreCheck(0)"
>保存</el-button
>
<el-button type="primary" style="width: 90px" @click="saveScoreCheck(1)"
>提交</el-button
>
</div>
</div>
<div class="flex-row jcsb aic userBox headerBox">
<div class="flex-row aic" style="width: 200px;">
<i
class="el-icon-user-solid"
style="color: #4096ff; font-size: 24px; margin-right: 5px"
></i
>{{ examineUser.userName }}
</div>
<div class="totalBox aic" v-if="!isNormal">
<div>考核评分</div>
<div class="scoreTotal">{{ saveData.manageScore }}</div>
</div>
</div>
<div class="tableBox">
<div
class="tableRow"
v-for="(table, index) in tableData"
:key="index"
style="margin-bottom: 20px"
>
<div class="userBox">{{ table[0].reviewCategory }}</div>
<el-table :data="table" style="width: 100%">
<el-table-column
v-for="(header, hIndex) in headers"
:key="hIndex"
:label="header.label"
:prop="header.prop"
:width="header.width"
:minWidth="header.minWidth"
>
</el-table-column>
<el-table-column
label="评分"
prop="score"
:minWidth="isNormal ? 320 : 420"
class-name="sorceTableCell"
>
<template slot-scope="scope">
<div style="width: 88%; position: relative">
<div>
<div
class="flex-row jcsb"
style="
margin-left: 10px;
width: 90%;
color: #999;
font-weight: 500;
font-size: 14px;
"
>
<div>0</div>
<div v-show="scope.row.score != 10">10</div>
</div>
<div
class="scoreText aic"
v-show="scope.row.score != 0"
:style="{
left: scope.row.score * 9 - 5 + '%',
}"
>
<img
src="@/assets/task/score.png"
:style="{
height: '28px',
width: '34px',
zIndex: 0,
position: 'absolute',
top: '0',
}"
alt=""
/>
<div
:style="{
zIndex: 10,
paddingLeft: scope.row.score != 10 ? '13px' : '9px',
}"
>
{{ scope.row.score }}
</div>
</div>
<div
v-for="item in scope.row.score == 0
? 0
: scope.row.score - 1"
:key="item"
class="stepBox"
:style="{
left: item * 9 + 1.5 + '%',
}"
></div>
<el-slider
v-model="scope.row.score"
:min="0"
:max="10"
@change="updateScore(scope.row)"
:disabled="!isEdit"
style="width: 90%"
show-stops
show-tooltip
></el-slider>
</div>
<div class="statusText" v-show="scope.row.score == 0">
暂未打分
</div>
</div>
</template>
</el-table-column>
<el-table-column class-name="editCell" label="自评总结" prop="score" v-if="isNormal" minWidth="140">
<template slot-scope="scope">
<div>
<el-button
v-if="isEdit"
@click="handleEdit(scope.row)"
type="text"
size="mini"
:class="{ hasEdit: !scope.row.remark }"
style="font-weight: 600"
>{{ scope.row.remark ? "查看" : "暂未评价" }}
<i
style="color: #4096ff; font-size: 14px"
class="el-icon-edit el-icon--right"
></i
></el-button>
<el-button
v-if="!isEdit"
@click="handleEdit(scope.row)"
type="text"
size="mini"
:class="{ hasEdit: !scope.row.remark }"
style="font-weight: 600"
>{{ scope.row.remark ? "查看" : "暂未评价" }}</el-button
>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div>
<div v-if="!isNormal">
<div class="userBox">总体评价</div>
<div>
<el-input
type="textarea"
:autosize="{ minRows: 4 }"
placeholder="0/300"
v-model="saveData.judgeContent"
:readonly="!isEdit"
maxlength="300"
show-word-limit
>
</el-input>
</div>
</div>
</div>
</div>
<el-dialog title="自评总结" :visible.sync="dialogVisible" width="30%">
<div>
<el-input
type="textarea"
:autosize="{ minRows: 4 }"
placeholder="0/200"
v-model="remark"
:readonly="!isEdit"
maxlength="200"
show-word-limit
>
</el-input>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="cancelEdit">{{
isEdit ? "取 消" : "关闭"
}}</el-button>
<el-button type="primary" @click="saveEdit" v-if="isEdit"
> </el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
import { taskApi } from "@/utils/api";
export default {
data() {
return {
userId: "",
taskId: "",
isNormal: false,
dialogVisible: false,
selectRow: false,
saveStatus: "",
remark: "",
isEdit: "",
headers: [
{ label: "考核项", prop: "reviewItem", minWidth: 150 },
{
label: "评分标准",
prop: "remarks",
minWidth:this.$route.query.isNormal? 240:360,
},
],
//
tableData: [
//
],
//
examineTaskId: "",
examineId: "",
reviewType: "",
reviewType: "",
//
saveData: {
examineId: "",
examineStatus: "",
examineStatusSelf: "",
manageScore: "",
taskId: "",
judgeContent: "",
examineDetailList: [],
},
examineUser: {},
examineTask: {},
};
},
methods: {
updateScore(row) {
//
if (!this.isNormal)
this.saveData.manageScore =
this.tableData
.flat()
.reduce((total, ele) => (ele.score || 0) * ele.weight + total, 0) /
10;
},
//
getSocreDetail() {
let param = {
examineTaskId: this.examineTaskId,
reviewType: this.reviewType,
userId: this.userId,
};
param.examineId = this.examineId;
taskApi.getTaskScoreDetail(param).then((res) => {
let objData = {};
res.data.examineConfigDetailVoList.forEach((ele) => {
if (!objData[ele.reviewCategory]) objData[ele.reviewCategory] = [];
objData[ele.reviewCategory].push(ele);
});
this.tableData = Object.values(objData);
if (!this.examineId) {
} else {
this.saveData.judgeContent = res.data.examineUser.judgeContent || "";
this.saveData.manageScore = res.data.examineUser.manageScore || "";
}
this.examineId = res.data.examineUser.id;
this.examineTask = res.data.examineTask;
this.examineUser = res.data.examineUser;
if (
res.data.examineUser.examineStatusSelf == 1 &&
res.data.examineUser.examineStatus == 1
)
this.isEdit = false;
});
},
handleEdit(row) {
this.dialogVisible = true;
this.selectRow = row;
this.remark = this.selectRow.remark;
},
saveEdit() {
if (this.remark.length > 200) {
this.$message({
message: "自评总结限制200个字符",
type: "warning",
});
return;
}
this.dialogVisible = false;
this.selectRow.remark = this.remark;
},
cancelEdit() {
this.dialogVisible = false;
},
saveDataSet(status) {
if (!this.isNormal) {
this.saveData.examineStatus = status;
} else {
this.saveData.examineStatusSelf = status;
}
this.saveData.examineDetailList = this.tableData.flat().map((ele) => ({
score: ele.score,
configId: ele.id,
remark: ele.remark,
}));
this.saveData.taskId = this.examineTaskId;
this.saveData.examineId = this.examineId;
},
saveScoreCheck(status) {
//
this.saveDataSet(status);
//
if (status == 0) {
this.saveScore();
return;
}
//
if (this.saveData.examineDetailList.filter((ele) => !ele.score).length) {
this.$alert("存在未评分绩效项,请完善后再试", "提交失败", {
confirmButtonText: "确定",
type: "warning",
});
} else if (
this.saveData.examineDetailList.filter((ele) => !ele.remark).length &&
this.isNormal &&
status
) {
this.$alert("自评总结为必填,请完善后再试", "提交失败", {
confirmButtonText: "确定",
type: "warning",
});
} else if (this.saveData.judgeContent.length > 300 && !this.isNormal) {
this.$message({
message: "总体评价限制300个字符",
type: "warning",
});
} else if (!this.saveData.judgeContent.length && !this.isNormal) {
this.$message({
message: "总体评价为必填",
type: "warning",
});
} else {
if (status) {
this.$confirm(
"提交后将无法修改,该操作不可逆,请确认后再试",
"确认提交绩效评分",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
this.saveScore();
});
} else {
this.saveScore();
}
}
},
saveScore() {
taskApi.saveTaskUserScore(this.saveData).then((res) => {
this.$message({
message: "操作成功",
type: "success",
});
this.$router.go(-1);
});
},
getRouteData() {
this.isNormal = this.$route.query.isNormal || "";
this.examineTaskId = this.$route.query.examineTaskId;
this.examineId = this.$route.query.examineId;
this.reviewType = this.$route.query.reviewType;
this.saveData.reviewType = this.$route.query.reviewType;
this.isEdit = this.$route.query.edit == 1 ? true : false;
this.userId = this.$store.state.user.id;
},
},
created() {
this.getRouteData();
},
mounted() {
this.getSocreDetail();
},
};
</script>
<style scoped>
.conetentBox {
padding: 40px 30px 30px;
background-color: #fff;
height: 90vh; /* 设置整体高度 */
overflow: auto;
}
.el-table {
margin-top: 20px;
}
.userBox {
margin: 0 0 20px;
font-size: 16px;
font-weight: bold;
}
::v-deep .el-slider__runway {
height: 14px;
border-radius: 10px;
margin: 10px !important;
/* width: 95%; */
}
::v-deep .el-slider__runway.disabled .el-slider__bar {
height: 14px;
border-radius: 10px;
background-color: #ff5722;
}
::v-deep .el-slider__stop {
height: 14px;
border-radius: 0;
z-index: 1000;
}
::v-deep .el-slider__bar {
height: 14px;
border-radius: 10px;
background: linear-gradient(to right, #ffb144, #ff7d00);
}
::v-deep .el-slider__button {
display: none;
}
.scoreText {
color: #fff;
position: absolute;
top: 2px;
font-weight: 500;
display: flex;
flex-direction: row;
}
::v-deep .el-table th {
text-align: left;
font-size: 14px;
font-weight: bold;
}
::v-deep .el-table .cell {
text-align: left;
font-size: 14px;
font-weight: bold;
}
.statusText {
color: #ff7d00;
position: absolute;
right: -50px;
top: 26px;
font-size: 14px;
font-weight: 600;
}
.hasEdit {
color: #999999;
}
::v-deep .el-dialog {
margin-top: 15% !important;
}
.tableBox {
height: 75%;
overflow: auto;
padding: 20px;
background-color: #fff;
}
.tableBox ::v-deep .el-table {
border-left: 1px solid #eeeeee;
border-right: 1px solid #eeeeee;
}
.tableRow {
padding: 20px;
box-shadow: 0 0 20px #0000000f;
}
.titleBox {
font-size: 20px;
font-weight: bold;
margin-bottom: 40px;
}
.headerBox {
padding: 20px;
background-color: #f9f9f9;
border-radius: 2px;
font-size: 16px;
}
.block {
width: 4px;
height: 24px;
background-color: #4096ff;
margin-right: 10px;
}
.stepBox {
position: absolute;
top: 33px;
height: 14px;
width: 3px;
background-color: #fff;
z-index: 100;
}
.totalBox {
width: 180px;
display: flex;
flex-direction: row;
font-size: 16px;
color: #333333;
}
.scoreTotal {
font-size: 28px;
font-weight: 700;
color: #ff7d00;
}
::v-deep .el-table__body .sorceTableCell .cell {
margin-bottom: 20px;
}
::v-deep .sorceTableCell.el-table__cell{
padding-right: 10px !important;
}
::v-deep .el-table__body .el-table__cell {
padding: 20px 40px;
}
::v-deep .el-table__header .el-table__cell {
padding: 14px 30px;
}
::v-deep .el-table__header .el-table__cell .cell{
padding-left: 20px !important;
}
::v-deep .editCell{
padding-right: 20px !important;
}
</style>

View File

@ -0,0 +1,227 @@
<template>
<div class="appraisal-manager">
<el-tabs v-model="activeName">
<el-tab-pane label="进行中" name="进行中">
<div class="assessment-container flex-row">
<div v-for="item in taskList['0']" :key="item.id" class="taskBox">
<div class="card-content">
<div class="cardBox flex-col">
<img
src="@/assets/task/titleIcon.png"
alt=""
style="width: 32px; height: 34px"
/>
<div class="flex-row aic nameBox">
<div>{{ item.taskName }}</div>
<el-tag type="warning" size="mini">进行中</el-tag>
</div>
<div class="timeBox">
截止时间{{ item.endTime.split(" ")[0] }}
</div>
</div>
<div class="status-actions aic">
<div class="peopleNumber">
考核人数{{ item.peopleNumber }}
</div>
<div
class="action-buttons aic"
@click="viewDetails(item, 1)"
:class="{ waitBtn: !item.taskEditFlag }"
>
考核评分
<img
src="@/assets/task/right.png"
alt=""
style="width: 16px; height: 16px"
/>
</div>
</div>
</div>
</div>
</div>
<el-empty v-if="!taskList['0']" description="暂无数据"></el-empty>
</el-tab-pane>
<el-tab-pane label="已过期" name="已过期">
<div class="assessment-container flex-row">
<div v-for="item in taskList['2']" :key="item.id" class="taskBox">
<div class="card-content">
<div class="cardBox flex-col">
<img
src="@/assets/task/titleIcon.png"
alt=""
style="width: 32px; height: 34px"
/>
<div class="flex-row aic nameBox">
<div>{{ item.taskName }}</div>
<el-tag type="info" size="mini">已过期</el-tag>
</div>
<div class="timeBox">
截止时间{{ item.endTime.split(" ")[0] }}
</div>
</div>
<div class="status-actions aic">
<div class="peopleNumber">
考核人数{{ item.peopleNumber }}
</div>
<div
class="action-buttons aic"
@click="viewDetails(item, 0)"
:class="{ waitBtn: !item.taskEditFlag }"
>
查看详情
<img
src="@/assets/task/right.png"
alt=""
style="width: 16px; height: 16px"
/>
</div>
</div>
</div>
</div>
</div>
<el-empty v-if="!taskList['2']" description="暂无数据"></el-empty>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { taskApi } from "@/utils/api";
export default {
name: "AppraisalManager",
data() {
return {
activeName: "进行中",
taskList: [],
};
},
methods: {
getTaks() {
taskApi.getTaskListSelf().then((res) => {
this.taskList = res.data;
});
},
viewDetails(row, isEdit) {
if (!row.taskEditFlag) {
this.$message({
message: "分数正在计算中,请等待计算完成",
type: "warning",
});
return;
}
//
this.$router.push({
path: "/workAppraisal/managerUser",
query: { taskId: row.id, isEdit },
});
},
},
created() {
this.getTaks();
},
};
</script>
<style scoped>
.appraisal-manager {
padding: 30px;
background-color: #fff;
height: calc(100vh - 100px); /* 设置整体高度 */
}
.assessment-container {
max-height: 650px; /* 减去标题高度 */
overflow-y: auto; /* 允许垂直滚动 */
gap: 2%;
padding-top: 20px;
padding-left: 20px;
flex-wrap: wrap;
}
.taskBox {
width: 23%;
height: 200px;
margin-bottom: 40px;
}
.card {
margin-bottom: 20px;
border-radius: 8px;
width: 80%;
}
.card-content {
padding: 20px 0 0 0;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
}
.cardBox {
gap: 20px;
padding: 0 20px;
}
.status-actions {
display: flex;
justify-content: space-between; /* 两端对齐 */
align-items: center; /* 垂直居中 */
height: 44px;
background-color: #f7faff;
padding: 0 20px;
margin-top: 20px;
}
.peopleNumber {
color: #666;
font-size: 14px;
font-weight: bold;
}
.status-text {
font-weight: bold; /* 状态文本加粗 */
}
.action-buttons {
display: flex;
gap: 10px; /* 按钮之间的间距 */
color: #4096ff;
font-size: 16px;
font-weight: bold;
cursor: pointer;
}
.statusText {
font-size: 26px;
margin: 20px 0;
font-weight: bold;
}
.timeBox {
font-size: 14px;
color: #999999;
font-weight: bold;
}
::v-deep .el-tabs__item.is-active {
color: #4096ff;
}
::v-deep .el-tabs__item {
font-size: 18px;
font-weight: bold;
color: #999999;
}
::v-deep .el-tabs__active-bar {
height: 3px;
width: 32px !important;
left: 10px;
}
.nameBox {
font-size: 16px;
font-weight: bold;
color: #333;
gap: 10px;
}
::v-deep .el-tag--warning {
color: #ea741e;
}
.waitBtn {
color: #999;
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<div class="project-list">
<div class="search-bar">
<el-form
:inline="true"
:model="searchForm"
class="demo-form-inline"
size="small"
>
<el-form-item label="考核人员" class="form-item">
<el-input
v-model="searchForm.userName"
placeholder="考核人员"
readonly
@click.native="openUserSelectDialog"
style="width: 300px"
><el-button slot="append" icon="el-icon-s-custom"></el-button
></el-input>
</el-form-item>
<el-form-item label="状态" class="form-item">
<el-select
v-model="searchForm.examineStatus"
placeholder="状态"
clearable
style="width: 300px"
>
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item class="search-buttons">
<el-button type="primary" @click="onSearch"></el-button>
<el-button @click="onReset"></el-button>
</el-form-item>
</el-form>
</div>
<div class="f1 df">
<CustomTable
:columns="columns"
:tableData="tableData"
:total="total"
:show-selection="false"
:show-index="true"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@sortChange="sortChange"
tableHeight="495px"
>
<template slot="operation" slot-scope="{ row }">
<div class="operation-buttons">
<el-button
v-if="isEdit == 1 && row.examineStatus == '0'"
@click="handleEdit(row, 1)"
type="text"
size="mini"
>去评分</el-button
>
<el-button
v-if="row.examineStatus == '1' || isEdit == 0"
type="text"
size="mini"
@click="handleEdit(row, 0)"
>查看详情</el-button
>
</div>
</template>
</CustomTable>
</div>
<SelectUser
:dialogVisible="userSelectDialogVisible"
:currentSelectedUser="currentSelectedUser"
:showSelection="true"
:highligt="false"
@confirm="handleUserConfirm"
@close="handleUserClose"
/>
</div>
</template>
<script>
import CustomTable from "@/components/CustomTable.vue";
import SelectUser from "@/components/SelectUser.vue";
import { taskApi } from "@/utils/api";
export default {
components: {
CustomTable,
SelectUser,
},
data() {
return {
searchForm: {
userIdList: [],
userName: "",
examineStatus: "",
},
taskId: "",
isEdit: "",
columns: [
{ prop: "userName", label: "考核人员" },
{ prop: "manageScore", label: "考核评分", sortable: "custom" },
{
prop: "examineStatus",
label: "状态",
type: "status",
callback: (value) => {
if (value == 0) var color = "#EA741D";
else var color = "#999";
return `<span style="color: ${color}">${
value == 0 ? "待评分" : "已完成"
}</span>`;
},
},
{
prop: "operation",
label: "操作",
width: "250",
className: "operation-column",
},
],
tableData: [],
total: 0,
userSelectDialogVisible: false,
currentSelectedUser: [],
pageNum: 1, //
pageSize: 10, //
statusList: [
{ label: "全部", value: "" },
{ label: "待评分", value: "0" },
{ label: "已完成", value: "1" },
],
isAsc: "",
};
},
methods: {
onSearch() {
this.taskUserList();
},
onReset() {
Object.keys(this.searchForm).forEach((key) => {
this.searchForm[key] = "";
});
this.currentSelectedUser = [];
this.taskUserList();
},
handleEdit(row, edit) {
this.$router.push({
path: "/workAppraisal/detail",
query: {
edit,
examineTaskId: this.taskId,
examineId: row.id,
reviewType: 0,
},
});
},
taskUserList() {
taskApi
.getTaskUserList({
...this.searchForm,
taskId: this.taskId,
pageNum: this.pageNum,
pageSize: this.pageSize,
isAsc: this.isAsc,
sortFiled: "manageScore",
})
.then((res) => {
this.tableData = res.rows;
this.total = res.total;
});
},
handleSizeChange(size) {
this.pageSize = size;
this.pageNum = 1; //
this.taskUserList();
},
handleCurrentChange(page) {
this.pageNum = page;
this.taskUserList();
},
openUserSelectDialog() {
this.userSelectDialogVisible = true;
},
handleUserConfirm(data) {
this.searchForm.userName = data.map((ele) => ele.nickName).join(",");
this.searchForm.userIdList = data.map((ele) => ele.userId);
},
handleUserClose() {
this.userSelectDialogVisible = false;
},
sortChange({ order }) {
this.isAsc =
order == "descending" ? "desc" : order == "ascending" ? "asc" : "";
this.taskUserList();
},
},
created() {
this.taskId = this.$route.query.taskId;
this.isEdit = this.$route.query.isEdit;
},
mounted() {
this.taskUserList();
},
};
</script>
<style lang="scss" scoped>
.project-list {
padding: 20px;
background-color: white;
height: 88vh;
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
}
.search-bar {
margin-bottom: 20px;
}
.demo-form-inline {
// display: flex;
// flex-wrap: nowrap;
// align-items: flex-start;
}
.demo-form-inline .el-form-item {
// margin-right: 50px; /* 30px */
// margin-bottom: 0;
}
.demo-form-inline .el-form-item:last-child {
margin-right: 0; /* 移除最后一个元素的右边距 */
}
.form-item {
flex: 1;
}
.form-item ::v-deep .el-form-item__content {
// width: 100%;
}
.form-item ::v-deep .el-input,
.form-item ::v-deep .el-select {
// width: 100%;
}
.search-buttons {
white-space: nowrap;
}
::v-deep .operation-column {
background-color: #ffffff;
box-shadow: -2px 0 5px rgba(241, 112, 6, 0.1);
}
.el-button.is-text {
min-width: 32px !important;
}
.dialog-footer {
display: flex;
justify-content: center;
align-items: center;
}
.dialog-footer .el-button {
margin: 0 10px;
}
.delete-content {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.warning-icon {
font-size: 24px;
color: #e6a23c;
margin-right: 10px;
}
/* 添加以下样式来使对话框垂直居中 */
::v-deep .delete-dialog.el-dialog {
margin-top: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
::v-deep .el-table th {
text-align: center;
}
::v-deep .el-table .cell {
text-align: center;
}
::v-deep .operation-buttons .el-button {
padding: 4px 8px;
margin: 0 2px;
font-weight: 600;
font-size: 14px;
}
.search-buttons ::v-deep .el-button {
width: 90px !important;
height: 36px;
}
.waitBtn {
color: #999;
}
</style>

View File

@ -0,0 +1,234 @@
<template>
<div class="appraisal-manager">
<el-tabs v-model="activeName">
<el-tab-pane label="进行中" name="进行中">
<div class="assessment-container flex-row">
<div v-for="item in taskList['0']" :key="item.id" class="taskBox">
<div class="card-content">
<div class="cardBox flex-col">
<img
src="@/assets/task/titleIcon.png"
alt=""
style="width: 32px; height: 34px"
/>
<div class="flex-row aic nameBox">
<div>{{ item.taskName }}</div>
<el-tag type="warning" size="mini">进行中</el-tag>
</div>
<div class="timeBox">
截止时间{{ item.endTime.split(" ")[0] }}
</div>
</div>
<div class="status-actions aic">
<div class="peopleNumber">
<!-- 考核人数{{ item.peopleNumber }} -->
</div>
<div
class="action-buttons aic"
@click="viewDetails(item, item.examineStatusSelf == 1 ?0: 1)"
:class="{ waitBtn: !item.taskEditFlag }"
>
{{ item.examineStatusSelf == 1 ? "查看详情" : "考核评分" }}
<img
src="@/assets/task/right.png"
alt=""
style="width: 16px; height: 16px"
/>
</div>
</div>
</div>
</div>
</div>
<el-empty v-if="!taskList['0']" description="暂无数据"></el-empty
></el-tab-pane>
<el-tab-pane label="已过期" name="已过期">
<div class="assessment-container flex-row">
<div v-for="item in taskList['2']" :key="item.id" class="taskBox">
<div class="card-content">
<div class="cardBox flex-col">
<img
src="@/assets/task/titleIcon.png"
alt=""
style="width: 32px; height: 34px"
/>
<div class="flex-row aic nameBox">
<div>{{ item.taskName }}</div>
<el-tag type="info" size="mini">已过期</el-tag>
</div>
<div class="timeBox">
截止时间{{ item.endTime.split(" ")[0] }}
</div>
</div>
<div class="status-actions aic">
<div class="peopleNumber">
<!-- 考核人数{{ item.peopleNumber }} -->
</div>
<div
class="action-buttons aic"
@click="viewDetails(item, 0)"
:class="{ waitBtn: item.taskEditFlag }"
>
查看详情
<img
src="@/assets/task/right.png"
alt=""
style="width: 16px; height: 16px"
/>
</div>
</div>
</div>
</div>
</div>
<el-empty v-if="!taskList['2']" description="暂无数据"></el-empty>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { taskApi } from "@/utils/api";
export default {
name: "NormalWorker",
data() {
return {
activeName: "进行中",
taskList: [],
};
},
methods: {
getTaks() {
taskApi.getTaskListSelfNormal().then((res) => {
this.taskList = res.data;
});
},
viewDetails(item, edit) {
if (!item.taskEditFlag) {
this.$message({
message: "分数正在计算中,请等待计算完成",
type: "warning",
});
return;
}
//
this.$router.push({
path: "/workAppraisal/detail",
query: {
edit,
isNormal: true,
examineTaskId: item.id,
reviewType: 1,
},
});
},
},
created() {
console.log(123);
this.getTaks();
},
};
</script>
<style scoped>
.appraisal-manager {
padding: 30px;
background-color: #fff;
height: calc(100vh - 100px); /* 设置整体高度 */
}
.assessment-container {
max-height: 650px; /* 减去标题高度 */
overflow-y: auto; /* 允许垂直滚动 */
gap: 2%;
padding-top: 20px;
padding-left: 20px;
flex-wrap: wrap;
}
.taskBox {
width: 23%;
height: 200px;
margin-bottom: 40px;
}
.card {
margin-bottom: 20px;
border-radius: 8px;
width: 80%;
}
.card-content {
padding: 20px 0 0 0;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
}
.cardBox {
gap: 20px;
padding: 0 20px;
}
.status-actions {
display: flex;
justify-content: space-between; /* 两端对齐 */
align-items: center; /* 垂直居中 */
height: 44px;
background-color: #f7faff;
padding: 0 20px;
margin-top: 20px;
}
.peopleNumber {
color: #666;
font-size: 14px;
font-weight: bold;
}
.status-text {
font-weight: bold; /* 状态文本加粗 */
}
.action-buttons {
display: flex;
gap: 10px; /* 按钮之间的间距 */
color: #4096ff;
font-size: 16px;
font-weight: bold;
cursor: pointer;
}
.statusText {
font-size: 26px;
margin: 20px 0;
font-weight: bold;
}
.timeBox {
font-size: 14px;
color: #999999;
font-weight: bold;
}
::v-deep .el-tabs__item.is-active {
color: #4096ff;
}
::v-deep .el-tabs__item {
font-size: 18px;
font-weight: bold;
color: #999999;
}
::v-deep .el-tabs__active-bar {
height: 3px;
width: 32px !important;
left: 10px;
}
.nameBox {
font-size: 16px;
font-weight: bold;
color: #333;
gap: 10px;
}
::v-deep .el-tag--warning {
color: #ea741e;
}
.waitBtn {
color: #999;
}
</style>

View File

@ -0,0 +1,927 @@
<template>
<div class="project-list">
<div class="search-bar">
<el-form
:inline="true"
:model="searchForm"
class="demo-form-inline"
size="small"
>
<el-form-item label="任务名称" class="form-item">
<el-input
v-model="searchForm.taskName"
placeholder="任务名称"
style="width: 300px;"
></el-input>
</el-form-item>
<el-form-item label="任务状态" class="form-item">
<el-select
v-model="searchForm.taskStatus"
placeholder="状态"
clearable
style="width: 300px;"
>
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="任务年份" class="form-item">
<el-date-picker
v-model="searchForm.year"
type="year"
style="width: 300px"
placeholder="选择年份"
value-format="yyyy"
>
</el-date-picker>
</el-form-item>
<el-form-item class="search-buttons">
<el-button type="primary" @click="onSearch"></el-button>
<el-button @click="onReset"></el-button>
</el-form-item>
</el-form>
</div>
<div class="table-actions mb10">
<el-button
type="primary"
size="mini"
@click="addTask"
style="height: 36px"
>+ 新增任务</el-button
>
</div>
<div class="f1 df">
<CustomTable
:columns="columns"
:tableData="tableData"
:total="total"
:show-selection="false"
:show-index="true"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
tableHeight="495px"
>
<template slot="operation" slot-scope="{ row }">
<div class="operation-buttons">
<el-button @click="editTask(row)" type="text" size="mini"
>编辑</el-button
>
<el-button
v-if="row.taskStatus == 0"
type="text"
size="mini"
@click="setTask(row)"
>指标配置</el-button
>
<el-button type="text" size="mini" @click="delTask(row)"
>删除</el-button
>
</div>
</template>
</CustomTable>
</div>
<SelectUser
:dialogVisible="userSelectDialogVisible"
:currentSelectedUser="currentSelectedUser"
:currentSelectedUserName="currentSelectedUserName"
:showSelection="true"
:highligt="false"
:selectable="selectable"
ref="selectUserRef"
@confirm="handleUserConfirm"
@close="handleUserClose"
/>
<el-dialog
:title="isEdit ? '编辑考核任务' : '新增考核任务'"
:visible.sync="dialogVisible1"
width="25%"
>
<div>
<el-form
:model="taskData"
size="small"
ref="taskFormRef"
:rules="rules"
label-width="100px"
>
<el-form-item label="任务名称" class="form-item" prop="taskName">
<el-input
v-model="taskData.taskName"
placeholder="0/20"
style="width: 90%"
></el-input>
</el-form-item>
<el-form-item
label="考核人员"
class="form-item"
prop="peopleNumberDetail"
>
<el-input
v-model="taskData.peopleNumberDetail"
placeholder="考核人员"
readonly
style="width: 90%"
@click.native="openUserSelectDialog"
><el-button slot="append" icon="el-icon-s-custom"></el-button
></el-input>
</el-form-item>
<el-form-item label="截止时间" class="form-item" prop="endTime">
<el-date-picker
v-model="taskData.endTime"
type="date"
style="width: 90%"
placeholder="选择日期"
value-format="yyyy-MM-dd 23:59:59"
:picker-options="{
disabledDate: disabledDate,
}"
>
</el-date-picker>
</el-form-item>
<el-form-item label="年份" class="form-item" prop="year">
<el-date-picker
v-model="taskData.year"
type="year"
style="width: 90%"
placeholder="选择年份"
value-format="yyyy"
:picker-options="{
disabledDate: disabledDate,
}"
>
</el-date-picker>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible1 = false"> </el-button>
<el-button type="primary" @click="saveTask"> </el-button>
</span>
</el-dialog>
<el-dialog title="配置指标和权重" :visible.sync="dialogVisible2">
<div class="modal">
<div class="left">
<div class="setText" style="font-weight: 600">累计权重</div>
<el-collapse
v-model="letfValue"
:accordion="true"
style="height: 300px; overflow: auto"
>
<el-collapse-item
v-for="(item, index) in scoreList"
:key="index"
:name="item.title"
>
<template #title>
<div class="jcsb flex-row contentTitle">
<span class="setTitle">{{ item.title }}</span>
<span class="statusText">{{ item.weight }}%</span>
</div>
</template>
<div>
<div
v-for="(ele, index) in item.list"
:key="index"
class="flex-row jcsb leftSub"
:class="{
selectClass: selectLeftRow == ele.title + ele.type,
}"
@click="selectLeft(ele)"
>
<div>{{ ele.title }}</div>
<div class="statusText">{{ ele.weight }}%</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
<div
class="flex-row jcsb"
style="margin-top: 20px; padding-right: 15px"
>
<div class="setTitle" style="font-weight: 600">总计</div>
<div class="statusText">
{{
scoreList.reduce((total, ele) => total + (ele.weight || 0), 0)
}}%
</div>
</div>
</div>
<div class="right">
<div class="flex-row jcsb" style="margin-bottom: 10px">
<div style="width: 50%;font-weight: 600">指标</div>
<div class="center" style="width: 50%;font-weight: 600">权重占比</div>
</div>
<div>
<div
v-for="(item, index) in (
(
(scoreList.find((ele) => ele.title == letfValue) || {})
.list || []
).find((ele) => ele.title + ele.type == selectLeftRow) || {}
).rightArr || []"
:key="index"
style="margin-bottom: 10px"
class="flex-row jcsb aic"
>
<div style="width: 50%; font-weight: 600">
{{ item.reviewItem }}
</div>
<div class="center" style="width: 50%">
<div
class="flex-row jcsb"
style="
margin-left: 10px;
width: 100%;
color: #999;
font-weight: 500;
font-size: 14px;
font-weight: 600;
"
>
<div>0</div>
<div v-show="item.weight != 20">20</div>
</div>
<div
class="scoreText aic"
v-show="item.weight != 0"
:style="{
left: item.weight * 4.5 - 2.5 + '%',
}"
>
<img
src="@/assets/task/score.png"
:style="{
height: '28px',
width: '34px',
zIndex: 0,
position: 'absolute',
top: '-2px',
}"
alt=""
/>
<div
:style="{
zIndex: 10,
textIndent: item.weight < 10 ? '13px' : '10px',
fontSize: '12px',
}"
>
{{ item.weight }}
</div>
</div>
<!-- <div class="flex-row jcsb scoreBox" style="margin-left: 15px">
<div>0%</div>
<div class="scoreText">{{ item.weight }}%</div>
<div>20%</div>
</div> -->
<div
v-for="item in item.weight == 0 ? 0 : item.weight - 1"
:key="item"
class="stepBox"
:style="{
left: item * 5 + 4 + '%',
}"
></div>
<el-slider
v-model="item.weight"
:max="20"
@change="changeTotal"
></el-slider>
</div>
</div>
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible2 = false">取消</el-button>
<el-button type="primary" @click="saveSet"></el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import CustomTable from "@/components/CustomTable.vue";
import SelectUser from "@/components/SelectUser.vue";
import { taskApi } from "@/utils/api";
export default {
components: {
CustomTable,
SelectUser,
},
data() {
return {
searchForm: {
taskName: "",
taskStatus: "",
year: "",
},
selectLeftRow: "",
columns: [
{ prop: "taskName", label: "任务名称" },
{ prop: "peopleNumber", label: "考核人数" },
{
prop: "taskStatus",
label: "任务状态",
type: "status",
callback: (value) => {
if (value == 2) var color = "#999";
else var color = "#4096FF";
return `<span style="color: ${color}">${
value ? "已过期" : "进行中"
}</span>`;
},
},
{
prop: "year",
label: "年份",
},
{
prop: "createTime",
label: "创建时间",
type: "status",
callback: (value) => {
return value.split(" ")[0];
},
},
{
prop: "endTime",
label: "截至时间",
type: "status",
callback: (value) => {
return value.split(" ")[0];
},
},
{
prop: "operation",
label: "操作",
width: "250",
className: "operation-column",
},
],
tableData: [],
total: 0,
userSelectDialogVisible: false,
currentSelectedUser: [],
currentSelectedUserName: [],
pageNum: 1, //
pageSize: 10, //
statusList: [
{ label: "全部", value: "" },
{ label: "进行中", value: "0" },
{ label: "已过期", value: "2" },
],
dialogVisible1: false,
isEdit: false,
taskData: {
id: "",
taskName: "",
peopleNumberDetail: "",
userIdList: [],
endTime: "",
peopleNumber: 0,
year: "",
},
rules: {
taskName: [
{ required: true, message: "请输入活动名称", trigger: "blur" },
{ min: 1, max: 20, message: "长度限制20个字符", trigger: "blur" },
],
peopleNumberDetail: [
{ required: true, message: "请选择考核人员", trigger: "change" },
],
endTime: [
{ required: true, message: "请选择截止时间", trigger: "blur" },
],
year: [{ required: true, message: "请选择年份", trigger: "blur" }],
},
dialogVisible2: false,
letfValue: "",
scoreList: [],
selectRow: {},
};
},
watch: {
dialogVisible1(newVal) {
if (newVal) {
this.$nextTick(() => {
this.$refs.selectUserRef?.$refs.customTableRef?.handleCurrentChange(
1
);
});
}
},
},
methods: {
onSearch() {
this.getTaskList();
},
onReset() {
Object.keys(this.searchForm).forEach((key) => {
this.searchForm[key] = "";
});
this.getTaskList();
},
disabledDate(date) {
const startDate = new Date().getTime(); //
return new Date(date).getTime() + 60 * 60 * 24 * 1000 < startDate; //
},
getTaskList() {
taskApi
.getTaskList({
...this.searchForm,
pageNum: this.pageNum,
pageSize: this.pageSize,
})
.then((res) => {
this.tableData = res.rows;
this.total = res.total;
});
},
handleSizeChange(size) {
this.pageSize = size;
this.pageNum = 1; //
this.getTaskList();
},
handleCurrentChange(page) {
this.pageNum = page;
this.getTaskList();
},
openUserSelectDialog() {
this.userSelectDialogVisible = true;
this.$nextTick(() => {
this.$refs.selectUserRef?.$refs.customTableRef?.clearSelection();
this.currentSelectedUser = [];
this.currentSelectedUserName = [];
(this.selectRow.userIdList || []).forEach((ele) => {
this.currentSelectedUser.push(ele);
});
(this.selectRow.peopleNumberDetail || "").split(",").forEach((ele) => {
if (ele) this.currentSelectedUserName.push(ele);
});
});
},
handleUserConfirm(data) {
this.taskData.peopleNumberDetail = data
.map((ele) => ele.nickName)
.join(",");
this.taskData.userIdList = data.map((ele) => ele.userId);
this.taskData.peopleNumber = data.length;
this.selectRow.userIdList = data.map((ele) => ele.userId);
this.selectRow.peopleNumberDetail = data
.map((ele) => ele.nickName)
.join(",");
},
handleUserClose() {
this.userSelectDialogVisible = false;
},
addTask() {
this.isEdit = false;
this.dialogVisible1 = true;
this.taskData = {
id: "",
taskName: "",
peopleNumberDetail: "",
userIdList: [],
endTime: "",
peopleNumber: 0,
};
this.selectRow = {};
},
editTask(row) {
this.isEdit = true;
this.dialogVisible1 = true;
this.taskData.id = row.id;
this.taskData.taskName = row.taskName;
this.taskData.peopleNumber = row.peopleNumber;
this.taskData.peopleNumberDetail = row.peopleNumberDetail;
this.taskData.userIdList = row.userIdList;
this.taskData.endTime = row.endTime;
this.taskData.year = String(row.year);
this.selectRow = row;
},
setTask(row) {
taskApi.getTaskSet(row.id).then((res) => {
let objData = {};
res.data.forEach((ele) => {
if (!objData[ele.reviewType]) objData[ele.reviewType] = [];
objData[ele.reviewType].push(ele);
});
let arrList = {};
Object.keys(objData).forEach((ele) => {
let arr = [];
objData[ele].forEach((item) => {
if (!arr[item.reviewCategory]) arr[item.reviewCategory] = [];
arr[item.reviewCategory].push(item);
});
arrList[ele] = arr;
});
this.scoreList = [
{
title: "组长评估绩效指标",
list: Object.keys(arrList["0"]).map((ele) => ({
title: ele,
weight: arrList["0"][ele].reduce(
(total, item) => (item.weight || 0) + total,
0
),
rightArr: arrList["0"][ele],
type: 0,
})),
weight: Object.values(arrList["0"]).reduce(
(total, item) =>
(item.reduce((total, item) => (item.weight || 0) + total, 0) ||
0) + total,
0
),
},
{
title: "个人自评绩效指标",
list: Object.keys(arrList["1"]).map((ele) => ({
title: ele,
weight: arrList["1"][ele].reduce(
(total, item) => (item.weight || 0) + total,
0
),
rightArr: arrList["1"][ele],
type: 1,
})),
weight: Object.values(arrList["1"]).reduce(
(total, item) =>
(item.reduce((total, item) => (item.weight || 0) + total, 0) ||
0) + total,
0
),
},
{
title: "系统核算绩效指标",
list: Object.keys(arrList["2"]).map((ele) => ({
title: ele,
weight: arrList["2"][ele].reduce(
(total, item) => (item.weight || 0) + total,
0
),
rightArr: arrList["2"][ele],
type: 2,
})),
weight: Object.values(arrList["2"]).reduce(
(total, item) =>
(item.reduce((total, item) => (item.weight || 0) + total, 0) ||
0) + total,
0
),
},
];
this.dialogVisible2 = true;
});
},
saveSet() {
if (
this.scoreList.reduce((total, ele) => total + (ele.weight || 0), 0) !==
100
) {
this.$message({
type: "warning",
message: "累计权重值必须为100%,请调整后再试",
});
} else {
let examineConfigList = [];
this.scoreList
.map((ele) => ele.list)
.flat()
.map((ele) => ele.rightArr)
.flat()
.forEach((ele) => {
examineConfigList.push({ id: ele.id, weight: ele.weight });
});
taskApi
.setTaskSet({ examineConfigList, taskId: this.id })
.then((res) => {
this.$message({
type: "success",
message: "操作成功",
});
this.dialogVisible2 = false;
});
}
},
delTask(row) {
this.$confirm(
"删除任务及绩效数据,该操作不可逆,请谨慎操作",
"确认删除任务",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
taskApi.delTask(row.id).then((res) => {
this.$message({
type: "success",
message: "删除成功!",
});
this.getTaskList();
});
});
},
selectLeft(row) {
this.selectLeftRow = row.title + row.type;
},
saveTask() {
this.$refs.taskFormRef.validate((valid) => {
if (valid) {
if (this.taskData.id) {
taskApi.upDateTask(this.taskData).then((res) => {
this.$message({
type: "success",
message: "修改成功!",
});
this.dialogVisible1 = false;
this.getTaskList();
});
} else {
taskApi.addTask(this.taskData).then((res) => {
this.$message({
type: "success",
message: "新增成功!",
});
this.dialogVisible1 = false;
this.getTaskList();
});
}
} else {
console.log("error submit!!");
return false;
}
});
},
changeTotal(data) {
this.$nextTick(() => {
this.scoreList.forEach((ele) => {
ele.list.forEach((item) => {
item.weight = item.rightArr.reduce(
(total, ele) => total + (ele.weight || 0),
0
);
});
ele.weight = ele.list.reduce(
(total, ele) => total + (ele.weight || 0),
0
);
});
});
},
selectable(row,index){
if(row.roles.find((ele)=>ele.roleName=='普通员工')){
return true
}else{
return false
}
}
},
mounted() {
this.getTaskList();
},
};
</script>
<style lang="scss" scoped>
.project-list {
padding: 20px;
background-color: white;
height: 88vh;
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
}
.search-bar {
margin-bottom: 20px;
}
.demo-form-inline {
// display: flex;
// flex-wrap: nowrap;
// align-items: flex-start;
}
.demo-form-inline .el-form-item {
// margin-right: 50px; /* 30px */
// margin-bottom: 0;
}
.demo-form-inline .el-form-item:last-child {
margin-right: 0; /* 移除最后一个元素的右边距 */
}
.form-item {
flex: 1;
}
.form-item ::v-deep .el-form-item__content {
// width: 100%;
}
.form-item ::v-deep .el-input,
.form-item ::v-deep .el-select {
// width: 100%;
}
.search-buttons {
white-space: nowrap;
}
::v-deep .operation-buttons .el-button {
padding: 4px 8px;
margin: 0 2px;
}
::v-deep .operation-column {
background-color: #ffffff;
box-shadow: -2px 0 5px rgba(241, 112, 6, 0.1);
}
.el-button.is-text {
min-width: 32px !important;
}
.dialog-footer {
display: flex;
justify-content: center;
align-items: center;
}
.dialog-footer .el-button {
margin: 0 10px;
}
.delete-content {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.warning-icon {
font-size: 24px;
color: #e6a23c;
margin-right: 10px;
}
/* 添加以下样式来使对话框垂直居中 */
::v-deep .delete-dialog.el-dialog {
margin-top: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
::v-deep .el-table th {
text-align: center;
}
::v-deep .el-table .cell {
text-align: center;
}
::v-deep .el-dialog {
margin-top: 10% !important;
}
.modal {
display: flex;
border-bottom: 1px solid #ccc;
border-top: 1px solid #ccc;
}
.left {
width: 40%;
padding: 20px;
border-right: 1px solid #ccc;
height: 450px;
overflow: auto;
}
.right {
width: 60%;
padding: 20px 40px;
height: 450px;
overflow: auto;
div.center {
text-align: center;
position: relative;
}
}
.scoreBox {
position: absolute;
top: 45px;
right: -20px;
}
::v-deep .el-slider__runway {
height: 14px;
border-radius: 10px;
margin: 10px !important;
/* width: 95%; */
}
::v-deep .el-slider__runway.disabled .el-slider__bar {
height: 14px;
border-radius: 10px;
background-color: #ff5722;
}
::v-deep .el-slider__stop {
height: 14px;
border-radius: 0;
z-index: 1000;
}
::v-deep .el-slider__bar {
height: 14px;
border-radius: 10px;
background: linear-gradient(to right, #ffb144, #ff7d00);
}
::v-deep .el-slider__button {
display: none;
}
.scoreText {
color: #1686d8;
}
.setText {
margin-bottom: 20px;
text-align: right;
}
.leftSub {
padding: 10px 20px 10px 10px;
margin-bottom: 5px;
cursor: pointer;
}
.setTitle {
font-weight: bold;
}
::v-deep .el-collapse-item__content {
padding-bottom: 10px;
}
::v-deep .el-collapse-item__header {
}
.contentTitle {
}
.statusText {
color: #ff5722;
}
::v-deep .el-collapse {
border: none !important;
}
.search-buttons ::v-deep .el-button {
width: 90px !important;
height: 36px;
}
::v-deep .operation-buttons .el-button {
padding: 4px 8px;
margin: 0 2px;
font-weight: 600;
font-size: 14px;
}
.selectClass {
background-color: #4096ff;
color: #fff;
}
.scoreText {
color: #fff;
position: absolute;
top: 2px;
font-weight: 500;
display: flex;
flex-direction: row;
width: 40px;
}
.block {
width: 4px;
height: 24px;
background-color: #4096ff;
margin-right: 10px;
}
.stepBox {
position: absolute;
top: 29px;
height: 14px;
width: 2px;
background-color: #fff;
z-index: 100;
}
.totalBox {
width: 150px;
display: flex;
flex-direction: row;
font-size: 14px;
color: #333333;
}
</style>

View File

@ -0,0 +1,238 @@
<template>
<div class="leftBox">
<div class="topBox">
<el-date-picker
v-model="selectMonth"
type="month"
format="yyyy年MM月"
:clearable="false"
style="margin-bottom: 10px; width: 150px"
@change="changeMonth"
prefix-icon="false"
value-format="yyyy-MM-dd"
/>
<div>
<i
class="el-icon-arrow-left"
style="font-size: 18px; font-weight: bold"
@click="preMonth"
></i>
<i
class="el-icon-arrow-right"
style="font-size: 18px; font-weight: bold"
@click="nextMonth"
></i>
</div>
</div>
<el-calendar v-model="selectMonthValue">
<template #dateCell="{ data }">
<div
:key="data.day"
:id="data.day"
@click="searchLog(data, isOverDay(data.day))"
:class="{
dayBox: true,
disabled: isOverDay(data.day),
hasLog: calendarList.filter(
(ele) => ele.date == data.day + ' 00:00:00' && ele.state != -1
).length,
}"
>
{{ data.day.split("-")[2] }}
</div>
</template>
</el-calendar>
</div>
</template>
<script>
import { workLogApi, projectApi } from "@/utils/api";
export default {
name: "LeftMonth",
data() {
return {
selectMonth: this.moment().format("YYYY-MM"),
nowDay: this.moment().format("YYYY-MM-DD 23:59:59"),
userId: this.$store.state.user.id,
calendarList: [],
selectDay:
this.moment().date() > 10
? "0" + this.moment().date()
: this.moment().date(),
selectMonthValue: this.moment().format("YYYY-MM-DD"),
};
},
methods: {
preMonth() {
this.selectDay = "01";
this.selectMonth = this.moment(this.selectMonth)
.subtract(1, "months")
.format(`YYYY-MM-${this.selectDay}`);
this.changeMonth();
},
nextMonth() {
this.selectDay = "01";
this.selectMonth = this.moment(this.selectMonth)
.add(1, "months")
.format(`YYYY-MM-${this.selectDay}`);
this.changeMonth();
},
searchLog(data, overDay) {
let checkMonth = this.moment(data.day).format("YYYY-MM");
let selectMonth = this.moment(this.selectMonth).format("YYYY-MM");
this.$nextTick(() => {
if (overDay) {
this.$message({
message: "不能超过当前时间",
type: "warning",
});
this.$emit("setDisableTable", true);
} else {
this.selectDay = data.day.split("-")[2];
if (selectMonth !== checkMonth) {
this.selectMonth = data.day;
this.getLogMonth();
}
this.$emit("searchDay", data.day + " 00:00:00");
this.$emit("setDisableTable", false);
}
});
},
isOverDay(data) {
return new Date(data).getTime() > new Date(this.nowDay).getTime();
},
getLogMonth() {
if (this.$route.query.userId) {
this.userId = this.$route.query.userId;
}
let start = this.moment(this.selectMonth)
.startOf("month")
.format(`YYYY-MM-DD 00:00:00`);
let end = this.moment(this.selectMonth)
.endOf("month")
.format("YYYY-MM-DD 23:59:59");
workLogApi
.getLogData({
startDate: start,
endDate: end,
userId: this.userId,
})
.then((res) => {
this.calendarList = res.data;
});
},
changeMonth() {
this.$nextTick(() => {
if (this.selectMonth == this.selectMonthValue) return;
this.selectDay = "01";
this.selectMonth = this.moment(this.selectMonth).format(
`YYYY-MM-${this.selectDay}`
);
this.selectMonthValue = this.selectMonth;
this.searchLog(
{ day: this.selectMonth },
this.isOverDay(this.selectMonth)
);
this.searchLog(
{ day: this.selectMonth },
this.isOverDay(this.selectMonth)
);
this.getLogMonth();
});
},
},
watch: {},
mounted() {
this.getLogMonth();
},
};
</script>
<style scoped lang="scss">
.leftBox ::v-deep .el-calendar__header {
display: none;
}
.leftBox ::v-deep .el-date-editor {
.el-input__inner {
border: none;
padding-left: 0;
font-size: 18px;
color: #333333;
font-weight: 600;
}
}
.leftBox ::v-deep .el-calendar__body {
padding: 0 !important;
thead {
th {
font-family: PingFang SC;
font-weight: 400;
font-size: 16px;
color: #999999;
}
}
tbody {
.hasLog {
background: #71afff;
border-radius: 50%;
color: #fff;
}
td {
border: none;
padding: 10px 0;
background-color: #fff;
.el-calendar-day {
text-align: center;
padding: 0 !important;
font-size: 16px;
background-color: #fff;
span {
font-family: cursive !important;
}
}
&.next,
&.prev {
color: #333 !important;
}
&.is-selected .dayBox:not(.disabled) {
border-radius: 50% !important;
background-color: #4096ff;
color: #fff;
}
.el-calendar-day {
height: 40px;
width: 40px;
line-height: 40px;
border-radius: 50% !important;
&:hover {
background-color: #88bdfd;
color: #fff;
}
}
}
}
}
.topBox {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
i {
margin-right: 10px;
cursor: pointer;
}
}
.leftBox ::v-deep .disabled {
background-color: #fff !important;
color: #c0c4cc;
cursor: not-allowed;
border-radius: 50% !important;
background-color: #fff;
}
</style>

View File

@ -0,0 +1,490 @@
<template>
<div class="container">
<div class="topBox" v-show="!disableTable">
<div>
<div class="topTitle">当日日志</div>
<div class="topText">
总计填报工时:<span> {{ totalWorkTime }} </span>
</div>
</div>
<div>
<el-button type="primary" @click="handleAdd">
<el-icon class="el-icon-plus" />
添加日志
</el-button>
</div>
</div>
<!-- 表格区域 -->
<el-table :data="tableData" style="width: 100%" class="tableBox">
<el-table-column label="序号" width="70" type="index" />
<el-table-column label="项目名称" prop="projectName">
<template #default="scope">
<div>
<div v-if="scope.row.edit">
<el-select
v-model="scope.row.projectId"
placeholder="时间匹配的项目"
class="filter-select"
@change="
(val) => {
getVersionList(val, scope.row);
}
"
>
<el-option
v-for="item in projectListFilter"
:key="item.projectId"
:label="item.projectName"
:value="item.projectId"
/>
</el-select>
</div>
<div v-show="!scope.row.edit">{{ scope.row.projectName }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="版本" prop="versionNumber">
<template #default="scope">
<div>
<div v-if="scope.row.edit">
<el-select
v-model="scope.row.versionId"
placeholder="含有可选需求的版本"
class="filter-select"
ref="versionSelectRef"
@change="
(val) => {
getDemandList(val, scope.row);
}
"
>
<el-option
v-for="item in versionList.filter((ele) => ele.type == 0)"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</div>
<div v-show="!scope.row.edit">{{ scope.row.versionNumber }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="项目需求">
<template #default="scope">
<div>
<div v-if="scope.row.edit">
<el-select
v-model="scope.row.demandId"
placeholder="时间匹配且非已结束的需求"
class="filter-select"
@change="openContent(scope.row)"
ref="demandSelectRef"
>
<el-option
v-for="item in demandList"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</div>
<div v-show="!scope.row.edit">{{ scope.row.title }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="工作内容" prop="workContent">
<template #default="scope">
<div @click="openContent(scope.row)">
<el-popover
placement="bottom"
width="400"
trigger="click"
v-model="scope.row.showContent"
:key="scope.row.loggerId"
>
<el-input
type="textarea"
:rows="5"
placeholder="请输入内容"
v-model="scope.row.workContent"
:disabled="!scope.row.edit"
>
</el-input>
<div
style="margin-top: 10px; text-align: right"
v-show="!disableTable&&scope.row.edit"
>
<el-button type="primary" @click="saveContent(scope.row, 1)"
>确认</el-button
>
<el-button @click="saveContent(scope.row, 0)">取消</el-button>
</div>
<div slot="reference">
<div
style="cursor: pointer"
:class="{
contentText: true,
noneText: !scope.row.workContent,
}"
>
{{ scope.row.workContent || "请输入" }}
</div>
</div>
</el-popover>
</div>
</template>
</el-table-column>
<el-table-column label="工时分配" prop="estimatedWorkHours">
<template #default="scope">
<div>
<div v-if="scope.row.edit">
<el-select
v-model="scope.row.workTime"
placeholder="请选择工时"
class="filter-select"
filterable
allow-create
>
<el-option
v-for="item in workTimeList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</div>
<div v-show="!scope.row.edit">{{ scope.row.workTime }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<div v-show="!disableTable">
<el-button type="text" @click="handleEdit(scope.row)">
{{ scope.row.loggerId && !scope.row.edit ? "编辑" : "确认" }}
</el-button>
<el-button
type="text"
@click="handleDelete(scope.row, scope.$index)"
style="color: #666"
>
{{ scope.row.loggerId && !scope.row.edit ? "删除" : "取消" }}
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { demandApi, workLogApi, projectApi } from "@/utils/api";
export default {
name: "RightTable",
props: {
selectDay: {
type: String,
default: "",
},
disableTable: {
type: Boolean,
default: false,
},
},
data() {
return {
statusList: [],
userId: this.$store.state.user.id,
projectId: "",
tableData: [],
projectList: [],
versionList: [],
demandList: [],
hasTimeLong: 0,
workTimeList: [],
oldContent: "",
};
},
methods: {
init() {
if (this.$route.query.userId) {
this.userId = this.$route.query.userId;
this.userName = this.$route.query.nickName;
this.projectId = this.$route.porjectId;
}
//
},
getLogList() {
workLogApi
.getLogDataDetail({
createBy: this.userId,
loggerDate: this.selectDay,
projectId: this.projectId,
})
.then((res) => {
this.tableData = res.data.map((ele) => {
ele.edit = false;
ele.showContent = false;
return ele;
});
});
},
handleDelete(row, index) {
if (row.loggerId && !row.edit) {
this.$confirm("此操作将永久删除该版本号, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
workLogApi.delLog(row.loggerId).then((res) => {
this.$message({
type: "success",
message: "删除成功!",
});
this.getLogList();
this.$emit("changeCaleder");
});
})
.catch(() => {});
} else if (!row.loggerId) {
this.tableData = this.tableData.filter((ele, ind) => index != ind);
} else {
row.edit = false;
}
//
},
handleAdd() {
if (this.tableData.filter((ele) => ele.edit).length) {
this.$message({
message: "请先保存未完成数据",
type: "warning",
});
return;
}
this.versionList = [];
this.demandList = [];
this.oldContent = "";
this.getDayTime("add");
},
async handleEdit(row) {
if (
this.tableData.filter((ele) => ele.edit && ele.loggerId != row.loggerId)
.length
) {
this.$message({
message: "请先保存未完成数据",
type: "warning",
});
return;
}
if (row.edit) {
if (row.workTime > this.workTimeList[this.workTimeList.length - 1]) {
this.$modal.msgWarning("工时超过最大值");
return;
} else if (!row.workContent) {
this.$modal.msgWarning("工作日志不能为空");
return;
} else if ((String(row.workTime).split(".")[1] || "").length > 1) {
this.$modal.msgWarning("工时只允许一位小数");
return;
} else if (row.workTime <= 0) {
this.$modal.msgWarning("工时不能小于等于0");
return;
}
let param = {
...row,
};
if (row.loggerId) {
await workLogApi.editLog(param);
} else {
await workLogApi.addLog(param);
this.$emit("changeCaleder");
}
this.$modal.msgSuccess("操作成功");
row.edit = false;
this.getLogList();
} else {
row.edit = true;
this.oldContent = row.workContent;
this.getDayTime("edit", row);
this.getVersionList(row.projectId, row, true);
}
},
async getAllProject() {
const response = await projectApi.listProject({
pageSize: 10000,
pageNum: 1,
});
this.projectList = response.rows;
},
//
async getDayTime(type, row) {
const res = await workLogApi.getDayTime({
loggerDate: this.selectDay,
});
this.hasTimeLong = Number(res.data);
if (type == "add") {
let now = new Date(
this.moment().format("YYYY-MM-DD 00:00:00")
).getTime();
let select = new Date(this.selectDay).getTime();
let row = {
loggerDate: this.selectDay,
projectId: "",
workTime: "",
workContent: "",
state: now == select ? 0 : 1,
userId: this.userId,
demandId: "",
edit: true,
};
this.computedTime(0);
if (this.workTimeList.length) this.tableData.push(row);
} else {
this.computedTime(row.workTime);
}
},
computedTime(time) {
let length = (this.hasTimeLong + (Number(time) || 0)).toFixed(1);
if (length <= 0) {
this.$modal.msgWarning("当日剩余工时为0");
this.workTimeList = [];
return;
}
this.workTimeList = new Array((length * 10) / 1)
.fill(0)
.map((ele, index) => {
return (index + 1) / 10;
});
},
getVersionList(val, row, isOpen) {
if (!isOpen) {
row.versionId = "";
row.demandId = "";
}
this.$nextTick(async () => {
const res = await demandApi.getVersionTree({
projectId: val,
userId: this.userId,
demandStatusList: [0, 1, 2, 3],
queryDate: this.selectDay,
});
this.versionList = res.data.filter((ele) => ele.type == 0);
this.demandList = res.data.filter((ele) => ele.type == 1);
if (!isOpen) this.$refs.versionSelectRef.toggleMenu();
if (isOpen) this.getDemandList(row.versionId, row, true);
});
},
async getDemandList(val, row, isOpen) {
if (!isOpen) row.demandId = "";
if (this.versionList.find((ele) => ele.id == val)) {
this.demandList = this.versionList.find(
(ele) => ele.id == val
).childrenList;
} else {
this.demandList = [];
}
if (!this.demandList.find((ele) => ele.id == row.demandId)) {
row.demandId = "";
}
if (!isOpen) this.$refs.demandSelectRef.toggleMenu();
},
openContent(row) {
row.showContent = true;
},
saveContent(row, type) {
this.$nextTick(() => {
if (type == 0) {
row.workContent = this.oldContent;
} else {
this.oldContent = row.workContent;
}
row.showContent = false;
});
},
},
watch: {
$route(to, from) {
if (this.$route.query.userId) {
this.init();
}
},
selectDay(newVal) {
this.getLogList();
},
disableTable(newVal) {
if (newVal) {
this.tableData = [];
}
},
versionList(newVal) {},
},
computed: {
projectListFilter() {
let now = new Date(this.selectDay).getTime();
return this.projectList.filter(
(ele) =>
new Date(ele.startDate).getTime() <= now &&
new Date(ele.endDate).getTime() >= now
);
},
totalWorkTime() {
return (
this.tableData
.map((ele) => Number(ele.workTime))
.reduce((total, now) => {
return now * 10 + total;
}, 0) / 10
).toFixed(1);
},
},
mounted() {
this.init();
this.getAllProject();
},
};
</script>
<style scoped lang="scss">
.container {
padding: 20px;
background-color: #ffffff;
min-width: 200px;
}
.topBox {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.topTitle {
font-size: 18px;
font-weight: 600;
margin-bottom: 5px;
}
.topText {
color: #999999;
font-weight: 600;
font-size: 16px;
span {
margin: 0 5px;
color: #333;
}
}
}
.contentText {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
.noneText {
color: #999;
}
</style>

View File

@ -34,6 +34,7 @@
:highligt="true"
ref="projectRef"
style="height: 100%"
rowKey="projectId"
/>
</div>
</div>
@ -90,6 +91,7 @@
type="month"
format="yyyy年MM月"
:clearable="false"
@change="changeMonth"
/>
</div>
<div class="calendar-view" v-if="projectInfo">
@ -106,7 +108,7 @@
disabled: isFutureDate(data.date) && isInProjectRange(data),
}"
>
{{ data.day.split("-")[2] }}
{{ data.day.split("-")[1] + "/" + data.day.split("-")[2] }}
</div>
</template>
</el-calendar>
@ -150,6 +152,7 @@
<el-button
type="text"
v-if="projectInfo.userId == $store.state.user.id"
@click="logForm.workTime = logForm.max"
>{{ `当天剩余可分配工时:${logForm.max}人天` }}</el-button
>
</el-form-item>
@ -168,6 +171,9 @@
v-if="projectInfo.userId == $store.state.user.id"
>
<el-button @click="logDialogVisible = false">取消</el-button>
<el-button v-if="logForm.loggerId" type="danger" @click="delWorkLog"
>删除</el-button
>
<el-button type="primary" @click="saveWorkLog"></el-button>
</span>
</template>
@ -220,7 +226,11 @@ export default {
res.data.startDate.split(" ")[0] + " 00:00:00";
this.projectInfo.endDate = res.data.endDate.split(" ")[0] + " 23:59:59";
if (this.projectInfo.userId == this.$store.state.user.id) {
this.selectedDate = new Date();
} else {
this.selectedDate = new Date(this.projectInfo.startDate); //
}
const res2 = await workLogApi.getLogData({
projectId: this.projectInfo.projectId,
@ -232,13 +242,23 @@ export default {
this.initDateColor(first);
},
initDateColor(first) {
let boxs = document.getElementsByClassName("date-cell");
for (var i = 0; i < boxs.length; i++) {
boxs[i].style = "";
}
this.logData.map((item) => {
var ele = document.getElementById(item.date.split(" ")[0]);
if (ele) {
if (item.state == -1) {
ele.style = "background:#ecf5ff";
} else {
ele.style = "background:#409eff;color:#fff";
} else if (item.state == 0||item.state == 1) {
ele.style = `background:linear-gradient(to right, #409eff ${
(item.workTime || 1) * 100
}% , #ecf5ff ${(item.workTime || 1) * 100}%);color:${
item.workTime > 0.65 ? "#fff" : "#333"
};`;
}
}
});
@ -254,7 +274,6 @@ export default {
this.$modal.msgWarning("请先选择一个项目");
return;
}
this.handleProjectClick(this.projectInfo);
const clickedDate = new Date(data.day);
const currentDate = new Date();
@ -289,6 +308,7 @@ export default {
projectId: this.projectInfo.projectId,
loggerDate: this.logForm.loggerDate,
});
if (res.data) {
this.logForm.workTime = res.data.workTime;
this.logForm.workContent = res.data.workContent;
this.logForm.loggerId = res.data.loggerId;
@ -297,12 +317,41 @@ export default {
this.logForm.workContent = "";
this.logForm.loggerId = "";
}
} else {
this.logForm.workTime = "";
this.logForm.workContent = "";
this.logForm.loggerId = "";
}
const res = await workLogApi.getDayTime({
loggerDate: this.logForm.loggerDate,
});
this.logForm.max =
Number(res.data) + (Number(this.logForm.workTime) || 0);
this.logForm.max = (
Number(res.data) + (Number(this.logForm.workTime) || 0)
).toFixed(1);
this.logDialogVisible = true;
this.$nextTick(() => {
this.changeMonth();
});
},
changeMonth() {
let that = this;
this.$nextTick(async () => {
const res2 = await workLogApi.getLogData({
projectId: that.projectInfo.projectId,
startDate:
that
.moment(that.selectedDate)
.startOf("month")
.format("YYYY-MM-DD") + " 00:00:00",
endDate:
that.moment(that.selectedDate).endOf("month").format("YYYY-MM-DD") +
" 23:59:59",
userId: that.projectInfo.userId,
});
that.logData = res2.data;
that.initDateColor(1);
});
},
isInProjectRange(data) {
if (!this.projectInfo) return false;
@ -377,14 +426,13 @@ export default {
}
this.logDialogVisible = false;
this.$modal.msgSuccess("操作成功");
this.handleProjectClick(this.projectInfo);
const response = await workLogApi.userProject(this.projectInfo.userId);
this.projectList = response.data;
this.handleProjectClick(this.projectInfo);
},
async fetchUserProjects() {
try {
const response = await workLogApi.userProject(this.projectInfo.userId);
console.log(12333, this.projectList);
this.projectList = response.data;
if (this.projectList.length) {
let arr = [];
@ -415,6 +463,17 @@ export default {
//
this.fetchUserProjects();
},
//
delWorkLog() {
this.$modal.confirm(`是否确认删日志`).then(async () => {
await workLogApi.delLog(this.logForm.loggerId);
this.$modal.msgSuccess("操作成功");
this.logDialogVisible = false;
this.changeMonth();
const response = await workLogApi.userProject(this.projectInfo.userId);
this.projectList = response.data;
});
},
},
watch: {
$route(to, from) {
@ -511,22 +570,30 @@ export default {
color: white; /* 白色文字 */
padding: 5px 0; /* 增加一些内边距 */
}
.project-info-calendar ::v-deep .el-calendar__body th {
height: 60px !important;
font-weight: bold !important;
font-size: 20px;
}
.project-info-calendar .calendar-view ::v-deep .el-calendar-day {
height: 65px; /* 增加日期单元格高度(原高度 + 5px */
height: 70px; /* 增加日期单元格高度(原高度 + 5px */
// padding-top: 5px;
padding: 3px;
padding: 10px;
border-radius: 10px;
}
.project-info-calendar .calendar-view ::v-deep .el-calendar-day.disabled {
cursor: not-allowed;
}
.project-info-calendar .calendar-view ::v-deep table * {
border: none;
}
.project-info-calendar .calendar-view ::v-deep .date-cell {
cursor: pointer;
height: 100%;
text-align: center;
line-height: 55px;
line-height: 50px;
border-radius: 10px;
}
.project-info-calendar .calendar-view ::v-deep .in-range {
@ -534,7 +601,7 @@ export default {
}
.project-info-calendar .calendar-view ::v-deep .out-range {
background-color: rgba(0, 0, 0, 0.05) !important;
background-color: rgba(0, 0, 0, 0.1) !important;
}
.project-info-calendar .calendar-view ::v-deep .disabled {

View File

@ -0,0 +1,87 @@
<template>
<div class="layout">
<!-- 左侧树形菜单 -->
<LeftMonth
class="sidebar"
ref="leftRef"
@searchDay="searchDay"
@setDisableTable="setDisableTable"
/>
<!-- 右侧表格和筛选 -->
<RightTable
class="main-content"
ref="rightRef"
:selectDay="selectDay"
:disableTable="disableTable"
@changeCaleder="changeCaleder"
/>
</div>
</template>
<script>
//
import LeftMonth from "./components/leftMonth.vue";
import RightTable from "./components/rightTable.vue";
export default {
name: "worklog",
components: {
LeftMonth,
RightTable,
},
data() {
return {
selectDay: "",
disableTable: false,
userId: this.$store.state.user.id,
};
},
methods: {
init() {
this.selectDay = this.moment().format("YYYY-MM-DD 00:00:00");
if (this.$route.query.userId && this.userId != this.$route.query.userId) {
this.setDisableTable(false);
}
},
searchDay(data) {
this.selectDay = data;
},
setDisableTable(data) {
if (this.$route.query.userId && this.userId != this.$route.query.userId) {
data = true;
}
this.disableTable = data;
},
changeCaleder() {
this.$refs.leftRef.getLogMonth();
},
},
mounted() {
this.init();
},
};
</script>
<style scoped lang="scss">
.layout {
display: flex;
width: 100%;
min-height: 100vh;
background-color: #f5f7fa;
}
.sidebar {
flex-grow: 1;
max-width: 320px;
width: 360px;
height: 100vh;
background-color: #fff;
padding: 20px;
}
.main-content {
flex: 1;
background-color: #ffffff;
padding: 20px;
}
</style>