<script lang="ts" setup>
|
import type { ActionItem } from '@jnpf/ui/vxeTable';
|
|
import { nextTick, reactive } from 'vue';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { useModal } from '@jnpf/ui/modal';
|
import { BasicPopup, usePopupInner } from '@jnpf/ui/popup';
|
import { BasicVxeTable, TableAction, useVxeTable } from '@jnpf/ui/vxeTable';
|
import { buildBitUUID, cloneDeep } from '@jnpf/utils';
|
|
import dayjs from 'dayjs';
|
import isBetween from 'dayjs/plugin/isBetween';
|
|
import { delProject, getTaskList } from '#/api/extend/projectGantt';
|
import { $t } from '#/locales';
|
|
import Form from './TaskForm.vue';
|
|
defineEmits(['register']);
|
|
dayjs.extend(isBetween);
|
|
const state = reactive({
|
managerIds: '',
|
startDate: '',
|
endDate: '',
|
list: [],
|
});
|
const { createMessage } = useMessage();
|
const [registerPopup] = usePopupInner(init);
|
const searchInfo = reactive({
|
projectId: '',
|
});
|
const columnsData = [
|
{ title: '任务名称', dataIndex: 'fullName', width: 200, fixed: 'left' },
|
{ title: '标记', dataIndex: 'signColor', width: 50, align: 'center', fixed: 'left', slots: { default: 'signColor' } },
|
{ title: '开始日期', dataIndex: 'startTime', width: 100, fixed: 'left', slots: { default: 'startTime' } },
|
{ title: '结束日期', dataIndex: 'endTime', width: 100, fixed: 'left', slots: { default: 'endTime' } },
|
{ title: '完成进度', dataIndex: 'schedule', width: 80, fixed: 'left', slots: { default: 'schedule' } },
|
];
|
let columns: any[] = reactive([]);
|
const [registerTable, { reload, expandAll }] = useVxeTable({
|
api: getTaskList,
|
searchInfo,
|
useSearchForm: true,
|
showTableSetting: false,
|
showIndexColumn: false,
|
isTreeTable: true,
|
border: true,
|
formConfig: {
|
baseColProps: { span: 8 },
|
schemas: [
|
{
|
field: 'keyword',
|
label: $t('common.keyword'),
|
component: 'Input',
|
componentProps: {
|
placeholder: $t('common.enterKeyword'),
|
submitOnPressEnter: true,
|
},
|
},
|
],
|
},
|
immediate: false,
|
actionColumn: {
|
width: 100,
|
title: '操作',
|
dataIndex: 'action',
|
},
|
afterFetch: (data) => {
|
state.list = data;
|
return data;
|
},
|
});
|
const [registerForm, { openModal: openFormModal }] = useModal();
|
|
function init(data) {
|
searchInfo.projectId = data.id;
|
state.managerIds = data.managerIds;
|
state.startDate = data.startTime;
|
state.endDate = data.endTime;
|
columns = cloneDeep(columnsData);
|
getColumns();
|
reload();
|
nextTick(() => expandAll());
|
}
|
function getColumns() {
|
const start_date_spilt = dayjs(state.startDate).format('YYYY-M-D').split('-');
|
const end_date_spilt = dayjs(state.endDate).format('YYYY-M-D').split('-');
|
const start_year = Number(start_date_spilt[0]);
|
const start_mouth = Number(start_date_spilt[1]);
|
const end_year = Number(end_date_spilt[0]);
|
const end_mouth = Number(end_date_spilt[1]);
|
const mouth = mouthAndDayTitleDate(start_year, start_mouth, end_year, end_mouth);
|
for (const element of mouth) {
|
const newColumns = { title: element.full_date, dataIndex: `mouth${element.date}`, align: 'center', children: [], slots: { default: 'dateItem' } };
|
if (element.children?.length) {
|
for (let j = 0; j < element.children.length; j++) {
|
const item = element.children[j];
|
(newColumns.children as any[]).push({
|
title: item.name,
|
dataIndex: item.full_date,
|
align: 'center',
|
width: 50,
|
slots: { default: 'dateItem' },
|
});
|
}
|
}
|
columns.push(newColumns);
|
}
|
}
|
function getTableActions(record): ActionItem[] {
|
return [
|
{
|
label: $t('common.editText'),
|
onClick: addOrUpdateHandle.bind(null, record.id),
|
},
|
{
|
label: $t('common.delText'),
|
color: 'error',
|
modelConfirm: {
|
onOk: handleDelete.bind(null, record.id),
|
},
|
},
|
];
|
}
|
function addOrUpdateHandle(id = '') {
|
openFormModal(true, { id, projectId: searchInfo.projectId, managerIds: state.managerIds });
|
}
|
function handleDelete(id) {
|
delProject(id).then((res) => {
|
createMessage.success(res.msg);
|
reload();
|
});
|
}
|
/**
|
* 月-日模式gantt标题
|
* start_year: 起始年
|
* start_mouth:起始月
|
* end_year:结束年
|
* end_mouth:结束月
|
*/
|
function mouthAndDayTitleDate(start_year, start_mouth, end_year, end_mouth) {
|
// 处理年份
|
const year_diff = end_year - start_year;
|
// 只存在同年或前后年的情况
|
if (year_diff === 0) {
|
// 年间隔为同一年
|
const isLeap = isLeapFun(start_year); // 是否闰年
|
const mouths = generationMonths(start_year, start_mouth, end_mouth + 1, isLeap); // 处理月份
|
return mouths;
|
}
|
// 处理开始月份
|
const startIsLeap = isLeapFun(start_year);
|
const start_mouths = generationMonths(start_year, start_mouth, 13, startIsLeap);
|
// 处理结束月份
|
const endIsLeap = isLeapFun(end_year);
|
const end_mouths = generationMonths(end_year, 1, end_mouth + 1, endIsLeap);
|
return [...start_mouths, ...end_mouths];
|
}
|
/**
|
* 是否闰年函数
|
* year: Number 当前年份
|
*/
|
function isLeapFun(year) {
|
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
}
|
/**
|
* 生成月份函数
|
* year: Number 当前年份
|
* start_num: Number 开始月分
|
* end_num:Number 结束月份
|
* isLeap: Boolean 是否闰年
|
* insert_days: Boolean 是否需要插入 日
|
* week: 是否以周的间隔
|
*/
|
function generationMonths(year, start_num = 1, end_num = 13, isLeap = false, insert_days = true, week = false) {
|
const months: any[] = [];
|
if (insert_days) {
|
// 无需 日 的模式
|
for (let i = start_num; i < end_num; i++) {
|
// 需要 日 的模式
|
const days = generationDays(year, i, isLeap, week);
|
months.push({ name: `${i}月`, date: i, full_date: `${year}-${i}`, children: days, id: buildBitUUID() });
|
}
|
return months;
|
}
|
for (let i = start_num; i < end_num; i++) {
|
// 需要 日 的模式
|
months.push({ name: `${i}月`, date: i, full_date: `${year}-${i}`, id: buildBitUUID() });
|
}
|
return months;
|
}
|
/**
|
* 生成日期函数
|
* year: Number 当前年份
|
* month: Number 当前月份
|
* isLeap: Boolean 是否闰年
|
* week: Boolean 是否间隔一周
|
*/
|
function generationDays(year, month, isLeap = false, week = false) {
|
const big_month = [1, 3, 5, 7, 8, 10, 12].includes(month);
|
const small_month = [4, 6, 9, 11].includes(month);
|
const dates_num = big_month ? 32 : small_month ? 31 : isLeap ? 30 : 29;
|
const days: any[] = [];
|
if (week) {
|
let _day = 1; // 从周日开始
|
const _start_day_inweek = timeInWeek(`${year}-${month}-1`);
|
if (_start_day_inweek !== 0) _day = 8 - _start_day_inweek;
|
for (let i = _day; i < dates_num; i += 7) {
|
days.push({ date: i, name: `${i}日`, id: buildBitUUID(), full_date: `${year}-${month}-${i}` });
|
}
|
} else {
|
for (let i = 1; i < dates_num; i++) {
|
days.push({ date: i, name: `${i}日`, id: buildBitUUID(), full_date: `${year}-${month}-${i}` });
|
}
|
}
|
return days;
|
}
|
/**
|
* 当前日期gantt状态
|
* row: object 当前行信息
|
* date: string 当前格子日期
|
* unit: string 时间单位,以天、月、年计算
|
*/
|
function dayGanttType(row, date, unit: any = 'days') {
|
const start_date = row.startTime;
|
const end_date = row.endTime;
|
const between = dayjs(date).isBetween(start_date, end_date, unit);
|
if (between) return 'item-on';
|
const start = dayjs(start_date).isSame(date, unit);
|
const end = dayjs(end_date).isSame(date, unit);
|
if (start && end) return 'item-on item-full';
|
if (start) return 'item-on item-start';
|
if (end) return 'item-on item-end';
|
}
|
/**
|
* 查询时间是周几
|
*/
|
function timeInWeek(date) {
|
return dayjs(date).day();
|
}
|
</script>
|
<template>
|
<BasicPopup v-bind="$attrs" @register="registerPopup" title="项目任务" class="full-popup" destroy-on-close>
|
<BasicVxeTable :columns="columns" @register="registerTable" class="jnpf-sub-table">
|
<template #toolbar>
|
<a-button type="primary" pre-icon="icon-ym icon-ym-btn-add" @click="addOrUpdateHandle()">{{ $t('common.addText') }}</a-button>
|
</template>
|
<template #signColor="{ record }">
|
<i class="icon-ym icon-ym-extend-star" :style="`color:${record.signColor}!important;`" v-if="record.signColor"></i>
|
<i class="icon-ym icon-ym-extend-star-o i-default" v-else></i>
|
</template>
|
<template #startTime="{ record }">
|
{{ dayjs(record.startTime).format('YYYY-MM-DD') }}
|
</template>
|
<template #endTime="{ record }">
|
{{ dayjs(record.endTime).format('YYYY-MM-DD') }}
|
</template>
|
<template #schedule="{ record }">{{ record.schedule }}%</template>
|
<template #action="{ record }">
|
<TableAction v-if="record.parentId != '0'" :actions="getTableActions(record)" />
|
</template>
|
<template #dateItem="{ column, record }">
|
<div :class="dayGanttType(record, column.property)" :style="`background:${record.signColor}!important;`" v-if="record.signColor"></div>
|
<div :class="dayGanttType(record, column.property)" v-else></div>
|
</template>
|
</BasicVxeTable>
|
</BasicPopup>
|
<Form @register="registerForm" @reload="reload" />
|
</template>
|
|
<style lang="scss" scoped>
|
.item-on {
|
position: absolute;
|
top: 50%;
|
right: -1px;
|
left: 0;
|
height: 16px;
|
margin-top: -8px;
|
background: #409eff;
|
transition: all 0.4s;
|
|
&.item-start {
|
left: 50%;
|
}
|
|
&.item-end {
|
right: 50%;
|
}
|
}
|
</style>
|