静态部分完成70%
parent
63baa08e8c
commit
60cb68401f
Binary file not shown.
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 5.6 KiB |
|
@ -1,18 +1,5 @@
|
||||||
@use 'sass:math';
|
@use 'sass:math';
|
||||||
|
|
||||||
// @font-face {
|
|
||||||
// font-family: AliPuHui;
|
|
||||||
// /*这里是说明调用来的字体名字*/
|
|
||||||
// src: url('../font/Alibaba-PuHuiTi/Alibaba-PuHuiTi-Regular.ttf');
|
|
||||||
// /*这里是字体文件路径*/
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @font-face {
|
|
||||||
// font-family: AliPuHuiBold;
|
|
||||||
// /*这里是说明调用来的字体名字*/
|
|
||||||
// src: url('../font/Alibaba-PuHuiTi/Alibaba-PuHuiTi-Bold.ttf');
|
|
||||||
// /*这里是字体文件路径*/
|
|
||||||
// }
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -117,76 +104,11 @@ body {
|
||||||
.aifs {
|
.aifs {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
// 按钮内边距
|
|
||||||
.pd8-15 {
|
|
||||||
padding: 0.08rem 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd8-23 {
|
|
||||||
padding: 0.08rem 0.23rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w300-h30 {
|
|
||||||
width: 70%;
|
|
||||||
height: 32px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
:deep(.w300-h30) {
|
|
||||||
width: 70%;
|
|
||||||
height: 32px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
.h100 {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
.h200 {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.h300 {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w100-h30 {
|
|
||||||
width: 1rem;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
.w200-h30 {
|
|
||||||
width: 70% !important;
|
|
||||||
height: 30px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
:deep(.w200-h30) {
|
|
||||||
width: 70% !important;
|
|
||||||
height: 30px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
.w200-h20 {
|
|
||||||
width: 2rem;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.w140-h30 {
|
|
||||||
width: 1.4rem;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
.w150-h30 {
|
|
||||||
width: 150px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w90-h40 {
|
|
||||||
width: 90px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w75-h30 {
|
|
||||||
width: 75px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
//外边距
|
//外边距
|
||||||
.ml10 {
|
.ml10 {
|
||||||
|
@ -256,18 +178,8 @@ body {
|
||||||
.no_wrap {
|
.no_wrap {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.no-data {
|
|
||||||
// color: #000;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
// .theme-color {
|
|
||||||
// color: $lightThemColor;
|
|
||||||
// }
|
|
||||||
/* 竖向弹性盒子 */
|
/* 竖向弹性盒子 */
|
||||||
.flex-col {
|
.flex-col {
|
||||||
@include flex-row-vc;
|
@include flex-row-vc;
|
||||||
|
@ -351,14 +263,6 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin scroll-none {
|
|
||||||
scrollbar-width: none;
|
|
||||||
/* firefox */
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
/* IE 10+ */
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dialog弹出给body加了该类名,导致页面右侧偏移
|
// dialog弹出给body加了该类名,导致页面右侧偏移
|
||||||
.el-popup-parent--hidden {
|
.el-popup-parent--hidden {
|
||||||
|
@ -412,67 +316,8 @@ body {
|
||||||
// color: #ffffff;
|
// color: #ffffff;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
// 公共dialog
|
|
||||||
.center-dialog {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
// overflow: hidden;
|
|
||||||
:deep(.el-dialog) {
|
|
||||||
margin: 0 !important;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
.el-dialog__header {
|
|
||||||
border-bottom: 1px solid #d8d8d8;
|
|
||||||
}
|
|
||||||
.el-dialog__body {
|
|
||||||
// overflow: hidden;
|
|
||||||
overflow: auto;
|
|
||||||
// height: 70vh; //最大高度为视口高度的90%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.common-dialog {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
// overflow: hidden;
|
|
||||||
:deep(.el-dialog) {
|
|
||||||
margin: 0 !important;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
.el-dialog__header {
|
|
||||||
border-bottom: 1px solid #d8d8d8;
|
|
||||||
height: 38px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 38px;
|
|
||||||
padding: 0;
|
|
||||||
padding-left: 16px;
|
|
||||||
text-align: left;
|
|
||||||
.el-dialog__title {
|
|
||||||
color: #333333;
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.el-dialog__body {
|
|
||||||
// overflow: hidden;
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 90vh; //最大高度为视口高度的90%
|
|
||||||
// height: auto;
|
|
||||||
}
|
|
||||||
.el-dialog__headerbtn {
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
.table-container {
|
|
||||||
min-height: 30vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.el-form-item__content) {
|
:deep(.el-form-item__content) {
|
||||||
align-items: flex-start !important;
|
align-items: flex-start !important;
|
||||||
}
|
}
|
||||||
|
@ -491,35 +336,8 @@ body {
|
||||||
.pr15 {
|
.pr15 {
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
.image-slot-error {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #f7f5f5;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.no-data {
|
|
||||||
// color: #000;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
// 蓝色文字下划线可点击
|
|
||||||
.text_underline {
|
|
||||||
font-family: MicrosoftYaHei;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #5584ff;
|
|
||||||
line-height: 22px;
|
|
||||||
text-align: center;
|
|
||||||
font-style: normal;
|
|
||||||
text-decoration-line: underline;
|
|
||||||
-moz-text-decoration-line: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
:deep(.el-button--primary:not(.is-text)) {
|
:deep(.el-button--primary:not(.is-text)) {
|
||||||
background-color: #5584ff !important ;
|
background-color: #5584ff !important ;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
|
@ -531,18 +349,7 @@ body {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
.optionTitle {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
height: 40px;
|
|
||||||
line-height: 40px;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
// :deep(.el-radio-button__inner){
|
|
||||||
// width: 120px;
|
|
||||||
// }
|
|
||||||
// :deep(*:not(.el-dialog__body) .is-active .el-radio-button__inner){
|
|
||||||
// background: #5584FF;
|
|
||||||
// }
|
|
||||||
:deep(.el-dialog--center .el-dialog__body) {
|
:deep(.el-dialog--center .el-dialog__body) {
|
||||||
padding: 32px !important;
|
padding: 32px !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
|
||||||
|
/* 适用于所有页面的通用样式 */
|
||||||
|
:root {
|
||||||
|
--primary-color: #1890ff;
|
||||||
|
--secondary-color: #f0f2f5;
|
||||||
|
--text-color: #333333;
|
||||||
|
--border-color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-container {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
padding: 24px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格样式优化 */
|
||||||
|
:deep(.el-table) {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header th) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body td) {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式优化 */
|
||||||
|
.el-button {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式优化 */
|
||||||
|
.el-form-item__label {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper,
|
||||||
|
.el-select .el-input__wrapper {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对话框样式优化 */
|
||||||
|
:deep(.el-dialog) {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__header) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__title) {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__footer) {
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
|
@ -0,0 +1,254 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog title="选择人员" :modelValue="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 />
|
||||||
|
</div>
|
||||||
|
<div class="user-list">
|
||||||
|
<CustomTable
|
||||||
|
ref="customTableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:tableData="userData"
|
||||||
|
:total="total"
|
||||||
|
:show-selection="true"
|
||||||
|
:show-index="true"
|
||||||
|
:table-height="tableHeight"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
>
|
||||||
|
<template #operation="{ 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>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</CustomTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleConfirm">确认</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue'
|
||||||
|
import CustomTable from '@/components/table.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
multiSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
dialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
currentSelectedUser: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:dialogVisible', 'close', 'confirm'])
|
||||||
|
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const total = ref(100)
|
||||||
|
const selectedUsers = ref([])
|
||||||
|
const currentDepartment = ref('')
|
||||||
|
const tableHeight = ref(350) // 设置一个合适的高度
|
||||||
|
|
||||||
|
// 组织树假数据
|
||||||
|
const treeData = [
|
||||||
|
{
|
||||||
|
label: '运维团队',
|
||||||
|
children: [{ label: '外场人员' }, { label: '内场人员' }, { label: '外场临时人员' }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
children: 'children',
|
||||||
|
label: 'label',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{ prop: 'name', label: '姓名' },
|
||||||
|
{ prop: 'department', label: '部门' },
|
||||||
|
{ prop: 'role', label: '角色' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 用户列表假数据
|
||||||
|
const allUserData = reactive(
|
||||||
|
Array(100)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, index) => ({
|
||||||
|
id: index + 1,
|
||||||
|
name: `张三${index + 1}`,
|
||||||
|
department: ['外场人员', '内场人员', '外场临时人员'][index % 3],
|
||||||
|
role: '客户经理',
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
const userData = computed(() => {
|
||||||
|
let filteredData = allUserData
|
||||||
|
if (currentDepartment.value) {
|
||||||
|
filteredData = allUserData.filter(user => user.department === currentDepartment.value)
|
||||||
|
}
|
||||||
|
const start = (currentPage.value - 1) * pageSize.value
|
||||||
|
const end = start + pageSize.value
|
||||||
|
return filteredData.slice(start, end)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleNodeClick = data => {
|
||||||
|
currentDepartment.value = data.label
|
||||||
|
currentPage.value = 1
|
||||||
|
fetchUserList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = val => {
|
||||||
|
currentPage.value = val
|
||||||
|
fetchUserList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = val => {
|
||||||
|
pageSize.value = val
|
||||||
|
fetchUserList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('update:dialogVisible', false)
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
emit('confirm', selectedUsers.value)
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
const customTableRef = ref(null)
|
||||||
|
const isInternalChange = ref(false)
|
||||||
|
|
||||||
|
const handleSelectionChange = val => {
|
||||||
|
if (isInternalChange.value) return
|
||||||
|
|
||||||
|
if (!props.multiSelect) {
|
||||||
|
isInternalChange.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
if (val.length > 0) {
|
||||||
|
const lastSelected = val[val.length - 1]
|
||||||
|
selectedUsers.value = [lastSelected]
|
||||||
|
customTableRef.value?.clearSelection()
|
||||||
|
customTableRef.value?.toggleRowSelection(lastSelected, true)
|
||||||
|
} else {
|
||||||
|
selectedUsers.value = []
|
||||||
|
}
|
||||||
|
isInternalChange.value = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
selectedUsers.value = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 currentSelectedUser 变化
|
||||||
|
watch(
|
||||||
|
() => props.currentSelectedUser,
|
||||||
|
newVal => {
|
||||||
|
isInternalChange.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
selectedUsers.value = newVal
|
||||||
|
if (customTableRef.value) {
|
||||||
|
customTableRef.value.clearSelection()
|
||||||
|
newVal.forEach(user => {
|
||||||
|
const row = allUserData.find(item => item.id === user.id)
|
||||||
|
if (row) {
|
||||||
|
customTableRef.value.toggleRowSelection(row, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isInternalChange.value = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// 在组件挂载时设置初始选中状态
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (customTableRef.value && props.currentSelectedUser.length > 0) {
|
||||||
|
customTableRef.value.clearSelection()
|
||||||
|
props.currentSelectedUser.forEach(user => {
|
||||||
|
const row = allUserData.find(item => item.id === user.id)
|
||||||
|
if (row) {
|
||||||
|
customTableRef.value.toggleRowSelection(row, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchUserList = async () => {
|
||||||
|
// 在实际应用中,这里应该调用后端 API
|
||||||
|
// const response = await api.getUserList({
|
||||||
|
// page: currentPage.value,
|
||||||
|
// pageSize: pageSize.value,
|
||||||
|
// department: currentDepartment.value
|
||||||
|
// })
|
||||||
|
// allUserData = response.data.list
|
||||||
|
// total.value = response.data.total
|
||||||
|
|
||||||
|
// 使用假数据模拟
|
||||||
|
total.value = allUserData.filter(
|
||||||
|
user => !currentDepartment.value || user.department === currentDepartment.value,
|
||||||
|
).length
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可以在组件挂载时调用这个方法获取初始数据
|
||||||
|
// onMounted(() => {
|
||||||
|
// fetchUserList()
|
||||||
|
// })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.select-user-container {
|
||||||
|
display: flex;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-tree {
|
||||||
|
width: 200px;
|
||||||
|
border-right: 1px solid #dcdfe6;
|
||||||
|
padding-right: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-list {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__footer) {
|
||||||
|
padding: 20px 20px 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer .el-button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer .el-button:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,39 +1,42 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="custom-table" ref="tableContainer">
|
<div class="custom-table" ref="tableContainer">
|
||||||
<el-table
|
<el-table
|
||||||
|
ref="elTableRef"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:height="tableHeight"
|
:max-height="computedTableHeight"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
|
:border="border"
|
||||||
>
|
>
|
||||||
<el-table-column
|
<el-table-column v-if="showSelection" type="selection" width="55" />
|
||||||
v-if="showSelection"
|
<el-table-column v-if="showIndex" type="index" width="50" label="序号" />
|
||||||
type="selection"
|
|
||||||
width="55"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
v-if="showIndex"
|
|
||||||
type="index"
|
|
||||||
width="50"
|
|
||||||
label="序号"
|
|
||||||
/>
|
|
||||||
<template v-for="(column, index) in columns" :key="index">
|
<template v-for="(column, index) in columns" :key="index">
|
||||||
<el-table-column
|
<el-table-column v-bind="column" :prop="column.prop" :label="column.label">
|
||||||
v-bind="column"
|
|
||||||
:prop="column.prop"
|
|
||||||
:label="column.label"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<slot :name="column.prop" :row="scope.row">
|
<slot :name="column.prop" :row="scope.row">
|
||||||
|
<template v-if="column.type === 'multiButton'">
|
||||||
|
<div class="button-group">
|
||||||
|
<el-button
|
||||||
|
v-for="(userName, userIndex) in scope.row[column.prop]"
|
||||||
|
:key="userIndex"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
@click="handleButtonClick(userName, column.prop)"
|
||||||
|
>
|
||||||
|
{{ userName }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
{{ scope.row[column.prop] }}
|
{{ scope.row[column.prop] }}
|
||||||
|
</template>
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</template>
|
</template>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="pagination-container">
|
<div class="pagination-container" ref="paginationContainer" v-if="showPagination">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-if="showPagination"
|
|
||||||
:current-page="currentPage"
|
:current-page="currentPage"
|
||||||
:page-size="pageSize"
|
:page-size="pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
|
@ -44,101 +47,117 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import useTableResize from '@/hooks/useTableResize'
|
import useTableResize from '@/hooks/useTableResize'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
columns: {
|
columns: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
tableData: {
|
tableData: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
showSelection: {
|
showSelection: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
showIndex: {
|
showIndex: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
showPagination: {
|
showPagination: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true,
|
||||||
},
|
},
|
||||||
total: {
|
total: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0,
|
||||||
},
|
},
|
||||||
pageSizes: {
|
pageSizes: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [10, 20, 30, 50]
|
default: () => [10, 20, 30, 50],
|
||||||
},
|
},
|
||||||
tableHeight: {
|
maxHeight: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 400
|
default: null,
|
||||||
}
|
},
|
||||||
})
|
offsetHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['selection-change', 'size-change', 'current-change'])
|
const emit = defineEmits(['selection-change', 'size-change', 'current-change', 'button-click'])
|
||||||
|
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
const tableContainer = ref(null)
|
const tableContainer = ref(null)
|
||||||
const tableHeight = computed(() => props.tableHeight)
|
const paginationContainer = ref(null)
|
||||||
|
const computedTableHeight = ref(null)
|
||||||
|
const elTableRef = ref(null)
|
||||||
|
|
||||||
const { w: tableWidth } = useTableResize()
|
const handleSelectionChange = selection => {
|
||||||
|
|
||||||
const handleSelectionChange = (selection) => {
|
|
||||||
emit('selection-change', selection)
|
emit('selection-change', selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSizeChange = (val) => {
|
const handleSizeChange = val => {
|
||||||
pageSize.value = val
|
pageSize.value = val
|
||||||
emit('size-change', val)
|
emit('size-change', val)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCurrentChange = (val) => {
|
const handleCurrentChange = val => {
|
||||||
currentPage.value = val
|
currentPage.value = val
|
||||||
emit('current-change', val)
|
emit('current-change', val)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTableHeight = () => {
|
const updateTableHeight = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (tableContainer.value) {
|
if (tableContainer.value && paginationContainer.value) {
|
||||||
const containerRect = tableContainer.value.getBoundingClientRect()
|
const parentElement = tableContainer.value.parentElement
|
||||||
const windowHeight = window.innerHeight
|
const parentHeight = parentElement.clientHeight
|
||||||
const containerTop = containerRect.top
|
const tableTop = tableContainer.value.getBoundingClientRect().top - parentElement.getBoundingClientRect().top
|
||||||
const paginationHeight = props.showPagination ? 32 : 0 // 分页器的大概高度
|
const paginationHeight = props.showPagination ? paginationContainer.value.offsetHeight + 30 : 0 // 加上 margin-top
|
||||||
tableHeight.value = windowHeight - containerTop - paginationHeight
|
computedTableHeight.value = parentHeight - tableTop - paginationHeight
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
// 暴露 el-table 的方法
|
||||||
|
defineExpose({
|
||||||
|
clearSelection: () => elTableRef.value?.clearSelection(),
|
||||||
|
toggleRowSelection: (row, selected) => elTableRef.value?.toggleRowSelection(row, selected),
|
||||||
|
setCurrentRow: row => elTableRef.value?.setCurrentRow(row),
|
||||||
|
// 可以根据需要暴露更多的方法
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
updateTableHeight()
|
updateTableHeight()
|
||||||
window.addEventListener('resize', updateTableHeight)
|
window.addEventListener('resize', updateTableHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', updateTableHeight)
|
window.removeEventListener('resize', updateTableHeight)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.custom-table {
|
.custom-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.pagination-container {
|
.pagination-container {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
<RouteTab></RouteTab>
|
<!-- <RouteTab></RouteTab> -->
|
||||||
<el-container>
|
<el-container>
|
||||||
<el-aside :style="{ width: isCollapse ? '64px' : '200px' }"><Aside></Aside></el-aside>
|
<el-aside :style="{ width: isCollapse ? '64px' : '200px' }"><Aside></Aside></el-aside>
|
||||||
<!-- 主体部分 -->
|
<!-- 主体部分 -->
|
||||||
|
@ -47,7 +47,7 @@ import editPassWordCom from './components/editPassWordCom.vue'
|
||||||
import { useMenu } from '@/layout/hook/hook.aside.js'
|
import { useMenu } from '@/layout/hook/hook.aside.js'
|
||||||
import Aside from './components/Aside.vue'
|
import Aside from './components/Aside.vue'
|
||||||
import MainContent from './components/MainContent.vue'
|
import MainContent from './components/MainContent.vue'
|
||||||
import RouteTab from './components/routeTab.vue'
|
// import RouteTab from './components/routeTab.vue'
|
||||||
import { loginApi } from '@/utils/api'
|
import { loginApi } from '@/utils/api'
|
||||||
import { removeToken } from '@/utils/auth'
|
import { removeToken } from '@/utils/auth'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
|
@ -15,6 +15,7 @@ window.$api = $api
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
// 重置css
|
// 重置css
|
||||||
import '@/assets/styles/reset.scss'
|
import '@/assets/styles/reset.scss'
|
||||||
|
import '@/assets/styles/pagecss.scss'
|
||||||
// 使用svg图标组件
|
// 使用svg图标组件
|
||||||
import 'virtual:svg-icons-register'
|
import 'virtual:svg-icons-register'
|
||||||
// 防止Xss攻击的v-html
|
// 防止Xss攻击的v-html
|
||||||
|
|
|
@ -5,7 +5,18 @@
|
||||||
* @LastEditTime: 2023-07-07 16:09:00
|
* @LastEditTime: 2023-07-07 16:09:00
|
||||||
* @Description :路由配置文件 routes静态路由 asyncRoutes异步路由
|
* @Description :路由配置文件 routes静态路由 asyncRoutes异步路由
|
||||||
*/
|
*/
|
||||||
import RouterView from './RouterView.vue'
|
import {
|
||||||
|
HomeFilled,
|
||||||
|
Briefcase,
|
||||||
|
User,
|
||||||
|
Calendar,
|
||||||
|
Document,
|
||||||
|
Setting,
|
||||||
|
PieChart,
|
||||||
|
List,
|
||||||
|
Tickets,
|
||||||
|
Monitor
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
import Login from '@/views/Login/Login.vue'
|
import Login from '@/views/Login/Login.vue'
|
||||||
import layout from '@/layout/Layout.vue'
|
import layout from '@/layout/Layout.vue'
|
||||||
import { shallowRef } from 'vue'
|
import { shallowRef } from 'vue'
|
||||||
|
@ -33,16 +44,26 @@ export const asyncRoutes = [
|
||||||
path: '/project',
|
path: '/project',
|
||||||
name: 'project',
|
name: 'project',
|
||||||
authKey: 'Workorder',
|
authKey: 'Workorder',
|
||||||
redirect: '/project/detail',
|
redirect: '/project/list',
|
||||||
meta: { title: '项目管理', imgSrc: 'slider/system' },
|
meta: { title: '项目管理', imgSrc: 'slider/system' },
|
||||||
component: shallowRef(layout),
|
component: shallowRef(layout),
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: '/project/list',
|
||||||
|
name: 'projectList',
|
||||||
|
authKey: 'Workorder',
|
||||||
|
meta: {
|
||||||
|
title: '项目列表',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/project/list.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/project/detail',
|
path: '/project/detail',
|
||||||
name: 'userWork',
|
name: 'projectDetail',
|
||||||
authKey: 'Workorder',
|
authKey: 'Workorder',
|
||||||
meta: {
|
meta: {
|
||||||
title: '项目详情',
|
title: '项目详情',
|
||||||
|
menuHide: true, // 添加这个属性来隐藏菜单项
|
||||||
},
|
},
|
||||||
component: () => import('@/views/project/detail.vue'),
|
component: () => import('@/views/project/detail.vue'),
|
||||||
},
|
},
|
||||||
|
@ -75,4 +96,59 @@ export const asyncRoutes = [
|
||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/worklog',
|
||||||
|
name: 'worklog',
|
||||||
|
authKey: 'Workorder',
|
||||||
|
meta: { title: '工作日志', imgSrc: 'slider/worklog' }, // 您可能需要为工作日志添加一个图标
|
||||||
|
component: shallowRef(layout),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/worklog/list',
|
||||||
|
name: 'worklogList',
|
||||||
|
authKey: 'Workorder',
|
||||||
|
meta: {
|
||||||
|
title: '日志列表',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/workLog/list.vue'),
|
||||||
|
},
|
||||||
|
// 如果需要其他工作日志相关的子路由,可以在这里添加
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projectBank',
|
||||||
|
name: 'projectBank',
|
||||||
|
authKey: 'Workorder',
|
||||||
|
meta: { title: '项目看板', imgSrc: 'slider/worklog' }, // 您可能需要为工作日志添加一个图标
|
||||||
|
component: shallowRef(layout),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/projectBank/projectProgress',
|
||||||
|
name: 'projectProgress',
|
||||||
|
authKey: 'Workorder',
|
||||||
|
meta: {
|
||||||
|
title: '项目执行表',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/projectBank/projectProgress.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projectBank/userProject',
|
||||||
|
name: 'userProject',
|
||||||
|
authKey: 'Workorder',
|
||||||
|
meta: {
|
||||||
|
title: '人员项目表',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/projectBank/userProject.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projectBank/projectUser',
|
||||||
|
name: 'projectUser',
|
||||||
|
authKey: 'Workorder',
|
||||||
|
meta: {
|
||||||
|
title: '项目人员表',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/projectBank/projectUser.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -246,7 +246,7 @@ const codeShadow = ref(false)
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
box-shadow: 0px 10px 20px 0px #2860b5;
|
box-shadow: 0px 10px 20px 0px #2860b5;
|
||||||
background: #3e8bff;
|
background: #fff;
|
||||||
border-bottom-left-radius: 40px;
|
border-bottom-left-radius: 40px;
|
||||||
background: url('@/assets/imgs/编组 3.png') no-repeat;
|
background: url('@/assets/imgs/编组 3.png') no-repeat;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
|
|
|
@ -1,34 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="project-management">
|
<div class="project-management">
|
||||||
<el-form :model="formData" :disabled="!isEditing" label-width="120px">
|
<el-form :model="formData" :rules="rules" label-width="120px" class="custom-form">
|
||||||
<el-row>
|
<el-row :gutter="24">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="项目名称">
|
<el-form-item label="项目名称">
|
||||||
<el-select v-model="formData.projectName" placeholder="请选择项目名称" class="full-width">
|
<el-select v-model="formData.projectName" placeholder="请选择项目名称" class="full-width longInput">
|
||||||
<el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
|
<el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
<el-col :span="12">
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="项目编号">
|
<el-form-item label="项目编号">
|
||||||
<el-input v-model="formData.projectCode" class="full-width" />
|
<el-input v-model="formData.projectCode" class="full-width" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="12">
|
||||||
<el-form-item label="项目负责人">
|
<el-form-item label="项目负责人">
|
||||||
<el-input v-model="formData.projectManager" class="full-width" />
|
<el-input
|
||||||
</el-form-item>
|
v-model="formData.projectManager"
|
||||||
</el-col>
|
placeholder="选择项目负责人"
|
||||||
<el-col :span="8">
|
readonly
|
||||||
<el-form-item label="预算人天数">
|
@click="openProjectManagerSelect"
|
||||||
<el-input-number v-model="formData.budgetDays" :min="0" class="full-width" />
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="24">
|
||||||
<el-col :span="8">
|
<el-col :span="12">
|
||||||
<el-form-item label="项目状态">
|
<el-form-item label="项目状态">
|
||||||
<el-select v-model="formData.projectStatus" placeholder="请选择项目状态" class="full-width">
|
<el-select v-model="formData.projectStatus" placeholder="请选择项目状态" class="full-width">
|
||||||
<el-option label="进行中" value="ongoing" />
|
<el-option label="进行中" value="ongoing" />
|
||||||
|
@ -37,12 +35,19 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="12">
|
||||||
|
<el-form-item label="预计工时">
|
||||||
|
<el-input type="number" v-model="formData.budgetDays" :min="0" class="full-width" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="12">
|
||||||
<el-form-item label="项目开始时间">
|
<el-form-item label="项目开始时间">
|
||||||
<el-date-picker v-model="formData.startDate" type="date" placeholder="选择日期" class="full-width" />
|
<el-date-picker v-model="formData.startDate" type="date" placeholder="选择日期" class="full-width" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="12">
|
||||||
<el-form-item label="项目结束时间">
|
<el-form-item label="项目结束时间">
|
||||||
<el-date-picker v-model="formData.endDate" type="date" placeholder="选择日期" class="full-width" />
|
<el-date-picker v-model="formData.endDate" type="date" placeholder="选择日期" class="full-width" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
@ -51,7 +56,7 @@
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div class="table-actions">
|
<div class="table-actions">
|
||||||
<el-button type="primary" @click="toggleEdit">{{ isEditing ? '保存' : '编辑' }}</el-button>
|
<el-button type="primary" :icon="Plus" @click="editUser">新增成员</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomTable
|
<CustomTable
|
||||||
|
@ -60,26 +65,88 @@
|
||||||
:total="total"
|
:total="total"
|
||||||
:show-selection="false"
|
:show-selection="false"
|
||||||
:show-index="true"
|
:show-index="true"
|
||||||
:table-height="400"
|
:table-height="tableHeight"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
|
<template #name="{ row }">
|
||||||
|
<el-input
|
||||||
|
v-model="row.name"
|
||||||
|
placeholder="选择人员"
|
||||||
|
readonly
|
||||||
|
@click="openSelectUser(row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #role="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-model="row.role"
|
||||||
|
placeholder="请选择职位"
|
||||||
|
@change="handleRoleChange(row)"
|
||||||
|
>
|
||||||
|
<el-option v-for="role in roleOptions" :key="role" :label="role" :value="role" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
<template #operation="{ row }">
|
<template #operation="{ row }">
|
||||||
<div class="operation-buttons">
|
<div class="operation-buttons">
|
||||||
<el-button text @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="danger" @click="confirmDelete(row)">删除</el-button>
|
||||||
<el-button text type="primary" @click="showTimesheet(row)">工时表</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</CustomTable>
|
</CustomTable>
|
||||||
|
|
||||||
|
<!-- 修改 SelectUser 组件 -->
|
||||||
|
<SelectUser
|
||||||
|
v-if="showSelectUser"
|
||||||
|
v-model:dialogVisible="showSelectUser"
|
||||||
|
:multi-select="false"
|
||||||
|
:selected-users="currentSelectedUser"
|
||||||
|
@confirm="handleUserConfirm"
|
||||||
|
@close="closeSelectUser"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectUser
|
||||||
|
v-if="showProjectManagerSelect"
|
||||||
|
v-model:dialogVisible="showProjectManagerSelect"
|
||||||
|
:multi-select="false"
|
||||||
|
:selected-users="projectManagerSelectedUser"
|
||||||
|
@confirm="handleProjectManagerConfirm"
|
||||||
|
@close="closeProjectManagerSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 删除确认对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="deleteDialogVisible"
|
||||||
|
title="确认删除"
|
||||||
|
width="30%"
|
||||||
|
>
|
||||||
|
<div class="delete-confirm">
|
||||||
|
<img src="@/assets/warning.png" alt="警告" class="warning-icon">
|
||||||
|
<p>确定要删除该成员吗?此操作不可逆。</p>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="deleteDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="danger" @click="handleDelete">确认删除</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import CustomTable from '@/components/table.vue'
|
import CustomTable from '@/components/table.vue'
|
||||||
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
|
import SelectUser from '@/components/SelectUser.vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
const isEditing = ref(false)
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const isEditing = ref(true)
|
||||||
|
const showSelectUser = ref(false)
|
||||||
|
const showProjectManagerSelect = ref(false)
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
projectName: '',
|
projectName: '',
|
||||||
projectCode: '',
|
projectCode: '',
|
||||||
|
@ -87,81 +154,178 @@ const formData = reactive({
|
||||||
budgetDays: 0,
|
budgetDays: 0,
|
||||||
projectStatus: '',
|
projectStatus: '',
|
||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: ''
|
endDate: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const projectOptions = [
|
const roleOptions = ['开发', '设计', '测试', '产品', '项目经理']
|
||||||
{ value: 'project1', label: '项目1' },
|
|
||||||
{ value: 'project2', label: '项目2' },
|
|
||||||
{ value: 'project3', label: '项目3' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ prop: 'name', label: '人员姓名' },
|
{ prop: 'name', label: '人员姓名', slot: 'name' },
|
||||||
{ prop: 'role', label: '项目角色' },
|
{ prop: 'role', label: '项目职位', slot: 'role' },
|
||||||
{ prop: 'workdays', label: '工时天数' },
|
{ prop: 'workdays', label: '累计人天数' },
|
||||||
{ prop: 'weight', label: '人员权重' },
|
|
||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 'auto',
|
width: '200',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
className: 'operation-column'
|
className: 'operation-column',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const tableData = ref(Array(22).fill(null).map((_, index) => ({
|
const tableData = ref([])
|
||||||
name: `员工${index + 1}`,
|
const total = ref(0)
|
||||||
role: ['开发', '设计', '测试', '产品'][index % 4],
|
const currentEditingRow = ref(null)
|
||||||
workdays: Math.floor(Math.random() * 20) + 10,
|
const currentSelectedUser = ref([])
|
||||||
weight: (Math.random() * 0.5 + 0.5).toFixed(2)
|
const projectManagerSelectedUser = ref([])
|
||||||
})))
|
|
||||||
|
|
||||||
const total = ref(tableData.value.length)
|
const validateDates = (rule, value, callback) => {
|
||||||
|
if (formData.startDate && formData.endDate) {
|
||||||
const toggleEdit = () => {
|
if (new Date(formData.startDate) > new Date(formData.endDate)) {
|
||||||
isEditing.value = !isEditing.value
|
callback(new Error('开始时间不能大于结束时间'))
|
||||||
if (!isEditing.value) {
|
} else {
|
||||||
// 这里可以添加保存逻辑
|
callback()
|
||||||
console.log('保存表单数据:', formData)
|
}
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSizeChange = (val) => {
|
const rules = {
|
||||||
|
startDate: [
|
||||||
|
{ required: true, message: '请选择开始时间', trigger: 'change' },
|
||||||
|
{ validator: validateDates, trigger: 'change' }
|
||||||
|
],
|
||||||
|
endDate: [
|
||||||
|
{ required: true, message: '请选择结束时间', trigger: 'change' },
|
||||||
|
{ validator: validateDates, trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const editUser = () => {
|
||||||
|
const newUser = {
|
||||||
|
id: Date.now(),
|
||||||
|
name: '',
|
||||||
|
role: '',
|
||||||
|
workdays: 0,
|
||||||
|
userId: null
|
||||||
|
}
|
||||||
|
tableData.value.push(newUser)
|
||||||
|
total.value = tableData.value.length
|
||||||
|
}
|
||||||
|
|
||||||
|
const openSelectUser = (row) => {
|
||||||
|
currentEditingRow.value = row
|
||||||
|
currentSelectedUser.value = row.userId ? [{ id: row.userId, name: row.name }] : []
|
||||||
|
showSelectUser.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUserConfirm = (users) => {
|
||||||
|
if (users.length > 0) {
|
||||||
|
const selectedUser = users[0]
|
||||||
|
currentEditingRow.value.name = selectedUser.name
|
||||||
|
currentEditingRow.value.userId = selectedUser.id
|
||||||
|
} else {
|
||||||
|
currentEditingRow.value.name = ''
|
||||||
|
currentEditingRow.value.userId = null
|
||||||
|
}
|
||||||
|
showSelectUser.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSelectUser = () => {
|
||||||
|
showSelectUser.value = false
|
||||||
|
currentEditingRow.value = null
|
||||||
|
currentSelectedUser.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const openProjectManagerSelect = () => {
|
||||||
|
projectManagerSelectedUser.value = formData.projectManager
|
||||||
|
? [{ id: formData.projectManagerId, name: formData.projectManager }]
|
||||||
|
: []
|
||||||
|
showProjectManagerSelect.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProjectManagerConfirm = (users) => {
|
||||||
|
if (users.length > 0) {
|
||||||
|
const selectedUser = users[0]
|
||||||
|
formData.projectManager = selectedUser.name
|
||||||
|
formData.projectManagerId = selectedUser.id
|
||||||
|
} else {
|
||||||
|
formData.projectManager = ''
|
||||||
|
formData.projectManagerId = null
|
||||||
|
}
|
||||||
|
showProjectManagerSelect.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeProjectManagerSelect = () => {
|
||||||
|
showProjectManagerSelect.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = val => {
|
||||||
console.log('每页显示条数:', val)
|
console.log('每页显示条数:', val)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCurrentChange = (val) => {
|
const handleCurrentChange = val => {
|
||||||
console.log('当前页:', val)
|
console.log('当前页:', val)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEdit = (row) => {
|
const handleRoleChange = (row) => {
|
||||||
console.log('编辑行:', row)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = (row) => {
|
|
||||||
console.log('删除行:', row)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const showTimesheet = (row) => {
|
const showTimesheet = (row) => {
|
||||||
console.log('显示工时表:', row)
|
router.push({
|
||||||
|
name: 'workLog',
|
||||||
|
params: { userId: row.userId },
|
||||||
|
query: { userName: row.name }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加以下代码来调整表格高度
|
const deleteDialogVisible = ref(false)
|
||||||
const tableHeight = ref(400) // 默认高度
|
const userToDelete = ref(null)
|
||||||
|
|
||||||
|
const confirmDelete = (row) => {
|
||||||
|
userToDelete.value = row
|
||||||
|
deleteDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
const index = tableData.value.findIndex(item => item.id === userToDelete.value.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
tableData.value.splice(index, 1)
|
||||||
|
total.value = tableData.value.length
|
||||||
|
ElMessage.success('已删除该成员')
|
||||||
|
deleteDialogVisible.value = false
|
||||||
|
userToDelete.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableHeight = ref(400)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateTableHeight()
|
const projectData = route.params.projectData || route.query.projectData
|
||||||
window.addEventListener('resize', updateTableHeight)
|
if (projectData) {
|
||||||
|
Object.assign(formData, projectData)
|
||||||
|
} else if (route.query.id) {
|
||||||
|
fetchProjectData(route.query.id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateTableHeight = () => {
|
const fetchProjectData = async id => {
|
||||||
const windowHeight = window.innerHeight
|
try {
|
||||||
const topOffset = document.querySelector('.project-management').offsetTop
|
const projectData = {
|
||||||
const formHeight = document.querySelector('.el-form').offsetHeight
|
id: id,
|
||||||
const actionsHeight = document.querySelector('.table-actions').offsetHeight
|
projectName: `项目${id}`,
|
||||||
const padding = 40 // 上下各20px的内边距
|
projectCode: `XM00${id}`,
|
||||||
tableHeight.value = windowHeight - topOffset - formHeight - actionsHeight - padding
|
projectManager: '张三',
|
||||||
|
budgetDays: 30,
|
||||||
|
projectStatus: 'ongoing',
|
||||||
|
startDate: '2024-08-31',
|
||||||
|
endDate: '2024-09-30',
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(formData, projectData)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取项目数据失败', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -169,38 +333,118 @@ const updateTableHeight = () => {
|
||||||
.project-management {
|
.project-management {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
min-height: 100vh;
|
height: 88vh;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.table-actions {
|
.custom-form {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
.form-container {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.full-width {
|
|
||||||
|
.custom-form :deep(.el-form-item) {
|
||||||
|
margin-bottom: 25px; /* 增加表单行间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-form :deep(.el-form-item__content) {
|
||||||
|
margin-left: auto !important;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
.custom-form :deep(.el-input) {
|
||||||
|
height: 42px; /* 调高输入框高度 */
|
||||||
|
}
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
height: 42px; /* 调高输入框高度 */
|
||||||
|
line-height: 42px;
|
||||||
|
}
|
||||||
|
.custom-form :deep(.el-input__wrapper),
|
||||||
|
.custom-form :deep(.el-date-editor.el-input),
|
||||||
|
.custom-form :deep(.el-input-number) {
|
||||||
|
height: 42px; /* 调高输入框高度 */
|
||||||
|
}
|
||||||
|
.custom-form :deep(.el-select) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
:deep(.el-input-number) {
|
.custom-form :deep(.el-input__wrapper),
|
||||||
width: 100%;
|
.custom-form :deep(.el-select .el-input__wrapper),
|
||||||
|
.custom-form :deep(.el-date-editor.el-input .el-input__wrapper),
|
||||||
|
.custom-form :deep(.el-input-number .el-input__wrapper) {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
:deep(.el-input-number .el-input__wrapper) {
|
|
||||||
|
.content-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-table) {
|
:deep(.el-table) {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.operation-buttons {
|
.operation-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.operation-buttons .el-button) {
|
:deep(.operation-buttons .el-button) {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.operation-column) {
|
:deep(.operation-column) {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 添加以下样式以使日期选择器宽度一致 */
|
||||||
|
:deep(.el-date-editor.el-input) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-editor.el-input .el-input__wrapper) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
:deep(.longInput) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.el-button.is-text {
|
||||||
|
min-width: 32px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select .el-input__wrapper) {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,281 @@
|
||||||
|
<template>
|
||||||
|
<div class="project-list">
|
||||||
|
<div class="search-bar">
|
||||||
|
<el-form :inline="true" :model="searchForm" class="demo-form-inline">
|
||||||
|
<el-form-item label="项目名称" class="form-item">
|
||||||
|
<el-input v-model="searchForm.projectName" placeholder="项目名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="负责人" class="form-item">
|
||||||
|
<el-select v-model="searchForm.manager" placeholder="负责人">
|
||||||
|
<el-option label="张三" value="张三" />
|
||||||
|
<el-option label="李四" value="李四" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目状态" class="form-item">
|
||||||
|
<el-select v-model="searchForm.status" placeholder="项目状态">
|
||||||
|
<el-option label="待启动" value="待启动" />
|
||||||
|
<el-option label="进行中" value="进行中" />
|
||||||
|
<el-option label="已完成" 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="table-actions mb10">
|
||||||
|
<el-button type="primary" @click="addProject">+ 新建项目</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CustomTable
|
||||||
|
:columns="columns"
|
||||||
|
:tableData="tableData"
|
||||||
|
:total="total"
|
||||||
|
:show-selection="false"
|
||||||
|
:show-index="true"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
>
|
||||||
|
<template #operation="{ row }">
|
||||||
|
<div class="operation-buttons">
|
||||||
|
<el-button text type="primary" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button text type="danger" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</CustomTable>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="deleteDialogVisible"
|
||||||
|
title="删除项目"
|
||||||
|
width="30%"
|
||||||
|
:before-close="handleCloseDeleteDialog"
|
||||||
|
class="delete-dialog"
|
||||||
|
>
|
||||||
|
<div class="delete-content">
|
||||||
|
<el-icon class="warning-icon"><WarningFilled /></el-icon>
|
||||||
|
<span>删除项目时将删除相关工作日志,此操作不可逆,请慎重考虑</span>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="deleteDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmDelete">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import CustomTable from '@/components/table.vue'
|
||||||
|
import { WarningFilled } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const searchForm = reactive({
|
||||||
|
projectName: '',
|
||||||
|
manager: '',
|
||||||
|
status: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ prop: 'projectName', label: '项目名称' },
|
||||||
|
{ prop: 'projectCode', label: '项目编号' },
|
||||||
|
{ prop: 'manager', label: '负责人' },
|
||||||
|
{ prop: 'budgetDays', label: '预计工时(天)' },
|
||||||
|
{ prop: 'startDate', label: '开始时间' },
|
||||||
|
{ prop: 'endDate', label: '结束时间' },
|
||||||
|
{ prop: 'status', label: '项目状态' },
|
||||||
|
{ prop: 'memberCount', label: '参与项目人数' },
|
||||||
|
{ prop: 'createdBy', label: '项目创建人' },
|
||||||
|
{
|
||||||
|
prop: 'operation',
|
||||||
|
label: '操作',
|
||||||
|
width: '150',
|
||||||
|
fixed: 'right',
|
||||||
|
className: 'operation-column',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const tableData = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const deleteDialogVisible = ref(false)
|
||||||
|
const currentDeleteItem = ref(null)
|
||||||
|
|
||||||
|
const onSearch = () => {
|
||||||
|
// 实现搜索逻辑
|
||||||
|
console.log('Search with:', searchForm)
|
||||||
|
fetchProjectList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
Object.keys(searchForm).forEach(key => {
|
||||||
|
searchForm[key] = ''
|
||||||
|
})
|
||||||
|
fetchProjectList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const addProject = () => {
|
||||||
|
router.push('/project/detail')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = row => {
|
||||||
|
router.push({
|
||||||
|
path: '/project/detail',
|
||||||
|
query: { id: row.id },
|
||||||
|
state: { projectData: row },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = row => {
|
||||||
|
currentDeleteItem.value = row
|
||||||
|
deleteDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseDeleteDialog = () => {
|
||||||
|
deleteDialogVisible.value = false
|
||||||
|
currentDeleteItem.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmDelete = () => {
|
||||||
|
// 实现删除逻辑
|
||||||
|
console.log('Delete project:', currentDeleteItem.value)
|
||||||
|
deleteDialogVisible.value = false
|
||||||
|
currentDeleteItem.value = null
|
||||||
|
fetchProjectList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = val => {
|
||||||
|
console.log(`每页 ${val} 条`)
|
||||||
|
fetchProjectList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = val => {
|
||||||
|
console.log(`当前页: ${val}`)
|
||||||
|
fetchProjectList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchProjectList = () => {
|
||||||
|
// 这里使用假数据,实际应该调用API
|
||||||
|
const fakeData = Array(20)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, index) => ({
|
||||||
|
id: index + 1,
|
||||||
|
projectName: `项目${index + 1}`,
|
||||||
|
projectCode: `XM00${index + 1}`,
|
||||||
|
manager: ['张三', '李四', '王五'][index % 3],
|
||||||
|
budgetDays: Math.floor(Math.random() * 100) + 10,
|
||||||
|
startDate: '2024-08-31',
|
||||||
|
endDate: '2024-09-15',
|
||||||
|
status: ['待启动', '进行中', '已完成'][index % 3],
|
||||||
|
memberCount: Math.floor(Math.random() * 10) + 3,
|
||||||
|
createdBy: '张三',
|
||||||
|
}))
|
||||||
|
tableData.value = fakeData
|
||||||
|
total.value = fakeData.length
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchProjectList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.project-list {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: white;
|
||||||
|
height: 88vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 :deep(.el-form-item__content) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item :deep(.el-input),
|
||||||
|
.form-item :deep(.el-select) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-buttons {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.operation-buttons .el-button) {
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.operation-column) {
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: -2px 0 5px rgba(0, 0, 0, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可以删除或注释掉之前的 .el-button.is-text 样式,如果不再需要的话 */
|
||||||
|
/* .el-button.is-text {
|
||||||
|
min-width: 32px !important;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* 添加以下样式来使对话框垂直居中 */
|
||||||
|
:deep(.delete-dialog.el-dialog) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,336 @@
|
||||||
|
<template>
|
||||||
|
<div class="project-progress-container">
|
||||||
|
<!-- 左侧固定列表格 -->
|
||||||
|
<div class="left-section">
|
||||||
|
<h2 class="mb20 textC">项目执行表</h2>
|
||||||
|
<CustomTable
|
||||||
|
:columns="fixedColumns"
|
||||||
|
:tableData="executionData"
|
||||||
|
:showPagination="false"
|
||||||
|
:maxHeight="tableHeight"
|
||||||
|
:showSummary="true"
|
||||||
|
:summaryMethod="getFixedColumnsSummaries"
|
||||||
|
>
|
||||||
|
<template #name="{ row }">
|
||||||
|
<span class="project-name" @click="goToDetail(row)">{{ row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</CustomTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧滚动列表格 -->
|
||||||
|
<div class="right-section">
|
||||||
|
<div class="date-range-container">
|
||||||
|
<span class="date-range-label">统计时间:</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
@change="handleDateRangeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomTable
|
||||||
|
:columns="scrollableColumns"
|
||||||
|
:tableData="executionData"
|
||||||
|
:showPagination="false"
|
||||||
|
:maxHeight="tableHeight"
|
||||||
|
:showSummary="true"
|
||||||
|
:summaryMethod="getScrollableColumnsSummaries"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import CustomTable from '@/components/table.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const dateRange = ref(getDefaultDateRange())
|
||||||
|
const tableHeight = ref('100%')
|
||||||
|
|
||||||
|
function getDefaultDateRange() {
|
||||||
|
const now = new Date()
|
||||||
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
|
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||||
|
return [startOfMonth, endOfMonth]
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixedColumns = [
|
||||||
|
{ prop: 'name', label: '项目', width: 200, slot: 'name' },
|
||||||
|
{ prop: 'presetDays', label: '预计工时\n(天)', width: 150 },
|
||||||
|
{ prop: 'actualDays', label: '累计工时\n(天)', width: 150 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const scrollableColumns = computed(() => {
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
const start = new Date(dateRange.value[0])
|
||||||
|
const end = new Date(dateRange.value[1])
|
||||||
|
const days = []
|
||||||
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||||
|
const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][d.getDay()]
|
||||||
|
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||||
|
days.push({
|
||||||
|
prop: dateStr,
|
||||||
|
label: `${dayOfWeek}\n${dateStr}`,
|
||||||
|
minWidth: 100
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
const executionData = computed(() => {
|
||||||
|
const projectList = [
|
||||||
|
{ id: 1, name: '项目1', presetDays: 20, actualDays: 21 },
|
||||||
|
{ id: 2, name: '项目2', presetDays: 20, actualDays: 13 },
|
||||||
|
{ id: 3, name: '项目3', presetDays: 20, actualDays: 15 },
|
||||||
|
{ id: 4, name: '项目4', presetDays: 20, actualDays: 17 },
|
||||||
|
{ id: 5, name: '项目5', presetDays: 20, actualDays: 18 },
|
||||||
|
]
|
||||||
|
|
||||||
|
return projectList.map(project => {
|
||||||
|
const data = {
|
||||||
|
...project,
|
||||||
|
}
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
const start = new Date(dateRange.value[0])
|
||||||
|
const end = new Date(dateRange.value[1])
|
||||||
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||||
|
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||||
|
data[dateStr] = Math.floor(Math.random() * 8) // 随机生成0-8的工时据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleDateRangeChange = () => {
|
||||||
|
// 日期范围变化时,可以在这里添加额外的逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFixedColumnsSummaries = param => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums = []
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
sums[index] = '合计工时(天)'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const values = data.map(item => Number(item[column.property]))
|
||||||
|
if (!values.every(value => isNaN(value))) {
|
||||||
|
sums[index] = values.reduce((prev, curr) => {
|
||||||
|
const value = Number(curr)
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
return prev + curr
|
||||||
|
} else {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
sums[index] = Number(sums[index].toFixed(2))
|
||||||
|
} else {
|
||||||
|
sums[index] = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
|
const getScrollableColumnsSummaries = param => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums = []
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
const values = data.map(item => Number(item[column.property]))
|
||||||
|
if (!values.every(value => isNaN(value))) {
|
||||||
|
sums[index] = values.reduce((prev, curr) => {
|
||||||
|
const value = Number(curr)
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
return prev + curr
|
||||||
|
} else {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
sums[index] = Number(sums[index].toFixed(2))
|
||||||
|
} else {
|
||||||
|
sums[index] = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTableHeight = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const container = document.querySelector('.project-execution-table')
|
||||||
|
if (container) {
|
||||||
|
const containerHeight = container.clientHeight
|
||||||
|
const otherElementsHeight =
|
||||||
|
container.querySelector('h2').offsetHeight + container.querySelector('.date-range').offsetHeight
|
||||||
|
tableHeight.value = `${containerHeight - otherElementsHeight - 40}px` // 40px for margins and paddings
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToDetail = (row) => {
|
||||||
|
router.push({
|
||||||
|
name: 'ProjectDetail',
|
||||||
|
params: { id: row.id },
|
||||||
|
query: {
|
||||||
|
startDate: dateRange.value[0].toISOString().split('T')[0],
|
||||||
|
endDate: dateRange.value[1].toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', updateTableHeight)
|
||||||
|
updateTableHeight()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', updateTableHeight)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.project-progress-container {
|
||||||
|
display: flex;
|
||||||
|
height: 88vh;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section,
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
width: 400px; /* 根据固定列的总宽度调整 */
|
||||||
|
padding-top: 34px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5); /* 添加右侧阴影 */
|
||||||
|
z-index: 1; /* 确保左侧在右侧之上 */
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
flex: 1;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 20px;
|
||||||
|
padding-left: 0; /* 移除左侧内边距,与左侧部分紧密相连 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-header {
|
||||||
|
height: 102px; /* 与左侧表格的标题和日期选择器高度一致 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
/* border: 1px solid #dcdfe6; */
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header th) {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body td) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__footer td) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center; /* 确保合计行内容居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header .cell),
|
||||||
|
:deep(.el-table__body .cell),
|
||||||
|
:deep(.el-table__footer .cell) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body-wrapper) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保两个表格的高度一致 */
|
||||||
|
.left-section :deep(.el-table),
|
||||||
|
.right-section :deep(.el-table) {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整日期选择器样式 */
|
||||||
|
:deep(.el-date-editor) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整表格内容的字体大小 */
|
||||||
|
:deep(.el-table) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整表头的样式 */
|
||||||
|
:deep(.el-table__header-wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
.rightTab :deep(.el-table__header th) {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.el-table__header th) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整合计行的样式 */
|
||||||
|
:deep(.el-table__footer-wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__footer td) {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 500px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-editor.el-input__wrapper) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.custom-table {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,316 @@
|
||||||
|
<template>
|
||||||
|
<div class="project-progress-container">
|
||||||
|
<!-- 左侧项目信息表单 -->
|
||||||
|
<div class="left-section">
|
||||||
|
<h3 class="mb20 ml10 textl">项目人员表</h3>
|
||||||
|
<el-form :model="projectInfo" label-width="120px" class="project-info-form">
|
||||||
|
<el-form-item label="选择项目">
|
||||||
|
<el-select v-model="selectedProject" placeholder="请选择项目" @change="handleProjectChange">
|
||||||
|
<el-option
|
||||||
|
v-for="project in projectList"
|
||||||
|
:key="project.id"
|
||||||
|
:label="project.name"
|
||||||
|
:value="project.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目名称">
|
||||||
|
<span>{{ projectInfo.name }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目编码">
|
||||||
|
<span>{{ projectInfo.code }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="预计工时">
|
||||||
|
<span>{{ projectInfo.presetDays }} 天</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目开始时间">
|
||||||
|
<span>{{ projectInfo.startDate }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目结束时间">
|
||||||
|
<span>{{ projectInfo.endDate }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧滚动列表格 -->
|
||||||
|
<div class="right-section">
|
||||||
|
<div class="date-range-container">
|
||||||
|
<span class="date-range-label">统计时间:</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
@change="handleDateRangeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomTable
|
||||||
|
:columns="scrollableColumns"
|
||||||
|
:tableData="executionData"
|
||||||
|
:showPagination="false"
|
||||||
|
:maxHeight="tableHeight"
|
||||||
|
@button-click="handleButtonClick"
|
||||||
|
>
|
||||||
|
</CustomTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import CustomTable from '@/components/table.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const dateRange = ref(getDefaultDateRange())
|
||||||
|
const tableHeight = ref('100%')
|
||||||
|
const selectedProject = ref(null)
|
||||||
|
const projectInfo = ref({
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
presetDays: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const projectList = [
|
||||||
|
{ id: 1, name: '项目1', code: 'XM001', presetDays: 120, startDate: '2024-09-01', endDate: '2024-09-30' },
|
||||||
|
{ id: 2, name: '项目2', code: 'XM002', presetDays: 90, startDate: '2024-10-01', endDate: '2024-12-31' },
|
||||||
|
// 添加更多项目...
|
||||||
|
]
|
||||||
|
|
||||||
|
function getDefaultDateRange() {
|
||||||
|
const now = new Date()
|
||||||
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
|
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||||
|
return [startOfMonth, endOfMonth]
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollableColumns = computed(() => {
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
const start = new Date(dateRange.value[0])
|
||||||
|
const end = new Date(dateRange.value[1])
|
||||||
|
const days = []
|
||||||
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||||
|
const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][d.getDay()]
|
||||||
|
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||||
|
days.push({
|
||||||
|
prop: dateStr,
|
||||||
|
label: `${dayOfWeek}\n${dateStr}`,
|
||||||
|
minWidth: 100,
|
||||||
|
type: 'multiButton'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
const executionData = computed(() => {
|
||||||
|
// 这里我们模拟一些数据,实际使用时应该从后端获取
|
||||||
|
const data = {
|
||||||
|
'张三': ['2023/5/1', '2023/5/2', '2023/5/3'],
|
||||||
|
'李四': ['2023/5/1', '2023/5/3', '2023/5/4'],
|
||||||
|
'王五': ['2023/5/2', '2023/5/3', '2023/5/5'],
|
||||||
|
'赵六': ['2023/5/1', '2023/5/4', '2023/5/5'],
|
||||||
|
'钱七': ['2023/5/2', '2023/5/4', '2023/5/5']
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {}
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
const start = new Date(dateRange.value[0])
|
||||||
|
const end = new Date(dateRange.value[1])
|
||||||
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||||
|
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||||
|
result[dateStr] = Object.keys(data).filter(name =>
|
||||||
|
data[name].includes(`${d.getFullYear()}/${dateStr}`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [result] // 将结果包装在数组中,因为 el-table 期望一个数组
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleDateRangeChange = () => {
|
||||||
|
// 日期范围变化时,可以在这里添加额外的逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProjectChange = projectId => {
|
||||||
|
const selectedProjectInfo = projectList.find(project => project.id === projectId)
|
||||||
|
if (selectedProjectInfo) {
|
||||||
|
projectInfo.value = { ...selectedProjectInfo }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleButtonClick = ({ userName, date }) => {
|
||||||
|
console.log(`点击了 ${userName} 在 ${date} 的工作日志`)
|
||||||
|
// 这里可以添加跳转到工作日志页面的逻辑
|
||||||
|
router.push({
|
||||||
|
name: 'WorkLog',
|
||||||
|
params: { userId: userName },
|
||||||
|
query: {
|
||||||
|
date: date,
|
||||||
|
projectId: selectedProject.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.project-progress-container {
|
||||||
|
display: flex;
|
||||||
|
height: 88vh;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section,
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
width: 400px; /* 根据固定列的总宽度调整 */
|
||||||
|
padding-top: 34px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5); /* 添加右侧阴影 */
|
||||||
|
z-index: 1; /* 确保左侧在右侧之上 */
|
||||||
|
margin-right: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
flex: 1;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 20px;
|
||||||
|
padding-left: 0; /* 移除左侧内边距,与左侧部分紧密相连 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-header {
|
||||||
|
height: 102px; /* 与左侧表格的标题和日期选择器高度一致 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
/* border: 1px solid #dcdfe6; */
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header th) {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body td) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__footer td) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center; /* 确保合计行内容居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header .cell),
|
||||||
|
:deep(.el-table__body .cell),
|
||||||
|
:deep(.el-table__footer .cell) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body-wrapper) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保两个表格的高度一致 */
|
||||||
|
.left-section :deep(.el-table),
|
||||||
|
.right-section :deep(.el-table) {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整日期选择器样式 */
|
||||||
|
:deep(.el-date-editor) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整表内容的字体大小 */
|
||||||
|
:deep(.el-table) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整表头的样式 */
|
||||||
|
:deep(.el-table__header-wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
.rightTab :deep(.el-table__header th) {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.el-table__header th) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整合计行的样式 */
|
||||||
|
:deep(.el-table__footer-wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__footer td) {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 500px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-editor.el-input__wrapper) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.custom-table {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-select-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
right: 10px;
|
||||||
|
width: 220px;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
span {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,380 @@
|
||||||
|
<template>
|
||||||
|
<div class="project-progress-container">
|
||||||
|
<!-- 左侧固定列表格 -->
|
||||||
|
<div class="left-section">
|
||||||
|
<h3 class="mb20 ml10 textl">人员项目表</h3>
|
||||||
|
<div class="user-select-container">
|
||||||
|
<span>选择人员</span>
|
||||||
|
<el-input v-model="selectedUserName" placeholder="请选择用户" readonly @click="openUserSelectDialog"></el-input>
|
||||||
|
</div>
|
||||||
|
<CustomTable
|
||||||
|
:columns="fixedColumns"
|
||||||
|
:tableData="executionData"
|
||||||
|
:showPagination="false"
|
||||||
|
:maxHeight="tableHeight"
|
||||||
|
:showSummary="true"
|
||||||
|
:summaryMethod="getFixedColumnsSummaries"
|
||||||
|
:border="true"
|
||||||
|
>
|
||||||
|
<template #name="{ row }">
|
||||||
|
<span class="project-name" @click="goToDetail(row)">{{ row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</CustomTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧滚动列表格 -->
|
||||||
|
<div class="right-section">
|
||||||
|
<div class="date-range-container">
|
||||||
|
<span class="date-range-label">统计时间:</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
@change="handleDateRangeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomTable
|
||||||
|
:columns="scrollableColumns"
|
||||||
|
:tableData="executionData"
|
||||||
|
:showPagination="false"
|
||||||
|
:maxHeight="tableHeight"
|
||||||
|
:showSummary="true"
|
||||||
|
:summaryMethod="getScrollableColumnsSummaries"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户选择对话框 -->
|
||||||
|
<SelectUser
|
||||||
|
v-model:dialogVisible="userSelectDialogVisible"
|
||||||
|
:multiSelect="false"
|
||||||
|
:currentSelectedUser="selectedUser ? [selectedUser] : []"
|
||||||
|
@confirm="handleUserSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import CustomTable from '@/components/table.vue'
|
||||||
|
import SelectUser from '@/components/selectUser.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const dateRange = ref(getDefaultDateRange())
|
||||||
|
const tableHeight = ref('100%')
|
||||||
|
const userSelectDialogVisible = ref(false)
|
||||||
|
const selectedUser = ref(null)
|
||||||
|
const selectedUserName = computed(() => (selectedUser.value ? selectedUser.value.name : ''))
|
||||||
|
|
||||||
|
function getDefaultDateRange() {
|
||||||
|
const now = new Date()
|
||||||
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
|
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||||
|
return [startOfMonth, endOfMonth]
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixedColumns = [
|
||||||
|
{ prop: 'name', label: '项目', slot: 'name' },
|
||||||
|
{ prop: 'presetDays', label: '统计工时\n(天)' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const scrollableColumns = computed(() => {
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
const start = new Date(dateRange.value[0])
|
||||||
|
const end = new Date(dateRange.value[1])
|
||||||
|
const days = []
|
||||||
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||||
|
const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][d.getDay()]
|
||||||
|
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||||
|
days.push({
|
||||||
|
prop: dateStr,
|
||||||
|
label: `${dayOfWeek}\n${dateStr}`,
|
||||||
|
minWidth: 100,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
const executionData = computed(() => {
|
||||||
|
const projectList = [
|
||||||
|
{ id: 1, name: '项目1', presetDays: 20 },
|
||||||
|
{ id: 2, name: '项目2', presetDays: 20 },
|
||||||
|
{ id: 3, name: '项目3', presetDays: 20 },
|
||||||
|
{ id: 4, name: '项目4', presetDays: 20 },
|
||||||
|
{ id: 5, name: '项目5', presetDays: 20 },
|
||||||
|
]
|
||||||
|
|
||||||
|
return projectList.map(project => {
|
||||||
|
const data = {
|
||||||
|
...project,
|
||||||
|
}
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
const start = new Date(dateRange.value[0])
|
||||||
|
const end = new Date(dateRange.value[1])
|
||||||
|
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||||
|
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||||
|
data[dateStr] = Math.floor(Math.random() * 8) // 随机生成0-8的工时据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleDateRangeChange = () => {
|
||||||
|
// 日期范围变化时,可以在这里添加额外的逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFixedColumnsSummaries = param => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums = []
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
sums[index] = '合计工时(天)'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const values = data.map(item => Number(item[column.property]))
|
||||||
|
if (!values.every(value => isNaN(value))) {
|
||||||
|
sums[index] = values.reduce((prev, curr) => {
|
||||||
|
const value = Number(curr)
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
return prev + curr
|
||||||
|
} else {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
sums[index] = Number(sums[index].toFixed(2))
|
||||||
|
} else {
|
||||||
|
sums[index] = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
|
const getScrollableColumnsSummaries = param => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums = []
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
const values = data.map(item => Number(item[column.property]))
|
||||||
|
if (!values.every(value => isNaN(value))) {
|
||||||
|
sums[index] = values.reduce((prev, curr) => {
|
||||||
|
const value = Number(curr)
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
return prev + curr
|
||||||
|
} else {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
sums[index] = Number(sums[index].toFixed(2))
|
||||||
|
} else {
|
||||||
|
sums[index] = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTableHeight = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const container = document.querySelector('.project-execution-table')
|
||||||
|
if (container) {
|
||||||
|
const containerHeight = container.clientHeight
|
||||||
|
const otherElementsHeight =
|
||||||
|
container.querySelector('h2').offsetHeight + container.querySelector('.date-range').offsetHeight
|
||||||
|
tableHeight.value = `${containerHeight - otherElementsHeight - 40}px` // 40px for margins and paddings
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToDetail = row => {
|
||||||
|
router.push({
|
||||||
|
name: 'ProjectDetail',
|
||||||
|
params: { id: row.id },
|
||||||
|
query: {
|
||||||
|
startDate: dateRange.value[0].toISOString().split('T')[0],
|
||||||
|
endDate: dateRange.value[1].toISOString().split('T')[0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const openUserSelectDialog = () => {
|
||||||
|
userSelectDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUserSelect = users => {
|
||||||
|
if (users.length > 0) {
|
||||||
|
selectedUser.value = users[0]
|
||||||
|
} else {
|
||||||
|
selectedUser.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', updateTableHeight)
|
||||||
|
updateTableHeight()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', updateTableHeight)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.project-progress-container {
|
||||||
|
display: flex;
|
||||||
|
height: 88vh;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section,
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
width: 400px; /* 根据固定列的总宽度调整 */
|
||||||
|
padding-top: 34px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5); /* 添加右侧阴影 */
|
||||||
|
z-index: 1; /* 确保左侧在右侧之上 */
|
||||||
|
margin-right: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
flex: 1;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 20px;
|
||||||
|
padding-left: 0; /* 移除左侧内边距,与左侧部分紧密相连 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-header {
|
||||||
|
height: 102px; /* 与左侧表格的标题和日期选择器高度一致 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
/* border: 1px solid #dcdfe6; */
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header th) {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body td) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__footer td) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center; /* 确保合计行内容居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__header .cell),
|
||||||
|
:deep(.el-table__body .cell),
|
||||||
|
:deep(.el-table__footer .cell) {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body-wrapper) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保两个表格的高度一致 */
|
||||||
|
.left-section :deep(.el-table),
|
||||||
|
.right-section :deep(.el-table) {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整日期选择器样式 */
|
||||||
|
:deep(.el-date-editor) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整表<E695B4><E8A1A8><EFBFBD>内容的字体大小 */
|
||||||
|
:deep(.el-table) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整表头的样式 */
|
||||||
|
:deep(.el-table__header-wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
.rightTab :deep(.el-table__header th) {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.el-table__header th) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
color: #606266;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整合计行的样式 */
|
||||||
|
:deep(.el-table__footer-wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__footer td) {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 500px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-editor.el-input__wrapper) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.custom-table {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-select-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
right: 10px;
|
||||||
|
width: 220px;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
gap:10px;
|
||||||
|
span{
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,576 @@
|
||||||
|
<template>
|
||||||
|
<div class="work-log-container">
|
||||||
|
<!-- 左侧项目列表 -->
|
||||||
|
<div class="project-list" :class="{ collapsed: isCollapsed }">
|
||||||
|
<div class="list-header">
|
||||||
|
<span v-if="!isCollapsed">项目列表</span>
|
||||||
|
<div class="collapse-button-wrapper">
|
||||||
|
<el-button type="text" @click="toggleCollapse" class="collapse-button">
|
||||||
|
<el-icon :size="20">
|
||||||
|
<ArrowLeft v-if="!isCollapsed" />
|
||||||
|
<ArrowRight v-else />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
v-show="!isCollapsed"
|
||||||
|
:data="projectList"
|
||||||
|
style="width: 100%"
|
||||||
|
@row-click="handleProjectClick"
|
||||||
|
highlight-current-row
|
||||||
|
:show-summary="true"
|
||||||
|
:summary-method="getSummaries"
|
||||||
|
sum-text="合计工时(h)"
|
||||||
|
>
|
||||||
|
<el-table-column prop="name" label="项目" align="center"></el-table-column>
|
||||||
|
<el-table-column prop="workHours" label="工时(小时)" align="center"></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧项目信息和日历 -->
|
||||||
|
<div class="project-info-calendar">
|
||||||
|
<h2 class="mb20 textC">工作日志</h2>
|
||||||
|
<!-- 项目信息表单 -->
|
||||||
|
<el-form :model="projectInfo" label-width="100px" disabled class="project-info-form">
|
||||||
|
<el-form-item label="项目名称">
|
||||||
|
<el-input v-model="projectInfo.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目编号">
|
||||||
|
<el-input v-model="projectInfo.code"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="工时填报人">
|
||||||
|
<el-input v-model="projectInfo.reporter"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目开始时间">
|
||||||
|
<el-date-picker v-model="projectInfo.startDate" type="date"></el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="项目结束时间">
|
||||||
|
<el-date-picker v-model="projectInfo.endDate" type="date"></el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 日历选择器 -->
|
||||||
|
|
||||||
|
<!-- 日历视图 -->
|
||||||
|
<div class="calendar-view" v-if="currentProject">
|
||||||
|
<el-calendar v-model="selectedDate" @input="handleMonthChange">
|
||||||
|
<template #header="{ date }">
|
||||||
|
<div class="calendar-header">
|
||||||
|
<el-date-picker v-model="selectedDate" type="month" format="YYYY年MM月" :clearable="false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #dateCell="{ data }">
|
||||||
|
<div
|
||||||
|
@click="openLogDialog(data)"
|
||||||
|
:class="{
|
||||||
|
'date-cell': true,
|
||||||
|
'in-range': isInProjectRange(data),
|
||||||
|
'out-range': !isInProjectRange(data),
|
||||||
|
disabled: isFutureDate(data.date) && isInProjectRange(data),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ data.day.split('-')[2] }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 工作日志对话框 -->
|
||||||
|
<el-dialog v-model="logDialogVisible" title="工作日志" width="30%">
|
||||||
|
<el-form :model="logForm" label-width="100px" class="log-form">
|
||||||
|
<el-form-item label="日期">
|
||||||
|
<el-input v-model="logForm.date" disabled></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="工作内容">
|
||||||
|
<el-input v-model="logForm.content" type="textarea" :rows="4"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="logDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveWorkLog">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
|
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const isCollapsed = ref(false)
|
||||||
|
const selectedDate = ref(new Date())
|
||||||
|
const currentMonth = ref(new Date())
|
||||||
|
|
||||||
|
// 更新项目列表,包含工时数据
|
||||||
|
const projectList = ref([
|
||||||
|
{ id: 1, name: '项目1', startDate: '2023-01-01', endDate: '2023-06-30', workHours: 120 },
|
||||||
|
{ id: 2, name: '项目2', startDate: '2023-03-15', endDate: '2023-12-31', workHours: 80 },
|
||||||
|
{ id: 3, name: '项目3', startDate: '2023-02-01', endDate: '2023-08-31', workHours: 160 },
|
||||||
|
])
|
||||||
|
|
||||||
|
// 当前选中的项目
|
||||||
|
const currentProject = ref(null)
|
||||||
|
const toggleCollapse = () => {
|
||||||
|
isCollapsed.value = !isCollapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProjectClick = row => {
|
||||||
|
currentProject.value = row
|
||||||
|
// 可能需要重置选中的日期或执行其他操作
|
||||||
|
}
|
||||||
|
|
||||||
|
const openLogDialog = data => {
|
||||||
|
if (!currentProject.value) {
|
||||||
|
ElMessage.warning('请先选择一个项目')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 data.day 而不是 data.date
|
||||||
|
const clickedDate = new Date(data.day)
|
||||||
|
const currentDate = new Date()
|
||||||
|
|
||||||
|
// 将两个日期都设置为当天的开始时间(UTC)
|
||||||
|
clickedDate.setUTCHours(0, 0, 0, 0)
|
||||||
|
currentDate.setUTCHours(0, 0, 0, 0)
|
||||||
|
|
||||||
|
if (clickedDate.getTime() > currentDate.getTime()) {
|
||||||
|
ElMessage.warning('不可编辑未来日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(data.day)
|
||||||
|
const start = new Date(currentProject.value.startDate)
|
||||||
|
const end = new Date(currentProject.value.endDate)
|
||||||
|
// 使用 getTime() 进行比较,这样可以准确比较日期
|
||||||
|
let flag = date.getTime() >= start.getTime() && date.getTime() <= end.getTime()
|
||||||
|
console.log(currentProject.value.endDate, currentProject.value.startDate, data.day)
|
||||||
|
|
||||||
|
if (!flag) {
|
||||||
|
ElMessage.warning('该日期不在项目范围内')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 这里可以添加打开日志对话框的逻辑
|
||||||
|
logDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isInProjectRange = data => {
|
||||||
|
if (!currentProject.value) return fals
|
||||||
|
const date = new Date(data.day)
|
||||||
|
const start = new Date(currentProject.value.startDate)
|
||||||
|
const end = new Date(currentProject.value.endDate)
|
||||||
|
// 使用 getTime() 进行比较,这样可以准确比较日期
|
||||||
|
return date.getTime() >= start.getTime() && date.getTime() <= end.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFutureDate = date => {
|
||||||
|
const clickedDate = new Date(date)
|
||||||
|
const currentDate = new Date()
|
||||||
|
return clickedDate.getTime() > currentDate.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSummaries = param => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums = []
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
sums[index] = '合计工时(h)'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const values = data.map(item => Number(item[column.property]))
|
||||||
|
if (!values.every(value => isNaN(value))) {
|
||||||
|
sums[index] = values.reduce((prev, curr) => {
|
||||||
|
const value = Number(curr)
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
return prev + curr
|
||||||
|
} else {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
} else {
|
||||||
|
sums[index] = 'N/A'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
|
const logForm = ref({
|
||||||
|
date: '',
|
||||||
|
content: '',
|
||||||
|
})
|
||||||
|
// 项目信息
|
||||||
|
const projectInfo = ref({
|
||||||
|
name: '项目1',
|
||||||
|
code: 'XM5836383',
|
||||||
|
reporter: '张三',
|
||||||
|
startDate: '2024-6-15',
|
||||||
|
endDate: '2024-8-29',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 工作日志对话框
|
||||||
|
const logDialogVisible = ref(false)
|
||||||
|
const workLog = ref({
|
||||||
|
hours: 0,
|
||||||
|
content: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 保存工作日志
|
||||||
|
const saveWorkLog = () => {
|
||||||
|
// 这里添加保存工作日志的逻辑
|
||||||
|
console.log('保存工作日志', workLog.value)
|
||||||
|
logDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMonthChange = date => {
|
||||||
|
currentMonth.value = date
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectDate = type => {
|
||||||
|
const date = new Date(currentMonth.value)
|
||||||
|
switch (type) {
|
||||||
|
case 'prev-month':
|
||||||
|
date.setMonth(date.getMonth() - 1)
|
||||||
|
break
|
||||||
|
case 'today':
|
||||||
|
date = new Date()
|
||||||
|
break
|
||||||
|
case 'next-month':
|
||||||
|
date.setMonth(date.getMonth() + 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentMonth.value = date
|
||||||
|
selectedDate.value = date
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听当前项目的变化,重置选中的日期
|
||||||
|
watch(currentProject, newProject => {
|
||||||
|
if (newProject) {
|
||||||
|
selectedDate.value = new Date(newProject.startDate)
|
||||||
|
currentMonth.value = new Date(newProject.startDate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const userId = route.params.userId
|
||||||
|
const userName = route.query.userName
|
||||||
|
|
||||||
|
if (userId && userName) {
|
||||||
|
console.log(`加载用户 ${userName}(ID: ${userId})的工作日志`)
|
||||||
|
// 这里可以添加加载特定用户工作日志的逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... 其他初始化代码 ...
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.work-log-container {
|
||||||
|
display: flex;
|
||||||
|
height: 88vh;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list {
|
||||||
|
width: 300px;
|
||||||
|
border-right: 1px solid #dcdfe6;
|
||||||
|
transition: all 0.3s;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list.collapsed {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #dcdfe6;
|
||||||
|
position: relative;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-button-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40px; /* 固定宽度 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list.collapsed .collapse-button-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-button {
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-button:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-button :deep(.el-icon) {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table__body-wrapper) {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table__footer-wrapper) {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table__footer) {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table__footer td) {
|
||||||
|
background-color: white !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table__body td),
|
||||||
|
.project-list :deep(.el-table__footer td) {
|
||||||
|
height: 60px; /* 增加单格度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-info-calendar {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-picker {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar-day) {
|
||||||
|
padding: 4px; /* 恢复默认内边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar-day:hover) {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #f2f6fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.date-cell) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.in-range) {
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
}
|
||||||
|
.calendar-view :deep(.out-range) {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.disabled) {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #c0c4cc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table .cell) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-form-item) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-input__inner) {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-textarea__inner) {
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar-header) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-form :deep(.el-form-item) {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-form :deep(.el-input__wrapper) {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-form :deep(.el-input__inner) {
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-form :deep(.el-textarea__inner) {
|
||||||
|
min-height: 150px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-form :deep(.el-input__wrapper) {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-form :deep(.el-input__inner) {
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-form :deep(.el-textarea__inner) {
|
||||||
|
min-height: 150px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 增加 el-form-item__label 的高度和行高 */
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
height: 42px; /* 默认通常是 32px,所以增加 10px 后变为 42px */
|
||||||
|
line-height: 42px; /* 行高与高度相同,确保文字垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 保持 el-form-item__content 的一致性 */
|
||||||
|
:deep(.el-form-item__content) {
|
||||||
|
min-height: 42px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调整输入框的高度以匹配增加的空间 */
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
height: 42px;
|
||||||
|
line-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对于 textarea,可能需要单独调整 */
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-calendar__header) {
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
:deep(.el-calendar__body) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 14px; /* 缩小字体大小 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar) {
|
||||||
|
--el-calendar-cell-width: 40px; /* 缩小日历单元格宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar__header) {
|
||||||
|
padding: 10px 0; /* 减小头部内边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar__body) {
|
||||||
|
padding: 10px 0; /* 减小主体内边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar__week) {
|
||||||
|
background-color: #4a4a4a; /* 深灰色背景 */
|
||||||
|
color: white; /* 白色文字 */
|
||||||
|
padding: 5px 0; /* 增加一些内边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-view :deep(.el-calendar-day) {
|
||||||
|
height: 65px; /* 增加日期单元格高度(原高度 + 5px) */
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table__body-wrapper) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-list :deep(.el-table .cell) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-cell {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-cell.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue