Compare commits

...

31 Commits
main ... dev

Author SHA1 Message Date
wangjiuyun 5de5b930bf 考核看板详情回显问题 2025-05-14 15:29:26 +08:00
rdpnr_hemingxia e63e98399a 绩效评价取值问题 2025-04-30 18:14:02 +08:00
rdpnr_hemingxia c66e0e5f06 菜单问题调整,项目详情附件改为非必填 2025-04-29 18:08:57 +08:00
rdpnr_hemingxia 5757b4d23c 样式调整 2025-04-28 10:56:18 +08:00
rdpnr_hemingxia b3bd2eff61 文件调试完成
考核看板初步完成
2025-04-25 18:04:28 +08:00
rdpnr_hemingxia b1251e3bc5 附件完成 2025-04-23 15:32:58 +08:00
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
43 changed files with 7994 additions and 469 deletions

1
.gitignore vendored
View File

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

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: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 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,29 +1,71 @@
<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">
<div class="flex-row aic jcfe mb10" style="gap: 10px">
<el-input
v-model="searchForm.nickName"
style="width: 300px"
placeholder="输入名称"
/>
<div>
<el-button type="primary" size="medium" @click="fetchUserList"
>查询</el-button
>
<el-button type="primary" size="medium" @click="resetTable"
>重置</el-button
>
</div>
</div>
<CustomTable
ref="customTableRef"
: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 +81,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 +95,37 @@ 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,
},
userIdList: {
type: Array,
default: () => [],
},
isFilter: {
type: Boolean,
default: false,
},
},
data() {
return {
@ -66,106 +133,229 @@ 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: [],
searchForm: {
nickName: "",
},
};
},
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)
);
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.searchForm,
userIdList:this.userIdList,
});
this.userData = response.rows;
this.total = response.total;
if (!this.multiSelect) {
this.userData.forEach((ele) => {
if (ele.userId == this.selectedUsers[0]?.userId)
this.$refs.customTableRef?.setCurrentRow(ele);
});
}
},
resetTable() {
this.searchForm.nickName = "";
this.fetchUserList();
},
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 (row) {
this.$refs.customTableRef.toggleRowSelection(row, true)
}
})
if (!this.showSelection) {
let row = this.userData.find(
(ele) => ele.userId == newVal[0]?.userId
);
if (row) {
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,
},
userIdList: {
handler(newVal) {
this.fetchUserList();
},
immediate: true,
deep: true,
},
},
mounted() {
this.fetchTreeData()
this.fetchUserList()
this.fetchTreeData();
if(!this.isFilter){
this.fetchUserList();
}
},
}
};
</script>
<style lang="scss" scoped>
@ -206,6 +396,10 @@ export default {
margin-left: 0;
}
::v-deep .el-table {
max-height:360px !important;
max-height: 360px !important;
}
</style>
::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

@ -161,6 +161,48 @@ export const dynamicRoutes = [
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
}
]
},
{
path: '/project/detail',
component: Layout,
hidden: true,
permissions: ['project:list:detail'],
children: [
{
path: '/project/detail',
component: () => import('@/views/project/detail'),
name: 'GenEdit',
meta: { title: '项目详情', activeMenu: '/project/detail' }
}
]
},
{
path: '/project/detail',
component: Layout,
hidden: true,
permissions: ['project:list:detail'],
children: [
{
path: '/project/detail',
component: () => import('@/views/project/detail'),
name: 'GenEdit',
meta: { title: '项目详情', activeMenu: '/project/detail' }
}
]
},
{
path: '/project/detail',
component: Layout,
hidden: true,
permissions: ['project:list:demand'],
children: [
{
path: '/project/demandManage',
component: () => import('@/views/project/demandManage/demandManage'),
name: 'GenEdit',
meta: { title: '项目详情', activeMenu: '/project/demandManage' }
}
]
}
]

View File

@ -1,6 +1,6 @@
import request from '@/utils/request'
// 登录板块api
// 公用板块
// 项目板块
export const projectApi = {
@ -87,6 +87,11 @@ export const workLogApi = {
method: 'put',
data: data,
}),
delLog: (id) => request({
url: `/business/work/hour/${id}`,
method: 'delete',
}),
}
@ -129,4 +134,151 @@ export const systemApi = {
url: '/system/user/deptTree',
method: 'get',
}),
fileUpload: process.env.NODE_ENV == 'development' ? '/common/upload' : '/prod-api/common/upload',
downFile: (data) => request({
url: '/common/download',
method: 'get',
params: data,
}),
delFile: (id) => request({
url: `/business/project/file/${id}`,
method: 'delete',
}),
delFileBatch: (id) => request({
url: `/business/project/file/batch/${id}`,
method: 'delete',
}),
}
// 任务考核板块
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',
}),
delTaskModule: (id) => request({
url: `/examine/template/${id}`,
method: 'delete',
}),
getTaskSet: (id) => request({
url: `/task/target/${id}`,
method: 'get',
}),
setTaskSet: (data) => request({
url: `/task/config/update`,
method: 'put',
data: data,
}),
getTaskModel: (data) => request({
url: `/examine/template/list`,
method: 'get',
params: data,
}),
// 获取模板配置
getTaskModelSet: (id) => request({
url: `/examine/template/list/${id}`,
method: 'get',
}),
}
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',
}),
getDemandDetail: (id) => request({
url: `/demand/${id}`,
method: 'get',
}),
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,448 @@
<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"
/>
<!-- 节点文本 -->
<el-tooltip
class="item"
effect="light"
:content="data.title"
placement="top"
:disabled="data.title.length > 8 ? false : true"
>
<div
:class="[
'nodeLabel',
data.nodeId === selectedId ? 'selected' : '',
]"
>
{{ data.title }}
</div>
</el-tooltip>
<!-- 右侧数字标记 -->
<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;
}
if (this.demandData.name.length > 10) {
this.$message({
message: "版本号限制10个字符",
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;
max-width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 20px;
}
.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

@ -1,219 +1,143 @@
<template>
<div class="project-management">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="120px"
class="custom-form"
>
<div class="project-management" v-loading="fileLoading">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" class="custom-form">
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="项目名称" prop="projectName">
<div>
<el-input
v-model="formData.projectName"
placeholder="请输入项目名称"
:disabled="isEditing"
class="full-width longInput"
/>
<el-input v-model="formData.projectName" placeholder="请输入项目名称" :disabled="isEditing"
class="full-width longInput" />
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目编码" prop="projectCode">
<el-input
v-model="formData.projectCode"
class="full-width"
:disabled="isEditing"
/>
<el-input v-model="formData.projectCode" class="full-width" :disabled="isEditing" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目负责人" prop="projectLeader">
<el-input
v-model="formData.projectLeaderName"
placeholder="选择项目负责人"
readonly
:disabled="isEditing"
@click.native="openProjectManagerSelect"
/>
<el-input v-model="formData.projectLeaderName" placeholder="选择项目负责人" 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-select
v-model="formData.projectState"
placeholder="根据时间自动生成"
disabled
class="full-width"
>
<el-form-item label="数据状态" prop="dataState" style="display: none">
<el-select v-model="formData.dataState" placeholder="根据时间自动生成" disabled class="full-width">
<el-option label="未启动" value="0" />
<el-option label="进行中" value="1" />
<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">
<el-input
v-model.number="formData.budgetDate"
:min="0"
:disabled="isEditing"
class="full-width"
/>
<el-input v-model.number="formData.budgetDate" :min="0" :disabled="isEditing" class="full-width" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="开始日期" prop="startDate">
<el-date-picker
v-model="formData.startDate"
type="date"
placeholder="选择开始日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
class="full-width"
:disabled="isEditing"
/>
<el-date-picker v-model="formData.startDate" type="date" placeholder="选择开始日期" format="yyyy-MM-dd"
value-format="yyyy-MM-dd" class="full-width" :disabled="isEditing" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期" prop="endDate">
<el-date-picker
v-model="formData.endDate"
type="date"
placeholder="选择结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
class="full-width"
:disabled="isEditing"
/>
<el-date-picker v-model="formData.endDate" type="date" placeholder="选择结束日期" format="yyyy-MM-dd"
value-format="yyyy-MM-dd" class="full-width" :disabled="isEditing" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="附件" prop="fileList">
<div>
<el-upload class="upload-demo" ref="upload" :action="fileUpload" :show-file-list="false"
:auto-upload="true" :multiple="true" :before-upload="beforeUpload" :on-success="successUpload" :headers="{
Authorization: 'Bearer ' + token,
}" :data="{}">
<div class="flex-row aic" style="gap: 10px;margin-bottom: 10px;">
<el-button slot="trigger" size="small" type="default"
style="width: 200px;color: #333;font-weight: 500;">上传附件</el-button>
<div slot="tip" style="color: #999999;font-size: 12px;">单个附件限制100M</div>
</div>
</el-upload>
<div class="fileBox">
<div v-for="(item, index) in formData.fileList" class="fileRow flex-row jcsb aic" :key="index">
<div class="flex-row aic fileItem">
<img class="" :src="filePng" v-if="getFileType(item.fileName) == 'file'"> </img>
<img class="" :src="imagePng" v-else-if="getFileType(item.fileName) == 'image'"> </img>
<img class="" :src="zipPng" v-else-if="getFileType(item.fileName) == 'zip'"> </img>
<div @click="downFile(item.fileUrl)">{{ item.fileName }}</div>
</div>
<div class="del" @click="delFile(item)">×</div>
</div>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="form-actions jcc" v-show="!isEditing">
<el-button
type="primary"
@click="saveProject"
v-hasPermi="['project:detail:save']"
>保存</el-button
>
<el-button type="primary" @click="saveProject" v-hasPermi="['project:detail:save']"></el-button>
<el-button @click="cancelEdit"></el-button>
</div>
<div class="userBox">
<div class="table-actions">
<el-button
type="primary"
@click="addUser"
v-hasPermi="['project:detail:addUser']"
>新增成员</el-button
>
<el-button type="primary" @click="addUser" v-hasPermi="['project:detail:addUser']"></el-button>
</div>
<div class="f1">
<CustomTable
:columns="columns"
:tableData="tableData"
:show-selection="false"
:show-index="true"
:show-pagination="false"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="height: 100%;"
>
<CustomTable :columns="columns" :tableData="tableData" :show-selection="false" :show-index="true"
:show-pagination="false" @size-change="handleSizeChange" @current-change="handleCurrentChange"
style="height: 100%">
<template slot="userName" slot-scope="{ row }">
<el-input
v-if="row.isNew"
v-model="row.userName"
placeholder="选择人员"
readonly
@click.native="openSelectUser(row)"
/>
<el-input v-if="row.isNew" v-model="row.userName" placeholder="选择人员" readonly
@click.native="openSelectUser(row)" />
<span v-else>{{ row.userName }}</span>
</template>
<template slot="post" slot-scope="{ row }">
<el-select
v-if="
(row.isEditing === true && row.teamId == row.teamId) ||
row.isNew
"
v-model="row.postId"
placeholder="请选择职位"
>
<el-option
v-for="post in postOptions"
:key="post.dictValue"
:label="post.dictLabel"
:value="post.dictValue"
/>
<el-select v-if="
(row.isEditing === true && row.teamId == row.teamId) ||
row.isNew
" v-model="row.postId" placeholder="请选择职位">
<el-option v-for="post in postOptions" :key="post.dictValue" :label="post.dictLabel"
:value="post.dictValue" />
</el-select>
<span v-else>{{
postOptions.find((post) => post.dictValue === row.postId)
? postOptions.find((post) => post.dictValue === row.postId)
.dictLabel
.dictLabel
: ""
}}</span>
</template>
<template slot="operation" slot-scope="{ row }">
<div class="operation-buttons">
<template v-if="row.isNew">
<el-button
text
type="text"
size="mini"
@click="confirmAddUser(row)"
>确认</el-button
>
<el-button
text
type="text"
size="mini"
@click="cancelAddUser(row)"
>取消</el-button
>
<el-button text type="text" size="mini" @click="confirmAddUser(row)"></el-button>
<el-button text type="text" size="mini" @click="cancelAddUser(row)"></el-button>
</template>
<template v-else-if="row.isEditing && row.teamId == row.teamId">
<el-button
text
type="text"
size="mini"
@click="saveUserEdit(row)"
>保存</el-button
>
<el-button
text
type="text"
size="mini"
@click="cancelUserEdit(row)"
>取消</el-button
>
<el-button text type="text" size="mini" @click="saveUserEdit(row)"></el-button>
<el-button text type="text" size="mini" @click="cancelUserEdit(row)"></el-button>
</template>
<template v-else>
<el-button
type="text"
size="mini"
v-hasPermi="['project:detail:editUser']"
@click="editUser(row)"
>编辑</el-button
>
<el-button
type="text"
size="mini"
v-hasPermi="['project:detail:workLog']"
@click="showTimesheet(row)"
>工作日志</el-button
>
<el-button
type="text"
size="mini"
v-hasPermi="['project:detail:deleteUser']"
@click="confirmDelete(row)"
>删除</el-button
>
<el-button type="text" size="mini" v-hasPermi="['project:detail:editUser']"
@click="editUser(row)">编辑</el-button>
<el-button type="text" size="mini" v-hasPermi="['project:detail:workLog']"
@click="showTimesheet(row)">工作日志</el-button>
<el-button type="text" size="mini" v-hasPermi="['project:detail:deleteUser']"
@click="confirmDelete(row)">删除</el-button>
</template>
</div>
</template>
@ -221,22 +145,12 @@
</div>
</div>
<SelectUser
:dialogVisible="showSelectUser"
:multi-select="false"
:currentSelectedUser="currentSelectedUser"
@confirm="handleUserConfirm"
@close="closeSelectUser"
/>
<SelectUser :dialogVisible="showSelectUser" :multi-select="false" :currentSelectedUser="currentSelectedUser"
@confirm="handleUserConfirm" @close="closeSelectUser" />
<SelectUser
v-if="showProjectManagerSelect"
:dialogVisible="showProjectManagerSelect"
:multi-select="false"
:selected-users="projectManagerSelectedUser"
@confirm="handleProjectManagerConfirm"
@close="closeProjectManagerSelect"
/>
<SelectUser :dialogVisible="showProjectManagerSelect" :multi-select="false"
:currentSelectedUser="projectManagerSelectedUser" @confirm="handleProjectManagerConfirm"
@close="closeProjectManagerSelect" />
</div>
</template>
@ -244,6 +158,10 @@
import CustomTable from "@/components/CustomTable.vue";
import SelectUser from "@/components/SelectUser.vue";
import { projectApi, systemApi } from "@/utils/api";
import filePng from "@/assets/images/file.png";
import zipPng from "@/assets/images/zip.png";
import imagePng from "@/assets/images/image.png";
import { getToken } from "@/utils/auth";
export default {
components: {
@ -268,8 +186,13 @@ export default {
endDate: "",
budgetDate: 0,
state: 0,
dataState: 0,
fileList: []
},
postOptions: [],
statusList: [],
fileUpload: systemApi.fileUpload,
columns: [
{ prop: "userName", label: "姓名" },
{ prop: "post", label: "项目职位" },
@ -277,6 +200,8 @@ export default {
{ prop: "operation", label: "操作", width: "250px" },
],
tableData: [],
fileLoading: false,
currentEditingRow: {},
currentSelectedUser: [],
projectManagerSelectedUser: [],
@ -287,8 +212,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,9 +225,17 @@ export default {
],
budgetDate: [
{ required: true, message: "预算天数为必填", trigger: "blur" },
{ validator: this.validateDates, trigger: "change" },
],
// fileList: [
// { required: true, message: "", trigger: "blur" },
// ],
},
filePng,
zipPng,
imagePng,
token: getToken(),
delFileArr: []
};
},
watch: {
@ -319,11 +256,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 +288,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,
};
@ -363,6 +300,8 @@ export default {
this.formData.projectId = res.data.projectId;
this.formData.projectCode = res.data.projectCode;
this.$modal.msgSuccess("操作成功");
this.fetchProjectData()
} else {
const hasLog = await projectApi.projectHasLogData({
projectId: this.formData.projectId,
@ -376,15 +315,28 @@ export default {
)
.then(async () => {
await projectApi.updateProject(projectDataToSave);
if (this.delFileArr.length) {
await systemApi.delFileBatch(this.delFileArr.join(','))
this.delFileArr = []
}
this.fetchProjectData()
});
} else {
await projectApi.updateProject(projectDataToSave);
if (this.delFileArr.length) {
await systemApi.delFileBatch(this.delFileArr.join(','))
this.delFileArr = []
}
this.fetchProjectData()
this.$modal.msgSuccess("操作成功");
}
}
} else {
this.$modal.msgError("请检查表单填写是否正确");
this.$modal.msgSuccess("操作成功");
// this.$modal.msgSuccess("");
}
});
},
@ -405,10 +357,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 +391,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 +411,8 @@ export default {
this.currentSelectedUser = row.userId
? [{ userId: row.userId, userName: row.userName }]
: [];
console.log(111, this.currentSelectedUser);
this.showSelectUser = true;
},
handleUserConfirm(selectedUsers) {
@ -477,11 +433,11 @@ export default {
openProjectManagerSelect() {
this.projectManagerSelectedUser = this.formData.projectLeader
? [
{
id: this.formData.projectLeaderId,
name: this.formData.projectLeader,
},
]
{
userId: this.formData.projectLeader,
nickName: this.formData.projectLeaderName,
},
]
: [];
this.showProjectManagerSelect = true;
},
@ -532,7 +488,7 @@ export default {
},
async fetchProjectData(id) {
try {
const response = await projectApi.getProjectDetail(id);
const response = await projectApi.getProjectDetail(id||this.formData.projectId);
const projectData = response.data;
let {
projectId,
@ -543,6 +499,7 @@ export default {
projectState,
budgetDate,
state,
fileList
} = response.data;
let startDate = projectData.startDate.split(" ")[0];
let endDate = projectData.endDate.split(" ")[0];
@ -557,6 +514,7 @@ export default {
endDate,
budgetDate,
state,
fileList
};
this.updateProjectState(); //
} catch (error) {
@ -566,7 +524,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);
@ -597,6 +557,90 @@ export default {
this.$modal.msgError("更新用户信息失败");
}
},
beforeUpload(file) {
this.fileLoading = true;
if (file.size > 1024 * 1024 * 100) {
this.fileLoading = false;
this.$message({
type: "warning",
message: "单个文件不能大于100M!",
});
return false;
}
},
successUpload(res, file, fileList) {
if (!fileList.filter((ele) => ele.percentage != 100).length) {
this.fileLoading = false;
}
if (res.code == 200) {
this.formData.fileList.push({
fileName: res.originalFilename, //
filePath: res.filePath, //
fileNewName: res.newFileName, //
fileUrl: res.url,
});
} else {
this.fileLoading = false;
this.$message({
type: "error",
message: res.msg,
});
}
},
delFile(row) {
if (row.id) {
this.$confirm("此操作将永久删除文件, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
this.delFileArr.push(row.id)
this.formData.fileList = this.formData.fileList.filter(
(ele) => ele.fileNewName != row.fileNewName
);
this.$message({
type: 'success',
message: "删除成功!"
})
});
} else {
this.formData.fileList = this.formData.fileList.filter(
(ele) => ele.fileNewName != row.fileNewName
);
this.$message({
type: 'success',
message: "删除成功!"
})
}
},
getFileType(name) {
var data = {
jpg: 'image',
jpeg: 'image',
png: 'image',
gif: 'image',
bmp: 'image',
tiff: 'image',
svg: 'image',
pdf: 'file',
doc: 'file',
docx: 'file',
xls: 'file',
xlsx: 'file',
txt: 'file',
ppt: 'file',
zip: 'zip',
rar: 'zip',
tar: 'zip',
targz: 'zip',
}
return data[name.split('.')[1]] || 'file'
},
downFile(url) {
window.open(url);
},
},
mounted() {
const projectId = this.$route.params.id || this.$route.query.id;
@ -608,6 +652,7 @@ export default {
} else {
this.updateProjectState(); //
}
//
},
};
</script>
@ -638,7 +683,8 @@ export default {
}
.custom-form ::v-deep .el-form-item {
margin-bottom: 25px; /* 增加表单行间距 */
margin-bottom: 25px;
/* 增加表单行间距 */
}
.custom-form ::v-deep .el-form-item__content {
@ -647,22 +693,29 @@ export default {
}
.custom-form ::v-deep .el-input {
height: 42px; /* 调高输入框高度 */
height: 42px;
/* 调高输入框高度 */
width: 80%;
}
::v-deep .el-form-item__label {
height: 42px; /* 调高输入框高度 */
height: 42px;
/* 调高输入框高度 */
line-height: 42px;
}
.custom-form ::v-deep .el-input__wrapper,
.custom-form ::v-deep .el-date-editor.el-input,
.custom-form ::v-deep .el-input-number {
height: 42px; /* 调高输入框高度 */
height: 42px;
/* 调高输入框高度 */
width: 80%;
}
.custom-form ::v-deep .el-input__inner {
height: 100%;
}
.custom-form ::v-deep .el-select {
width: 100%;
}
@ -674,12 +727,14 @@ export default {
display: flex;
flex-direction: column;
}
.userBox {
width: 100%;
display: flex;
flex-direction: column;
flex: 1;
}
.table-actions {
width: 100%;
display: flex;
@ -746,4 +801,34 @@ export default {
height: 64px;
margin-bottom: 16px;
}
.fileRow {
height: 38px;
padding: 0 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-bottom: 10px;
img {
height: 18px;
}
.del {
font-size: 16px;
cursor: pointer;
}
}
.fileItem {
gap: 10px;
cursor: pointer;
}
.fileBox {
max-height: 136px;
overflow: auto;
padding-right: 10px;
max-width: 90%;
}
</style>

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: "/project/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,11 +232,13 @@ export default {
}
.selectBox {
width: 200px;
margin-left: 35px;
margin-left: 15px;
}
.selectBox span {
width: 120px;
}
.content{
::v-deep .el-table {
height: 100% !important;
}
@ -286,7 +291,7 @@ export default {
}
/* 调整合计行的样式 */
::v-deep .el-table__footer td {
background-color: #c0c4cc !important;
background-color: #e0e1e3 !important;
font-weight: bold;
color: #606266;
@ -312,4 +317,9 @@ 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,510 @@
<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 class-name="editCell" label="员工自评" prop="score"
v-if="examineTask.templateId && table[0].reviewCategory == '发展与协作' && examineTask.templateType != 0"
min-width="220">
<template slot-scope="scope">
<div>
<el-input type="textarea" :autosize="{ minRows: 4 }" placeholder="0/300" v-model="scope.row.remark"
readonly maxlength="300" show-word-limit>
</el-input>
</div>
</template>
</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"
v-if="table[0].reviewCategory != '发展与协作'">
<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"
v-if="(examineTask.templateId && table[0].reviewCategory == '发展与协作') || !examineTask.templateId">
<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 v-if="examineTask.templateType != 0 && table[0].reviewCategory != '发展与协作'&&examineTask.templateType" style="margin-top: 10px;">
<div class="userBox">评价</div>
<div>
<el-input type="textarea" :autosize="{ minRows: 4 }" placeholder="0/300" v-model="table[0].remarkCate"
readonly maxlength="300" show-word-limit>
</el-input>
</div>
</div>
</div>
<div v-if="(examineTask.templateType && examineTask.templateType == 0)||!examineTask.templateType">
<div class="userBox">总体评价</div>
<div>
<el-input type="textarea" :autosize="{ minRows: 4 }" placeholder="0/300" v-model="selfJudgeContent"
:readonly="true" maxlength="300" show-word-limit>
</el-input>
</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: "",
selfJudgeContent: "",
score: "",
activeName: "first",
manageUserName: "",
examineTask: {},
examineRemark: {}
};
},
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) => {
this.examineRemark = res.data.remark
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).map((ele, index) => {
ele[0].remarkCate = this.examineRemark.find((item)=>item.reviewCategory==ele[0].reviewCategory)?.remark||''
return ele
});;
this.judgeContent = res.data.examineUser.judgeContent;
this.selfJudgeContent = res.data.examineUser.judgeContent;
this.manageUserName = res.data.examineUser.manageUserName;
this.examineTask = res.data.examineTask
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,570 @@
<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 class-name="editCell" label="员工自评" prop="score"
v-if="!isNormal && examineTask.templateId && table[0].reviewCategory == '发展与协作' && examineTask.templateType != 0"
min-width="320">
<template slot-scope="scope">
<div>
<el-input type="textarea" :autosize="{ minRows: 4 }" placeholder="0/300" v-model="scope.row.remark"
readonly maxlength="300" show-word-limit>
</el-input>
</div>
</template>
</el-table-column>
<el-table-column label="评分" prop="score"
:minWidth="400" class-name="sorceTableCell"
v-if="(isNormal && table[0].reviewCategory != '发展与协作') || !isNormal">
<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 && examineTask.templateId && table[0].reviewCategory == '发展与协作') || (isNormal && !examineTask.templateId)"
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 v-if="examineTask.templateType != 0 && isNormal && table[0].reviewCategory != ''"
style="margin-top: 10px;">
<div class="userBox">评价</div>
<div>
<el-input type="textarea" :autosize="{ minRows: 4 }" placeholder="0/300" v-model="table[0].remarkCate"
:readonly="!isEdit" maxlength="300" show-word-limit>
</el-input>
</div>
</div>
</div>
<div>
<div v-if="(isNormal && examineTask.templateType == 0) || (!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:300
// width: '25%',
},
],
//
tableData: [
//
],
//
examineTaskId: "",
examineId: "",
reviewType: "",
reviewType: "",
//
saveData: {
examineId: "",
examineStatus: "",
examineStatusSelf: "",
manageScore: "",
taskId: "",
judgeContent: "",
examineDetailList: [],
},
examineUser: {},
examineTask: {},
examineRemark: {},
};
},
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 = {};
this.examineRemark = res.data.remark;
res.data.examineConfigDetailVoList.forEach((ele, index) => {
if (!objData[ele.reviewCategory]) objData[ele.reviewCategory] = [];
objData[ele.reviewCategory].push(ele);
});
this.tableData = Object.values(objData).map((ele, index) => {
ele[0].remarkCate = this.examineRemark.find((item)=>item.reviewCategory==ele[0].reviewCategory)?.remark||''
return ele
});
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 (this.examineTask.templateType == 0) {
this.saveData.judgeContent = res.data.examineUser.selfJudgeContent
}
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,
reviewCategory: ele.reviewCategory,
}));
if (this.examineTask.templateType != 0 && this.isNormal) {
this.saveData.examineRemarkList = this.tableData.filter((ele) => ele[0].reviewCategory != '发展与协作').map((ele) => ({ reviewCategory: ele[0].reviewCategory, remark: ele[0].remarkCate }))
} else {
this.saveData.examineRemarkList = []
}
this.saveData.taskId = this.examineTaskId;
this.saveData.examineId = this.examineId;
},
saveScoreCheck(status) {
//
this.saveDataSet(status);
//
if (status == 0) {
if (this.isNormal && this.examineTask.templateType == 0) {
this.saveData.selfJudgeContent = this.saveData.judgeContent
this.saveData.judgeContent = ''
}
this.saveScore();
return;
}
//
if (this.saveData.examineDetailList.filter((ele) => !ele.score && ele.reviewCategory != '发展与协作' || (!ele.score && !this.isNormal)).length) {
this.$alert("存在未评分绩效项,请完善后再试", "提交失败", {
confirmButtonText: "确定",
type: "warning",
});
} else if (
this.saveData.examineDetailList.filter((ele) => !ele.remark && ele.reviewCategory == '发展与协作').length &&
this.isNormal &&
status
) {
this.$alert("发展与协作下的自评总结为必填,请完善后再试", "提交失败", {
confirmButtonText: "确定",
type: "warning",
});
} else if (
this.saveData.examineDetailList.filter((ele) => ele.remark.length < 100 && ele.reviewCategory == '发展与协作').length &&
this.isNormal &&
status
) {
this.$alert("发展与协作下的自评总结最少100个字符请完善后再试", "提交失败", {
confirmButtonText: "确定",
type: "warning",
});
} else if (this.saveData.judgeContent.length > 300 && !this.isNormal && this.examineTask.templateType == '0') {
this.$message({
message: "总体评价限制300个字符",
type: "warning",
});
} else if (!this.saveData.judgeContent.length && !this.isNormal) {
this.$message({
message: "总体评价为必填",
type: "warning",
});
} else if (!this.saveData.judgeContent.length && this.isNormal && this.examineTask.templateType == 0) {
this.$message({
message: "个人总体评价为必填",
type: "warning",
});
} else if (this.saveData.examineRemarkList.filter((ele) => !ele.remark).length && this.isNormal && this.examineTask.templateType != 0 && this.isNormal) {
this.$message({
message: "存在未填写大类评价,请完善后再试",
type: "warning",
});
} else if (this.saveData.examineRemarkList.filter((ele) => ele.remark.length < 100).length && this.isNormal && this.examineTask.templateType != 0 && this.isNormal) {
this.$message({
message: "大类评价最少100个字符请完善后再试",
type: "warning",
});
} else {
if (status) {
this.$confirm(
"提交后将无法修改,该操作不可逆,请确认后再试",
"确认提交绩效评分",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
if (this.isNormal && this.examineTask.templateType == 0) {
this.saveData.selfJudgeContent = this.saveData.judgeContent
this.saveData.judgeContent = ''
}
this.saveScore();
});
} else {
if (this.isNormal && this.examineTask.templateType == 0) {
this.saveData.selfJudgeContent = this.saveData.judgeContent
this.saveData.judgeContent = ''
}
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;
padding-right: 30px;
}
::v-deep .sorceTableCell.el-table__cell {
padding-left: 10% !important;
/* padding-right: 10px !important; */
}
::v-deep .el-table__body .el-table__cell {
padding: 20px 30px;
}
::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,493 @@
<template>
<div class="appraisal-manager ">
<div class="flex-row aic" style="gap: 20px;margin-bottom: 20px;">
<div>看板名称</div>
<div>
<el-input v-model="moduleName" class="filter-input" readonly>
</el-input>
</div>
<div>看板类型</div>
<el-select v-model="moduleType" placeholder="看板类型" clearable style="width: 200px" readonly disabled>
<el-option label="年度考核" value="0" />
<el-option label="季度考核" value="1" />
<el-option label="年度考核" value="2" />
</el-select>
</div>
<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"
@change="showAllScoure">
<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 v-show="leftType !== '组长评估绩效指标' && leftType !== '个人自评绩效指标'">
<div class="flex-row jcsb setHeader">
<div style="width: 15%; font-weight: 600">考核项</div>
<div style="width: 50%; font-weight: 600;text-align: center;">评分标准</div>
<div class="center" style="width: 25%; font-weight: 600">
权重占比
</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" class="flex-row jcsb aic contentRow">
<div style="width: 15%; font-weight: 500">
{{ item.reviewItem }}
</div>
<div class="center" style="width: 50%;padding: 0 20px;">
{{ item.remarks }}
</div>
<div class="center" style="width: 25%">
<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 :disabled="isDisabled" v-model="item.weight" :max="20"
@change="changeTotal"></el-slider>
</div>
</div>
</div>
<div v-show="leftType == '组长评估绩效指标' || leftType == '个人自评绩效指标'">
<div v-for="(ele, index) in
(scoreList.find((ele) => ele.title == leftType) || {})
.list || []
" :key="index">
<div style="margin: 10px;font-weight: 600;font-size: 18px;">{{ ele.title }}</div>
<div class="flex-row jcsb setHeader">
<div style="width: 15%; font-weight: 600">考核项</div>
<div style="width: 50%; font-weight: 600;text-align: center;">评分标准</div>
<div class="center" style="width: 25%; font-weight: 600">
权重占比
</div>
</div>
<div style="margin-bottom: 50px;">
<div v-for="(item, index) in (ele.rightArr || [])" class="flex-row jcsb aic contentRow">
<div style="width: 15%; font-weight: 600">
{{ item.reviewItem }}
</div>
<div class="center" style="width: 50%;padding: 0 20px;">
{{ item.remarks }}
</div>
<div class="center" style="width: 25%">
<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 v-for="item in item.weight == 0 ? 0 : item.weight - 1" :key="item"
class="stepBox" :style="{
left: item * 5 + 4 + '%',
}"></div>
<el-slider :disabled="isDisabled" v-model="item.weight" :max="20"
@change="changeTotal"></el-slider>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { taskApi } from "@/utils/api";
export default {
components: {
},
data() {
return {
moduleId: '',
moduleName: '',
moduleType: '',
scoreList: [],
letfValue: "",
selectLeftRow: '',
leftType: '',
isDisabled: true
};
},
methods: {
getDetail() {
taskApi.getTaskModelSet(this.moduleId).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
),
},
];
if (!arrList["2"]) {
this.scoreList.length = 2
}
this.dialogVisible2 = true;
});
},
selectLeft(row) {
this.leftType = ''
this.selectLeftRow = row.title + row.type;
},
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
);
});
});
},
showAllScoure(val) {
if (val)
this.leftType = val
else {
this.leftType = parseInt(this.selectLeftRow)==0?'组长评估绩效指标':'个人自评绩效指标'
}
}
},
mounted() {
this.moduleId = this.$route.query.id
this.moduleName = this.$route.query.moduleName
this.moduleType = this.$route.query.moduleType
this.getDetail()
},
};
</script>
<style lang="scss" scoped>
.appraisal-manager {
padding: 30px;
background-color: #fff;
height: calc(100vh - 100px);
/* 设置整体高度 */
}
.modal {
display: flex;
border-bottom: 1px solid #ccc;
border-top: 1px solid #ccc;
height: 100%;
}
.left {
width: 20%;
padding: 20px;
border-right: 1px solid #ccc;
height: 100%;
overflow: auto;
}
.right {
width: 80%;
padding: 20px 40px;
height: 100%;
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;
}
.contentRow {
border-bottom: 1px solid #ccc;
padding: 15px 0;
}
.setHeader {
background-color: #f8f8f9;
padding: 15px;
}
</style>

