静态部分完成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';
|
||||
|
||||
// @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;
|
||||
|
@ -117,76 +104,11 @@ body {
|
|||
.aifs {
|
||||
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 {
|
||||
|
@ -256,18 +178,8 @@ body {
|
|||
.no_wrap {
|
||||
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 {
|
||||
@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加了该类名,导致页面右侧偏移
|
||||
.el-popup-parent--hidden {
|
||||
|
@ -412,67 +316,8 @@ body {
|
|||
// 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) {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
@ -491,35 +336,8 @@ body {
|
|||
.pr15 {
|
||||
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)) {
|
||||
background-color: #5584ff !important ;
|
||||
color: #fff !important;
|
||||
|
@ -531,18 +349,7 @@ body {
|
|||
padding: 10px 0;
|
||||
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) {
|
||||
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,144 +1,163 @@
|
|||
<template>
|
||||
<div class="custom-table" ref="tableContainer">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:height="tableHeight"
|
||||
v-bind="$attrs"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
v-if="showSelection"
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="showIndex"
|
||||
type="index"
|
||||
width="50"
|
||||
label="序号"
|
||||
/>
|
||||
<template v-for="(column, index) in columns" :key="index">
|
||||
<el-table-column
|
||||
v-bind="column"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
>
|
||||
<template #default="scope">
|
||||
<slot :name="column.prop" :row="scope.row">
|
||||
<div class="custom-table" ref="tableContainer">
|
||||
<el-table
|
||||
ref="elTableRef"
|
||||
:data="tableData"
|
||||
:max-height="computedTableHeight"
|
||||
v-bind="$attrs"
|
||||
@selection-change="handleSelectionChange"
|
||||
:border="border"
|
||||
>
|
||||
<el-table-column v-if="showSelection" type="selection" width="55" />
|
||||
<el-table-column v-if="showIndex" type="index" width="50" label="序号" />
|
||||
<template v-for="(column, index) in columns" :key="index">
|
||||
<el-table-column v-bind="column" :prop="column.prop" :label="column.label">
|
||||
<template #default="scope">
|
||||
<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] }}
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-if="showPagination"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="pageSizes"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
<div class="pagination-container" ref="paginationContainer" v-if="showPagination">
|
||||
<el-pagination
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="pageSizes"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import useTableResize from '@/hooks/useTableResize'
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
showSelection: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showPagination: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default: () => [10, 20, 30, 50]
|
||||
},
|
||||
tableHeight: {
|
||||
type: Number,
|
||||
default: 400
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import useTableResize from '@/hooks/useTableResize'
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showPagination: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default: () => [10, 20, 30, 50],
|
||||
},
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
offsetHeight: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['selection-change', 'size-change', 'current-change', 'button-click'])
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const tableContainer = ref(null)
|
||||
const paginationContainer = ref(null)
|
||||
const computedTableHeight = ref(null)
|
||||
const elTableRef = ref(null)
|
||||
|
||||
const handleSelectionChange = selection => {
|
||||
emit('selection-change', selection)
|
||||
}
|
||||
|
||||
const handleSizeChange = val => {
|
||||
pageSize.value = val
|
||||
emit('size-change', val)
|
||||
}
|
||||
|
||||
const handleCurrentChange = val => {
|
||||
currentPage.value = val
|
||||
emit('current-change', val)
|
||||
}
|
||||
|
||||
const updateTableHeight = () => {
|
||||
nextTick(() => {
|
||||
if (tableContainer.value && paginationContainer.value) {
|
||||
const parentElement = tableContainer.value.parentElement
|
||||
const parentHeight = parentElement.clientHeight
|
||||
const tableTop = tableContainer.value.getBoundingClientRect().top - parentElement.getBoundingClientRect().top
|
||||
const paginationHeight = props.showPagination ? paginationContainer.value.offsetHeight + 30 : 0 // 加上 margin-top
|
||||
computedTableHeight.value = parentHeight - tableTop - paginationHeight
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['selection-change', 'size-change', 'current-change'])
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const tableContainer = ref(null)
|
||||
const tableHeight = computed(() => props.tableHeight)
|
||||
|
||||
const { w: tableWidth } = useTableResize()
|
||||
|
||||
const handleSelectionChange = (selection) => {
|
||||
emit('selection-change', selection)
|
||||
}
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
emit('size-change', val)
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
emit('current-change', val)
|
||||
}
|
||||
|
||||
const updateTableHeight = () => {
|
||||
nextTick(() => {
|
||||
if (tableContainer.value) {
|
||||
const containerRect = tableContainer.value.getBoundingClientRect()
|
||||
const windowHeight = window.innerHeight
|
||||
const containerTop = containerRect.top
|
||||
const paginationHeight = props.showPagination ? 32 : 0 // 分页器的大概高度
|
||||
tableHeight.value = windowHeight - containerTop - paginationHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTableHeight()
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateTableHeight)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-table {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
// 暴露 el-table 的方法
|
||||
defineExpose({
|
||||
clearSelection: () => elTableRef.value?.clearSelection(),
|
||||
toggleRowSelection: (row, selected) => elTableRef.value?.toggleRowSelection(row, selected),
|
||||
setCurrentRow: row => elTableRef.value?.setCurrentRow(row),
|
||||
// 可以根据需要暴露更多的方法
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
updateTableHeight()
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateTableHeight)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-table {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</el-dropdown>
|
||||
</div>
|
||||
</el-header>
|
||||
<RouteTab></RouteTab>
|
||||
<!-- <RouteTab></RouteTab> -->
|
||||
<el-container>
|
||||
<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 Aside from './components/Aside.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 { removeToken } from '@/utils/auth'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
|
|
@ -15,6 +15,7 @@ window.$api = $api
|
|||
import { createPinia } from 'pinia'
|
||||
// 重置css
|
||||
import '@/assets/styles/reset.scss'
|
||||
import '@/assets/styles/pagecss.scss'
|
||||
// 使用svg图标组件
|
||||
import 'virtual:svg-icons-register'
|
||||
// 防止Xss攻击的v-html
|
||||
|
|
|
@ -5,7 +5,18 @@
|
|||
* @LastEditTime: 2023-07-07 16:09:00
|
||||
* @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 layout from '@/layout/Layout.vue'
|
||||
import { shallowRef } from 'vue'
|
||||
|
@ -33,16 +44,26 @@ export const asyncRoutes = [
|
|||
path: '/project',
|
||||
name: 'project',
|
||||
authKey: 'Workorder',
|
||||
redirect: '/project/detail',
|
||||
redirect: '/project/list',
|
||||
meta: { title: '项目管理', imgSrc: 'slider/system' },
|
||||
component: shallowRef(layout),
|
||||
children: [
|
||||
{
|
||||
path: '/project/list',
|
||||
name: 'projectList',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '项目列表',
|
||||
},
|
||||
component: () => import('@/views/project/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/project/detail',
|
||||
name: 'userWork',
|
||||
name: 'projectDetail',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '项目详情',
|
||||
menuHide: true, // 添加这个属性来隐藏菜单项
|
||||
},
|
||||
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;
|
||||
flex: 1;
|
||||
box-shadow: 0px 10px 20px 0px #2860b5;
|
||||
background: #3e8bff;
|
||||
background: #fff;
|
||||
border-bottom-left-radius: 40px;
|
||||
background: url('@/assets/imgs/编组 3.png') no-repeat;
|
||||
background-position: center center;
|
||||
|
|
|
@ -1,34 +1,32 @@
|
|||
<template>
|
||||
<div class="project-management">
|
||||
<el-form :model="formData" :disabled="!isEditing" label-width="120px">
|
||||
<el-row>
|
||||
<el-form :model="formData" :rules="rules" label-width="120px" class="custom-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<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-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编号">
|
||||
<el-input v-model="formData.projectCode" class="full-width" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目负责人">
|
||||
<el-input v-model="formData.projectManager" class="full-width" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="预算人天数">
|
||||
<el-input-number v-model="formData.budgetDays" :min="0" class="full-width" />
|
||||
<el-input
|
||||
v-model="formData.projectManager"
|
||||
placeholder="选择项目负责人"
|
||||
readonly
|
||||
@click="openProjectManagerSelect"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目状态">
|
||||
<el-select v-model="formData.projectStatus" placeholder="请选择项目状态" class="full-width">
|
||||
<el-option label="进行中" value="ongoing" />
|
||||
|
@ -37,12 +35,19 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
</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-date-picker v-model="formData.startDate" type="date" placeholder="选择日期" class="full-width" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目结束时间">
|
||||
<el-date-picker v-model="formData.endDate" type="date" placeholder="选择日期" class="full-width" />
|
||||
</el-form-item>
|
||||
|
@ -51,7 +56,7 @@
|
|||
</el-form>
|
||||
|
||||
<div class="table-actions">
|
||||
<el-button type="primary" @click="toggleEdit">{{ isEditing ? '保存' : '编辑' }}</el-button>
|
||||
<el-button type="primary" :icon="Plus" @click="editUser">新增成员</el-button>
|
||||
</div>
|
||||
|
||||
<CustomTable
|
||||
|
@ -60,26 +65,88 @@
|
|||
:total="total"
|
||||
:show-selection="false"
|
||||
:show-index="true"
|
||||
:table-height="400"
|
||||
:table-height="tableHeight"
|
||||
@size-change="handleSizeChange"
|
||||
@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 }">
|
||||
<div class="operation-buttons">
|
||||
<el-button text @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button text type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button text type="primary" @click="showTimesheet(row)">工时表</el-button>
|
||||
<el-button text type="primary" @click="showTimesheet(row)">工作日志</el-button>
|
||||
<el-button text type="danger" @click="confirmDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
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({
|
||||
projectName: '',
|
||||
projectCode: '',
|
||||
|
@ -87,81 +154,178 @@ const formData = reactive({
|
|||
budgetDays: 0,
|
||||
projectStatus: '',
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
endDate: '',
|
||||
})
|
||||
|
||||
const projectOptions = [
|
||||
{ value: 'project1', label: '项目1' },
|
||||
{ value: 'project2', label: '项目2' },
|
||||
{ value: 'project3', label: '项目3' }
|
||||
]
|
||||
const roleOptions = ['开发', '设计', '测试', '产品', '项目经理']
|
||||
|
||||
const columns = [
|
||||
{ prop: 'name', label: '人员姓名' },
|
||||
{ prop: 'role', label: '项目角色' },
|
||||
{ prop: 'workdays', label: '工时天数' },
|
||||
{ prop: 'weight', label: '人员权重' },
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 'auto',
|
||||
{ prop: 'name', label: '人员姓名', slot: 'name' },
|
||||
{ prop: 'role', label: '项目职位', slot: 'role' },
|
||||
{ prop: 'workdays', label: '累计人天数' },
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: '200',
|
||||
fixed: 'right',
|
||||
className: 'operation-column'
|
||||
}
|
||||
className: 'operation-column',
|
||||
},
|
||||
]
|
||||
|
||||
const tableData = ref(Array(22).fill(null).map((_, index) => ({
|
||||
name: `员工${index + 1}`,
|
||||
role: ['开发', '设计', '测试', '产品'][index % 4],
|
||||
workdays: Math.floor(Math.random() * 20) + 10,
|
||||
weight: (Math.random() * 0.5 + 0.5).toFixed(2)
|
||||
})))
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const currentEditingRow = ref(null)
|
||||
const currentSelectedUser = ref([])
|
||||
const projectManagerSelectedUser = ref([])
|
||||
|
||||
const total = ref(tableData.value.length)
|
||||
|
||||
const toggleEdit = () => {
|
||||
isEditing.value = !isEditing.value
|
||||
if (!isEditing.value) {
|
||||
// 这里可以添加保存逻辑
|
||||
console.log('保存表单数据:', formData)
|
||||
const validateDates = (rule, value, callback) => {
|
||||
if (formData.startDate && formData.endDate) {
|
||||
if (new Date(formData.startDate) > new Date(formData.endDate)) {
|
||||
callback(new Error('开始时间不能大于结束时间'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
const handleCurrentChange = val => {
|
||||
console.log('当前页:', val)
|
||||
}
|
||||
|
||||
const handleEdit = (row) => {
|
||||
console.log('编辑行:', row)
|
||||
}
|
||||
|
||||
const handleDelete = (row) => {
|
||||
console.log('删除行:', row)
|
||||
const handleRoleChange = (row) => {
|
||||
}
|
||||
|
||||
const showTimesheet = (row) => {
|
||||
console.log('显示工时表:', row)
|
||||
router.push({
|
||||
name: 'workLog',
|
||||
params: { userId: row.userId },
|
||||
query: { userName: row.name }
|
||||
})
|
||||
}
|
||||
|
||||
// 添加以下代码来调整表格高度
|
||||
const tableHeight = ref(400) // 默认高度
|
||||
const deleteDialogVisible = ref(false)
|
||||
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(() => {
|
||||
updateTableHeight()
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
const projectData = route.params.projectData || route.query.projectData
|
||||
if (projectData) {
|
||||
Object.assign(formData, projectData)
|
||||
} else if (route.query.id) {
|
||||
fetchProjectData(route.query.id)
|
||||
}
|
||||
})
|
||||
|
||||
const updateTableHeight = () => {
|
||||
const windowHeight = window.innerHeight
|
||||
const topOffset = document.querySelector('.project-management').offsetTop
|
||||
const formHeight = document.querySelector('.el-form').offsetHeight
|
||||
const actionsHeight = document.querySelector('.table-actions').offsetHeight
|
||||
const padding = 40 // 上下各20px的内边距
|
||||
tableHeight.value = windowHeight - topOffset - formHeight - actionsHeight - padding
|
||||
const fetchProjectData = async id => {
|
||||
try {
|
||||
const projectData = {
|
||||
id: id,
|
||||
projectName: `项目${id}`,
|
||||
projectCode: `XM00${id}`,
|
||||
projectManager: '张三',
|
||||
budgetDays: 30,
|
||||
projectStatus: 'ongoing',
|
||||
startDate: '2024-08-31',
|
||||
endDate: '2024-09-30',
|
||||
}
|
||||
|
||||
Object.assign(formData, projectData)
|
||||
} catch (error) {
|
||||
console.error('获取项目数据失败', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -169,38 +333,118 @@ const updateTableHeight = () => {
|
|||
.project-management {
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
height: 88vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
.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%;
|
||||
}
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
.custom-form :deep(.el-input__wrapper),
|
||||
.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%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
margin-bottom: 20px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
: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);
|
||||
}
|
||||
|
||||
/* 添加以下样式以使日期选择器宽度一致 */
|
||||
: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>
|
|
@ -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