View File

@ -0,0 +1,492 @@
<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.templateName" placeholder="看板名称" style="width: 300px"></el-input>
</el-form-item>
<el-form-item label="创建人" class="form-item">
<el-input v-model="searchForm.createByName" 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.templateType" placeholder="状态" clearable style="width: 300px">
<el-option label="年度考核" value="0" />
<el-option label="季度考核" value="1" />
<el-option label="年度考核" value="2" />
</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="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 type="text" size="mini" @click="viewDetail(row)"></el-button>
<!-- <el-button @click="viewDetail(row)" type="text" size="mini">编辑</el-button> -->
<el-button type="text" size="mini" @click="delTask(row)"></el-button>
</div>
</template>
</CustomTable>
</div>
<SelectUser :dialogVisible="userSelectDialogVisible" :currentSelectedUser="currentSelectedUser"
:currentSelectedUserName="currentSelectedUserName" :highligt="true" ref="selectUserRef"
@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: {
templateName: "",
templateType: "",
createBy: "",
createByName: ''
},
columns: [
{ prop: "templateName", label: "看板名称" },
{
prop: "templateType", label: "考核类型", type: "status",
callback: (value) => {
if (value == 0) var type = "年度考核";
else if (value == 1) var type = "季度考核";
else if (value == 2) var type = "月度考核";
return type
},
},
{
prop: "createByName",
label: "创建人",
},
{
prop: "createTime",
label: "创建日期",
},
{
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: [],
modelList: [],
};
},
watch: {
dialogVisible1(newVal) {
if (newVal) {
this.$nextTick(() => {
this.$refs.selectUserRef?.$refs.customTableRef?.handleCurrentChange(
1
);
});
}
},
},
methods: {
onSearch() {
this.getTaskModelList();
},
onReset() {
Object.keys(this.searchForm).forEach((key) => {
this.searchForm[key] = "";
});
this.getTaskModelList();
},
getTaskModelList() {
taskApi
.getTaskModel({
...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.getTaskModelList();
},
handleCurrentChange(page) {
this.pageNum = page;
this.getTaskModelList();
},
openUserSelectDialog() {
this.userSelectDialogVisible = true;
this.$nextTick(() => {
this.$refs.selectUserRef?.$refs.customTableRef?.clearSelection();
this.currentSelectedUser = [this.searchForm.createBy];
this.currentSelectedUserName = [this.searchForm.createName];
});
},
handleUserConfirm(data) {
this.searchForm.createByName = data[0].nickName;
this.searchForm.createBy = data[0].userId;
},
handleUserClose() {
this.userSelectDialogVisible = false;
},
addTask() {
this.isEdit = false;
this.dialogVisible1 = true;
this.taskData = {
id: "",
taskName: "",
peopleNumberDetail: "",
userIdList: [],
endTime: "",
peopleNumber: 0,
};
},
viewDetail(row) {
this.$router.push({
path: `/workAppraisal/moduleDetail?id=${row.id}&moduleName=${row.templateName}&moduleType=${row.templateType}`
})
},
delTask(row) {
this.$confirm(
"是否确认删除该条看板",
"确认删除",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
taskApi.delTaskModule(row.id).then((res) => {
this.$message({
type: "success",
message: "删除成功!",
});
this.getTaskModelList();
});
});
},
},
mounted() {
this.getTaskModelList();
},
};
</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,876 @@
<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="templateId">
<div>
<el-radio-group v-model="taskData.templateType" @change="() => {
taskData.templateId = ''
}" :disabled="taskData.taskStatus == 2">
<el-radio label="0">年度看板</el-radio>
<el-radio label="1">季度看板</el-radio>
<el-radio label="2">月度看板</el-radio>
</el-radio-group>
<el-select v-model="taskData.templateId" placeholder="选择看板" clearable style="width: 300px"
:disabled="taskData.taskStatus == 2">
<el-option v-for="item in modelList.filter((ele) => ele.templateType == taskData.templateType)"
:key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</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 flex-row jcsb" style="font-weight: 600">
<div>评估维度</div>
<div>累计权重</div>
</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 setText" style="margin-bottom: 20px">
<div style="width: 50%; font-weight: 600;text-align: left;">指标</div>
<div class="center" style="width: 50%; font-weight: 600;text-align: left;padding-left: 5px;">
权重占比
</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;color: #333;">
{{ 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 style="font-weight: 500;">0</div>
<div v-show="item.weight != 20" style="font-weight: 500;">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 ? '7px' : '5px',
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,
templateType: '0',
templateId: '',
year: "",
taskStatus: 0
},
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" }],
templateId: [{ required: true, message: "请选择任务看板", trigger: "blur" }],
},
dialogVisible2: false,
letfValue: "",
scoreList: [],
selectRow: {},
modelList: [],
};
},
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,
templateType: '0',
templateId: '',
taskStatus: 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.templateId = row.templateId;
this.taskData.templateType = row.templateType;
this.taskData.taskStatus = row.taskStatus;
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
),
},
];
if (!arrList["2"]) {
this.scoreList.length = 2
}
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;
}
},
async getTaskModel() {
const res = await taskApi.getTaskModel({ pageNum: 1, pageSize: 1000 });
this.modelList = res.rows.map((ele) => ({
value: ele.id,
label: ele.templateName,
templateType: ele.templateType
}));
},
},
mounted() {
this.getTaskList();
this.getTaskModel();
},
};
</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 #E6E6E6;
// border-top: 1px solid #E6E6E6;
}
.left {
width: 40%;
padding:0;
// border-right: 1px solid #E6E6E6;
height: 450px;
overflow: auto;
box-shadow: 5px 0 20px 5px #eeeeee;
z-index: 100;
>div{
padding-left: 20px;
padding-right: 20px;
}
}
.right {
width: 60%;
// padding: 0 40px;
height: 450px;
overflow: auto;
div.center {
text-align: center;
position: relative;
}
>div{
padding-left: 3cap;
padding-right: 30px;
}
}
.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;
background-color: #FAFAFA;
text-align: right;
padding:15px 5px;
}
.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: transparent;
z-index: 100;
}
.totalBox {
width: 150px;
display: flex;
flex-direction: row;
font-size: 14px;
color: #333333;
}
</style>

View File

@ -0,0 +1,258 @@
<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,
timeout: isTimeOut(
data.day,
calendarList.find(
(ele) => ele.date == data.day + ' 00:00:00' && ele.state == -1
)
),
}"
>
{{ 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();
},
isTimeOut(data, hasDay) {
if (
hasDay &&
new Date(data).getTime() <
new Date(this.moment().format(`YYYY-MM-DD 00:00:00`)).getTime()
) {
return true;
}
},
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;
}
.timeout {
background: #f7c940;
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,659 @@
<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="200">
<template #default="scope">
<div>
<el-button type="text" @click="handleFile(scope.row)">
附件详情
</el-button>
<el-button type="text" @click="handleEdit(scope.row)" v-show="!disableTable">
{{ scope.row.loggerId && !scope.row.edit ? "编辑" : "确认" }}
</el-button>
<el-button type="text" @click="handleDelete(scope.row, scope.$index)" style="color: #666"
v-show="!disableTable">
{{ scope.row.loggerId && !scope.row.edit ? "删除" : "取消" }}
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<el-dialog title="附件" :visible.sync="dialogVisibleFile" width="780px" :close-on-click-modal="false">
<div v-loading="fileLoading">
<div v-show="!disableTable">
<el-upload class="upload-demo" ref="upload" :action="fileUpload" :show-file-list="false" :auto-upload="true"
:multiple="true" :before-upload="beforeUpload" :on-success="successUpload" :headers="{
Authorization: 'Bearer ' + token,
}" :data="{}">
<div class="flex-row aic" style="gap: 10px; margin-bottom: 10px">
<el-button slot="trigger" size="small" type="default"
style="width: 80px; color: #333; font-weight: 500">上传附件</el-button>
<div slot="tip" style="color: #999999; font-size: 12px">
单个附件限制100M
</div>
</div>
</el-upload>
</div>
<div style="height: 500px;overflow: auto;">
<el-table :data="fileList" style="width: 100%;height: 100%;" class="tableBox">
<el-table-column label="名称" prop="fileNewName">
<template #default="scope">
<div class="flex-row aic fileBox" style="gap: 10px;">
<img class="" :src="filePng" v-if="getFileType(scope.row.fileNewName) == 'file'"> </img>
<img class="" :src="imagePng" v-else-if="getFileType(scope.row.fileNewName) == 'image'"> </img>
<img class="" :src="zipPng" v-else-if="getFileType(scope.row.fileNewName) == 'zip'"> </img>
<div class="downFileBox" @click="downFile(scope.row.fileUrl)">
{{ scope.row.fileNewName }}
</div>
</div>
</template>
</el-table-column>
<el-table-column label="类型" prop="fileName">
<template #default="scope">
<div>
{{ scope.row.fileNewName.split(".")[1] }}
</div>
</template>
</el-table-column>
<el-table-column label="上传时间" prop="createTime">
<template #default="scope">
<div>
{{
scope.row.createTime || moment().format("YYYY-MM-DD HH:mm:ss")
}}
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<div>
<el-button type="text" @click="delFile(scope.row)" v-show="!disableTable">
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div slot="footer" class="dialog-footer" v-show="!disableTable">
<el-button @click="cancalFile"></el-button>
<el-button type="primary" @click="saveFile"></el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { demandApi, workLogApi, projectApi, systemApi } from "@/utils/api";
import { getToken } from "@/utils/auth";
import filePng from "@/assets/images/file.png";
import zipPng from "@/assets/images/zip.png";
import imagePng from "@/assets/images/image.png";
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: "",
dialogVisibleFile: false,
fileList: [],
checkRow: {},
fileUpload: systemApi.fileUpload,
token: getToken(),
fileLoading: false,
filePng,
zipPng,
imagePng,
delFileArr: []
};
},
methods: {
init() {
if (this.$route.query.userId) {
this.userId = this.$route.query.userId;
this.userName = this.$route.query.nickName;
this.projectId = this.$route.query.projectId;
}
//
},
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;
});
if (this.tableData.length == 0 && !this.disableTable) {
this.handleAdd();
}
});
},
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");
}
if (this.delFileArr.length) {
systemApi.delFile({ ids: this.delFileArr }).then((res) => {
this.fileList = []
this.delFileArr = []
});
}
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);
}
},
handleFile(row) {
this.dialogVisibleFile = true;
this.checkRow = row;
this.fileList = [...(row.fileList || []).map((ele) => ({ ...ele }))];
},
beforeUpload(file) {
this.fileLoading = true;
if (file.size > 1024 * 1024 * 100) {
this.fileLoading = false;
this.$message({
type: "warning",
message: "单个文件不能大于100M!",
});
return false;
}
},
successUpload(res, file, fileList) {
if (!fileList.filter((ele) => ele.percentage != 100).length) {
this.fileLoading = false;
}
if (res.code == 200) {
this.fileList.push({
fileName: res.originalFilename, //
filePath: res.filePath, //
fileNewName: res.newFileName, //
fileUrl: res.url,
});
} else {
this.fileLoading = false;
this.$message({
type: "error",
message: res.msg,
});
}
},
cancalFile() {
this.dialogVisibleFile = false;
this.fileList = [];
},
async saveFile() {
this.dialogVisibleFile = false;
this.checkRow.fileList = this.fileList;
if (this.checkRow.loggerId) {
await workLogApi.editLog(this.checkRow);
if (this.delFileArr.length) {
systemApi.delFileBatch(this.delFileArr.join(',')).then((res) => {
this.fileList = []
this.delFileArr = []
this.getLogList();
});
} else {
this.getLogList();
}
}
this.$message({
type: "success",
message: "保存成功!",
});
},
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,
fileList: [],
};
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;
});
},
downFile(url) {
window.open(url);
},
delFile(row) {
if (row.id) {
this.$confirm("此操作将永久删除文件, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
this.delFileArr.push(row.id)
this.fileList = this.fileList.filter(
(ele) => ele.fileNewName != row.fileNewName
);
this.$message({
type: 'success',
message: "删除成功!"
})
});
} else {
this.fileList = this.fileList.filter(
(ele) => ele.fileNewName != row.fileNewName
);
this.$message({
type: 'success',
message: "删除成功!"
})
}
},
getFileType(name) {
console.log(name);
var data = {
jpg: "image",
jpeg: "image",
png: "image",
gif: "image",
bmp: "image",
tiff: "image",
svg: "image",
pdf: "file",
doc: "file",
docx: "file",
xls: "file",
xlsx: "file",
txt: "file",
ppt: "file",
zip: "zip",
rar: "zip",
tar: "zip",
targz: "zip",
};
return data[name.split(".")[1]] || "file";
},
},
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;
}
.downFileBox {
color: #1686d8;
cursor: pointer;
}
.fileBox {
img {
height: 18px;
}
}
.dialog-footer {
text-align: center;
margin-top: 20px;
}
</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";
this.selectedDate = new Date(this.projectInfo.startDate); //
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,9 +308,15 @@ export default {
projectId: this.projectInfo.projectId,
loggerDate: this.logForm.loggerDate,
});
this.logForm.workTime = res.data.workTime;
this.logForm.workContent = res.data.workContent;
this.logForm.loggerId = res.data.loggerId;
if (res.data) {
this.logForm.workTime = res.data.workTime;
this.logForm.workContent = res.data.workContent;
this.logForm.loggerId = res.data.loggerId;
} else {
this.logForm.workTime = "";
this.logForm.workContent = "";
this.logForm.loggerId = "";
}
} else {
this.logForm.workTime = "";
this.logForm.workContent = "";
@ -300,9 +325,33 @@ export default {
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>