|
|
@@ -1,7 +1,1333 @@
|
|
|
<template>
|
|
|
- <task-workspace title="任务管理" scene="list" />
|
|
|
+ <div class="frame-body adaption-frame-body task-page">
|
|
|
+ <div class="frame-card">
|
|
|
+ <div class="frame-card-header border-radius" :style="{ minHeight: viewType === 1 ? '110px' : '56px' }">
|
|
|
+ <div class="frame-search">
|
|
|
+ <el-form :inline="true" :model="search" class="demo-form-inline">
|
|
|
+ <div class="search-line">
|
|
|
+ <div class="item2">
|
|
|
+ <el-form-item>
|
|
|
+ <el-input
|
|
|
+ v-model="kws"
|
|
|
+ placeholder="请输入关键字"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="keydownSearch"
|
|
|
+ @blur="keydownSearch"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-select v-model="search.searchType" placeholder="全部状态" clearable @change="onSearch">
|
|
|
+ <el-option v-for="item in searchTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-select
|
|
|
+ v-model="search.status"
|
|
|
+ multiple
|
|
|
+ collapse-tags
|
|
|
+ collapse-tags-tooltip
|
|
|
+ placeholder="全部状态"
|
|
|
+ style="width: 160px"
|
|
|
+ @change="onSearch"
|
|
|
+ >
|
|
|
+ <el-option label="未开始" value="1" />
|
|
|
+ <el-option label="进行中" value="2" />
|
|
|
+ <el-option label="已完成" value="3" />
|
|
|
+ <el-option label="已逾期" value="5" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="viewType === 3">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="dt2"
|
|
|
+ type="daterange"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ start-placeholder="开始时间"
|
|
|
+ end-placeholder="结束时间"
|
|
|
+ :clearable="false"
|
|
|
+ @change="handleChangeTime"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="viewType === 1">
|
|
|
+ <el-date-picker
|
|
|
+ v-if="toggleActive === 'day'"
|
|
|
+ v-model="dt"
|
|
|
+ type="date"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ placeholder="选择日期"
|
|
|
+ :clearable="false"
|
|
|
+ @change="handleChangeTime"
|
|
|
+ />
|
|
|
+ <el-date-picker
|
|
|
+ v-else-if="toggleActive === 'week'"
|
|
|
+ v-model="dt"
|
|
|
+ type="week"
|
|
|
+ format="YYYY 第 WW 周"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ placeholder="选择日期"
|
|
|
+ :clearable="false"
|
|
|
+ @change="handleChangeTime"
|
|
|
+ />
|
|
|
+ <el-date-picker
|
|
|
+ v-else
|
|
|
+ v-model="dt"
|
|
|
+ type="month"
|
|
|
+ value-format="YYYY-MM"
|
|
|
+ placeholder="选择日期"
|
|
|
+ :clearable="false"
|
|
|
+ @change="handleChangeTime"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="reset">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <div class="item3">
|
|
|
+ <el-form-item>
|
|
|
+ <span class="search-right">
|
|
|
+ <el-button
|
|
|
+ circle
|
|
|
+ :type="viewType === 3 ? 'primary' : undefined"
|
|
|
+ style="margin-right: 20px"
|
|
|
+ @click="setViewType(3)"
|
|
|
+ >
|
|
|
+ <el-icon><Tickets /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ circle
|
|
|
+ :type="viewType === 1 ? 'primary' : undefined"
|
|
|
+ style="margin-right: 20px"
|
|
|
+ @click="setViewType(1)"
|
|
|
+ >
|
|
|
+ <el-icon><OfficeBuilding /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" style="margin-right: 20px" @click="delTasks">批量删除</el-button>
|
|
|
+ <el-button type="primary" @click="openDialogForm">新建任务</el-button>
|
|
|
+ </span>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="viewType === 1" class="search-line search-line-toggle">
|
|
|
+ <div class="item1">
|
|
|
+ <el-form-item>
|
|
|
+ <div class="toggle-group">
|
|
|
+ <button
|
|
|
+ v-for="item in toggleData"
|
|
|
+ :key="item.value"
|
|
|
+ type="button"
|
|
|
+ class="toggle-btn"
|
|
|
+ :class="{ 'is-active': toggleActive === item.value }"
|
|
|
+ @click="handleChangeType(item.value)"
|
|
|
+ >
|
|
|
+ {{ item.label }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-if="viewType === 1">
|
|
|
+ <div class="frame-card calendar-card">
|
|
|
+ <div class="frame-card-content">
|
|
|
+ <div v-if="toggleActive === 'day'" class="calendar-day-view">
|
|
|
+ <div class="calendar-header">
|
|
|
+ <div class="column-time">周次{{ dayInfo.week }}</div>
|
|
|
+ <div class="column-content">{{ dayInfo.weekdayLabel }} {{ dayInfo.day }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="calendar-content-list">
|
|
|
+ <div class="calendar-item">
|
|
|
+ <div class="column-time">全天</div>
|
|
|
+ <div class="column-content"></div>
|
|
|
+ </div>
|
|
|
+ <div v-for="slot in daySlots" :key="slot.time" class="calendar-item">
|
|
|
+ <div class="column-time">{{ slot.time }}</div>
|
|
|
+ <div class="column-content">
|
|
|
+ <div v-if="!slot.data.length" class="calendar-empty">暂无任务</div>
|
|
|
+ <div v-for="row in slot.data" :key="row.taskDateId || row.id" class="calendar-task-chip" @click="goDetail(row)">
|
|
|
+ <span class="calendar-task-chip__title" v-html="row.title || row.name"></span>
|
|
|
+ <span class="calendar-task-chip__meta">{{ formatPercent(row.percent) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else-if="toggleActive === 'week'" class="calendar-week-view">
|
|
|
+ <div class="calendar-week-header">
|
|
|
+ <div v-for="item in weekData" :key="item.dayKey" class="column-week">{{ item.day }}({{ item.weekdayLabel }})</div>
|
|
|
+ </div>
|
|
|
+ <div class="calendar-week-body">
|
|
|
+ <div v-for="item in weekData" :key="item.dayKey" class="calendar-week-column">
|
|
|
+ <div v-if="!item.data.length" class="calendar-empty">暂无任务</div>
|
|
|
+ <div v-for="row in item.data" :key="row.taskDateId || row.id" class="summary-card" @click="goDetail(row)">
|
|
|
+ <span class="summary-card__line" :class="`priority-${formatPriority(row.priorityType)}`"></span>
|
|
|
+ <div class="summary-card__title" v-html="row.title || row.name"></div>
|
|
|
+ <div class="summary-card__meta">{{ row.dateName || row.deadline || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else class="calendar-month-view">
|
|
|
+ <div class="calendar-month-header">
|
|
|
+ <div v-for="label in monthWeekLabels" :key="label" class="column-week">{{ label }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="calendar-month-grid">
|
|
|
+ <div v-for="item in monthData" :key="item.dayKey" class="calendar-month-cell">
|
|
|
+ <div class="column-day">
|
|
|
+ <span class="day">{{ item.day }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="column-week month-task-list">
|
|
|
+ <div v-if="!item.data.length" class="calendar-empty">暂无任务</div>
|
|
|
+ <div v-for="row in item.data.slice(0, 3)" :key="row.taskDateId || row.id" class="summary-card summary-card--month" @click="goDetail(row)">
|
|
|
+ <span class="summary-card__line" :class="`priority-${formatPriority(row.priorityType)}`"></span>
|
|
|
+ <div class="summary-card__title" v-html="row.title || row.name"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div v-else class="frame-card-content table-area">
|
|
|
+ <div class="calendar-day h-100">
|
|
|
+ <div class="target_list h-100">
|
|
|
+ <div class="list-header">
|
|
|
+ <div class="name-col">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="checkouts"
|
|
|
+ :disabled="allDisabled"
|
|
|
+ style="padding: 0 10px"
|
|
|
+ :indeterminate="allIndeterminate"
|
|
|
+ @change="checkAllIn"
|
|
|
+ />
|
|
|
+ 任务名称
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="person-col">执行人</div>
|
|
|
+ <div class="progress-col">进度</div>
|
|
|
+ <div class="person-col">状态</div>
|
|
|
+ <div class="time-col">任务时间</div>
|
|
|
+ <div class="person-col">来源</div>
|
|
|
+ <div class="handle-col">操作</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div ref="cardContentRef" class="card-content" v-loading="loading">
|
|
|
+ <div v-for="item in pageData" :key="item.taskDateId || item.id" class="card-box">
|
|
|
+ <div class="card-row-main">
|
|
|
+ <div class="icon-col">
|
|
|
+ <el-icon v-if="item.sonTasks && item.sonTasks.length" class="expand-icon" :class="{ 'is-close': !item.show }" @click="item.show = !item.show">
|
|
|
+ <ArrowDown />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="box-target-col text-overflow">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="item.checks"
|
|
|
+ :disabled="!item.isEditable"
|
|
|
+ :indeterminate="item.indeterminate"
|
|
|
+ style="padding-right: 10px"
|
|
|
+ @change="checkChange(item)"
|
|
|
+ />
|
|
|
+ <p class="title" v-html="item.title || item.name" @click="goDetail(item)"></p>
|
|
|
+ <span v-if="formatTaskLabel(item.taskLabel)" class="task-label">{{ formatTaskLabel(item.taskLabel) }}</span>
|
|
|
+ <span v-for="(tag, idx) in normalizeTags(item.taskTagls)" :key="idx" class="task-tag">{{ tag }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="person-col person-col-flex">
|
|
|
+ <el-avatar :size="28">{{ getAvatarText(item.executeName || item.ownerName) }}</el-avatar>
|
|
|
+ <span class="person-name">{{ item.executeName || item.ownerName || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="progress-col">
|
|
|
+ <div class="progress-click" @click="openDialogTaskProgress(item.taskDateId || item.id)">
|
|
|
+ <el-progress :percentage="toNumber(item.percent)" :stroke-width="12" color="#2d8cf0" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="person-col">
|
|
|
+ <div>{{ formatStatus(item.status, item.statusLabel) }}</div>
|
|
|
+ <div v-if="Number(item.status) === 5 && item.overdueDays" class="overdue-text">{{ `(已逾期${item.overdueDays})` }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="time-col">
|
|
|
+ <span v-if="item.dateType && Number(item.dateType) < 5">{{ item.dateStartDate }}</span>
|
|
|
+ <span v-if="item.dateType && Number(item.dateType) < 5">(</span>
|
|
|
+ {{ item.dateName || item.deadline || '-' }}
|
|
|
+ <span v-if="item.dateType && Number(item.dateType) < 5">)</span>
|
|
|
+ </div>
|
|
|
+ <div class="person-col">{{ item.createName || item.fromName || '-' }}</div>
|
|
|
+ <div class="handle-col handle-col-btn">
|
|
|
+ <button type="button" class="img-btn" title="添加子任务" @click="openDialogForm(item.id)">+</button>
|
|
|
+ <button type="button" class="img-btn look-btn" title="查看详情" @click="goDetail(item)">看</button>
|
|
|
+ <button type="button" class="img-btn del-btn" title="删除任务" @click="del(item.id)">删</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="item.show" class="box-content">
|
|
|
+ <div v-for="task in item.sonTasks || []" :key="task.taskDateId || task.id" class="card-row-sub">
|
|
|
+ <div class="icon-col icon-col-placeholder"></div>
|
|
|
+ <div class="content-target-col text-overflow">
|
|
|
+
|
|
|
+ <el-checkbox
|
|
|
+ v-model="task.checks"
|
|
|
+ :disabled="!task.isEditable"
|
|
|
+ style="padding-right: 10px"
|
|
|
+ @change="checkChangeSon(item, task)"
|
|
|
+ />
|
|
|
+ <p class="title" v-html="task.title || task.name" @click="goDetail(task)"></p>
|
|
|
+ <span v-if="formatTaskLabel(task.taskLabel)" class="task-label">{{ formatTaskLabel(task.taskLabel) }}</span>
|
|
|
+ <span v-for="(tag, idx) in normalizeTags(task.taskTagls)" :key="idx" class="task-tag">{{ tag }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="person-col person-col-flex">
|
|
|
+ <el-avatar :size="28">{{ getAvatarText(task.executeName || task.ownerName) }}</el-avatar>
|
|
|
+ <span class="person-name">{{ task.executeName || task.ownerName || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="progress-col">
|
|
|
+ <div class="progress-click" @click="openDialogTaskProgress(task.taskDateId || task.id)">
|
|
|
+ <el-progress :percentage="toNumber(task.percent)" :stroke-width="12" color="#2d8cf0" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="person-col">
|
|
|
+ <div>{{ formatStatus(task.status, task.statusLabel) }}</div>
|
|
|
+ <div v-if="Number(task.status) === 5 && task.overdueDays" class="overdue-text">{{ `(已逾期${task.overdueDays})` }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="time-col">
|
|
|
+ <span v-if="task.dateType && Number(task.dateType) < 5">{{ task.dateStartDate }}</span>
|
|
|
+ <span v-if="task.dateType && Number(task.dateType) < 5">(</span>
|
|
|
+ {{ task.dateName || task.deadline || '-' }}
|
|
|
+ <span v-if="task.dateType && Number(task.dateType) < 5">)</span>
|
|
|
+ </div>
|
|
|
+ <div class="person-col">{{ task.createName || task.fromName || '-' }}</div>
|
|
|
+ <div class="handle-col handle-col-btn">
|
|
|
+ <button type="button" class="img-btn look-btn" title="查看详情" @click="goDetail(task)">看</button>
|
|
|
+ <button type="button" class="img-btn del-btn" title="删除任务" @click="del(task.id)">删</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-empty v-if="!pageData.length && !loading" description="暂无任务" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="pager-wrap">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pagination.pageNo"
|
|
|
+ v-model:page-size="pagination.pageSize"
|
|
|
+ background
|
|
|
+ :page-sizes="pagination.psizes"
|
|
|
+ :total="pagination.total"
|
|
|
+ layout="total, sizes, prev, pager, next"
|
|
|
+ @size-change="loadListData"
|
|
|
+ @current-change="loadListData"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import TaskWorkspace from '@/modules/otr/task/components/TaskWorkspace.vue'
|
|
|
+import { computed, nextTick, onActivated, onMounted, reactive, ref, watch } from 'vue'
|
|
|
+import moment from 'moment'
|
|
|
+
|
|
|
+import { ArrowDown, OfficeBuilding, Tickets } from '@element-plus/icons-vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
+import { taskListByTable, taskListMineGantt } from '@/api/otr/task/core'
|
|
|
+
|
|
|
+type TaskRow = Record<string, any>
|
|
|
+
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+const loading = ref(false)
|
|
|
+const viewType = ref<number>(Number(localStorage.getItem('viewType') || 3))
|
|
|
+const toggleActive = ref<string>(localStorage.getItem('calendarActive') || 'day')
|
|
|
+const kws = ref('')
|
|
|
+const dt = ref('')
|
|
|
+const dt2 = ref<[string, string] | string[]>([
|
|
|
+ moment().format('YYYY-MM-DD'),
|
|
|
+ moment().endOf('month').format('YYYY-MM-DD'),
|
|
|
+
|
|
|
+])
|
|
|
+const pageData = ref<TaskRow[]>([])
|
|
|
+const checkouts = ref(false)
|
|
|
+const allIndeterminate = ref(false)
|
|
|
+const allDisabled = ref(false)
|
|
|
+const scrollFlag = ref(false)
|
|
|
+const cardContentRef = ref<HTMLElement | null>(null)
|
|
|
+
|
|
|
+const searchTypeOptions = [
|
|
|
+ { label: '全部', value: '' },
|
|
|
+ { label: '我负责的', value: '1' },
|
|
|
+ { label: '我参与的', value: '2' },
|
|
|
+ { label: '我分配的', value: '3' },
|
|
|
+ { label: '分配给我的', value: '4' },
|
|
|
+]
|
|
|
+
|
|
|
+const toggleData = [
|
|
|
+ { label: '日', value: 'day' },
|
|
|
+ { label: '周', value: 'week' },
|
|
|
+ { label: '月', value: 'month' },
|
|
|
+]
|
|
|
+
|
|
|
+const monthWeekLabels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
|
|
+
|
|
|
+const pagination = reactive({
|
|
|
+ total: 0,
|
|
|
+ psizes: [10, 20, 50, 100],
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: 10,
|
|
|
+})
|
|
|
+
|
|
|
+const search = reactive({
|
|
|
+ orderBy: '',
|
|
|
+ sort: '',
|
|
|
+ deptId: '',
|
|
|
+ subCompanyId: '',
|
|
|
+ searchType: '',
|
|
|
+ keyWord: '',
|
|
|
+ endDate: moment().endOf('month').format('YYYY-MM-DD'),
|
|
|
+ startDate: moment().format('YYYY-MM-DD'),
|
|
|
+
|
|
|
+ sta: [] as string[],
|
|
|
+ status: [] as string[],
|
|
|
+ userId: '',
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: 10,
|
|
|
+})
|
|
|
+
|
|
|
+const params = reactive({
|
|
|
+ kws: '',
|
|
|
+ start: '',
|
|
|
+ end: '',
|
|
|
+ deptId: '',
|
|
|
+ subCompanyId: '',
|
|
|
+ status: '',
|
|
|
+ userId: '',
|
|
|
+ searchType: '',
|
|
|
+ scope: 'mine',
|
|
|
+})
|
|
|
+
|
|
|
+const dayInfo = computed(() => {
|
|
|
+ const base = moment(params.start || moment().format('YYYY-MM-DD'))
|
|
|
+
|
|
|
+ const weekLabels = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
|
|
+ return {
|
|
|
+ day: `${base.year()}年${base.format('MM')}月${base.format('DD')}日`,
|
|
|
+ week: String(base.week()),
|
|
|
+ weekdayLabel: weekLabels[base.day()] || '周一',
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const daySlots = computed(() => {
|
|
|
+ const rows = pageData.value
|
|
|
+ return [
|
|
|
+ { time: '上午', data: rows.filter((item) => isMorning(item)) },
|
|
|
+ { time: '下午', data: rows.filter((item) => !isMorning(item)) },
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+const weekData = computed(() => buildWeekData(pageData.value, params.start || moment().format('YYYY-MM-DD')))
|
|
|
+const monthData = computed(() => buildMonthData(pageData.value, params.start || moment().format('YYYY-MM-DD')))
|
|
|
+
|
|
|
+
|
|
|
+watch(viewType, (value) => {
|
|
|
+ localStorage.setItem('viewType', String(value))
|
|
|
+})
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => pageData.value,
|
|
|
+ async () => {
|
|
|
+ await nextTick()
|
|
|
+ if (cardContentRef.value) {
|
|
|
+ scrollFlag.value = cardContentRef.value.scrollWidth > cardContentRef.value.clientWidth
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { deep: true },
|
|
|
+)
|
|
|
+
|
|
|
+function setViewType(value: number) {
|
|
|
+ viewType.value = value
|
|
|
+ if (viewType.value === 1) {
|
|
|
+ initData(toggleActive.value, dt.value || getDateDefault(toggleActive.value))
|
|
|
+ }
|
|
|
+ loadData()
|
|
|
+}
|
|
|
+
|
|
|
+function getDateDefault(type: string) {
|
|
|
+ const now = moment()
|
|
|
+
|
|
|
+ if (type === 'day') return now.format('YYYY-MM-DD')
|
|
|
+ if (type === 'week') return now.startOf('isoWeek').format('YYYY-MM-DD')
|
|
|
+ return now.format('YYYY-MM')
|
|
|
+}
|
|
|
+
|
|
|
+function initData(type: string, value: string) {
|
|
|
+ params.deptId = search.deptId
|
|
|
+ params.subCompanyId = search.subCompanyId
|
|
|
+ params.status = (search.status || []).join(',')
|
|
|
+ params.userId = search.userId
|
|
|
+ params.searchType = search.searchType
|
|
|
+ params.kws = search.keyWord
|
|
|
+
|
|
|
+ if (type === 'day') {
|
|
|
+ dt.value = value
|
|
|
+ params.start = value
|
|
|
+ params.end = value
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (type === 'week') {
|
|
|
+ dt.value = value
|
|
|
+ params.start = value
|
|
|
+ params.end = moment(value).add(6, 'day').format('YYYY-MM-DD')
|
|
|
+
|
|
|
+ return
|
|
|
+ }
|
|
|
+ dt.value = value
|
|
|
+ params.start = `${value}-01`
|
|
|
+ params.end = moment(`${value}-01`).endOf('month').format('YYYY-MM-DD')
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function handleChangeType(value: string) {
|
|
|
+ toggleActive.value = value
|
|
|
+ localStorage.setItem('calendarActive', value)
|
|
|
+ const defaultDate = getDateDefault(value)
|
|
|
+ initData(value, defaultDate)
|
|
|
+ loadData()
|
|
|
+}
|
|
|
+
|
|
|
+function handleChangeTime(value: any) {
|
|
|
+ if (viewType.value === 3) {
|
|
|
+ const range = Array.isArray(value) ? value : []
|
|
|
+ search.startDate = range[0] || moment().format('YYYY-MM-DD')
|
|
|
+ search.endDate = range[1] || moment().endOf('month').format('YYYY-MM-DD')
|
|
|
+
|
|
|
+ loadListData()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof value === 'string') {
|
|
|
+ initData(toggleActive.value, value)
|
|
|
+ }
|
|
|
+ loadCalendarData()
|
|
|
+}
|
|
|
+
|
|
|
+function keydownSearch() {
|
|
|
+ search.keyWord = kws.value
|
|
|
+ params.kws = kws.value
|
|
|
+ if (viewType.value === 1) {
|
|
|
+ initData(toggleActive.value, dt.value || getDateDefault(toggleActive.value))
|
|
|
+ loadCalendarData()
|
|
|
+ } else {
|
|
|
+ loadListData()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onSearch() {
|
|
|
+ search.sta = [...(search.status || [])]
|
|
|
+ if (viewType.value === 1) {
|
|
|
+ initData(toggleActive.value, dt.value || getDateDefault(toggleActive.value))
|
|
|
+ loadCalendarData()
|
|
|
+ } else {
|
|
|
+ loadListData()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function reset() {
|
|
|
+ search.keyWord = ''
|
|
|
+ search.searchType = ''
|
|
|
+ search.status = []
|
|
|
+ search.sta = []
|
|
|
+ search.startDate = moment().format('YYYY-MM-DD')
|
|
|
+ search.endDate = moment().endOf('month').format('YYYY-MM-DD')
|
|
|
+ dt2.value = [moment().format('YYYY-MM-DD'), moment().endOf('month').format('YYYY-MM-DD')]
|
|
|
+
|
|
|
+ dt.value = getDateDefault(toggleActive.value)
|
|
|
+ kws.value = ''
|
|
|
+ if (viewType.value === 1) {
|
|
|
+ initData(toggleActive.value, dt.value)
|
|
|
+ }
|
|
|
+ loadData()
|
|
|
+}
|
|
|
+
|
|
|
+function openDialogForm(parentTaskId?: string | number) {
|
|
|
+ if (parentTaskId) {
|
|
|
+ router.push({ path: '/otr/task/form', query: { ptaskId: String(parentTaskId) } })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ router.push('/otr/task/form')
|
|
|
+}
|
|
|
+
|
|
|
+function goDetail(row: TaskRow) {
|
|
|
+ router.push({ path: '/otr/task/detail', query: { id: String(row.taskDateId || row.id || '') } })
|
|
|
+}
|
|
|
+
|
|
|
+function openDialogTaskProgress(taskId?: string | number) {
|
|
|
+ if (!taskId) return
|
|
|
+ router.push({ path: '/otr/task/detail', query: { id: String(taskId) } })
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeTaskRows(records: TaskRow[]): TaskRow[] {
|
|
|
+ return (records || []).map((item) => ({
|
|
|
+ ...item,
|
|
|
+ title: item.title || item.name || '-',
|
|
|
+ executeName: item.executeName || item.ownerName || item.memberName || '-',
|
|
|
+ createName: item.createName || item.fromName || '-',
|
|
|
+ percent: toNumber(item.percent || item.progress || 0),
|
|
|
+ status: item.status ?? inferStatus(item.statusLabel),
|
|
|
+ dateName: item.dateName || item.deadline || item.createdAt || '-',
|
|
|
+ dateStartDate: item.dateStartDate || item.startDate || item.deadline || item.createdAt || '',
|
|
|
+ show: item.show ?? true,
|
|
|
+ isEditable: item.isEditable ?? true,
|
|
|
+ sonTasks: normalizeTaskRows(item.sonTasks || []),
|
|
|
+ checks: false,
|
|
|
+ indeterminate: false,
|
|
|
+ }))
|
|
|
+}
|
|
|
+
|
|
|
+async function loadListData() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res: any = await taskListByTable({
|
|
|
+ ...search,
|
|
|
+ pageNo: pagination.pageNo,
|
|
|
+ pageSize: pagination.pageSize,
|
|
|
+ sta: search.status,
|
|
|
+ keyWord: search.keyWord,
|
|
|
+ startDate: search.startDate,
|
|
|
+ endDate: search.endDate,
|
|
|
+ })
|
|
|
+ const result = res?.result || {}
|
|
|
+ const records = result.records || result.list || []
|
|
|
+ pageData.value = normalizeTaskRows(records)
|
|
|
+ pagination.total = Number(result.total || pageData.value.length)
|
|
|
+ syncCheckState()
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadCalendarData() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res: any = await taskListMineGantt({
|
|
|
+ page: 1,
|
|
|
+ psize: 1000,
|
|
|
+ start: params.start,
|
|
|
+ end: params.end,
|
|
|
+ kws: params.kws,
|
|
|
+ deptId: params.deptId,
|
|
|
+ subCompanyId: params.subCompanyId,
|
|
|
+ status: params.status,
|
|
|
+ userId: params.userId,
|
|
|
+ searchType: params.searchType,
|
|
|
+ scope: params.scope,
|
|
|
+ })
|
|
|
+ const result = res?.result || {}
|
|
|
+ const records = result.records || result.list || []
|
|
|
+ pageData.value = normalizeTaskRows(records)
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function loadData() {
|
|
|
+ if (viewType.value === 1) {
|
|
|
+ loadCalendarData()
|
|
|
+ } else {
|
|
|
+ loadListData()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function checkAllIn() {
|
|
|
+ allIndeterminate.value = false
|
|
|
+ pageData.value.forEach((item) => {
|
|
|
+ if (item.isEditable) {
|
|
|
+ item.checks = checkouts.value
|
|
|
+ item.indeterminate = false
|
|
|
+ }
|
|
|
+ ;(item.sonTasks || []).forEach((child: TaskRow) => {
|
|
|
+ if (child.isEditable) child.checks = checkouts.value
|
|
|
+ })
|
|
|
+ })
|
|
|
+ syncCheckState()
|
|
|
+}
|
|
|
+
|
|
|
+function checkChange(row: TaskRow) {
|
|
|
+ row.indeterminate = false
|
|
|
+ ;(row.sonTasks || []).forEach((child: TaskRow) => {
|
|
|
+ if (child.isEditable) child.checks = !!row.checks
|
|
|
+ })
|
|
|
+ syncCheckState()
|
|
|
+}
|
|
|
+
|
|
|
+function checkChangeSon(parent: TaskRow, task: TaskRow) {
|
|
|
+ const editableChildren = (parent.sonTasks || []).filter((child: TaskRow) => child.isEditable)
|
|
|
+ const checkedCount = editableChildren.filter((child: TaskRow) => child.checks).length
|
|
|
+ parent.checks = checkedCount > 0 && checkedCount === editableChildren.length
|
|
|
+ parent.indeterminate = checkedCount > 0 && checkedCount < editableChildren.length
|
|
|
+ task.checks = !!task.checks
|
|
|
+ syncCheckState()
|
|
|
+}
|
|
|
+
|
|
|
+function getChooseIds() {
|
|
|
+ const ids: Array<string | number> = []
|
|
|
+ pageData.value.forEach((item) => {
|
|
|
+ if (item.checks) ids.push(item.id)
|
|
|
+ ;(item.sonTasks || []).forEach((child: TaskRow) => {
|
|
|
+ if (child.checks) ids.push(child.id)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ return ids.filter(Boolean)
|
|
|
+}
|
|
|
+
|
|
|
+function syncCheckState() {
|
|
|
+ const editableRows = pageData.value.flatMap((item) => {
|
|
|
+ const rows = item.isEditable ? [item] : []
|
|
|
+ const children = (item.sonTasks || []).filter((child: TaskRow) => child.isEditable)
|
|
|
+ return rows.concat(children)
|
|
|
+ })
|
|
|
+ const checkedRows = editableRows.filter((row) => row.checks)
|
|
|
+ allDisabled.value = editableRows.length === 0
|
|
|
+ checkouts.value = editableRows.length > 0 && checkedRows.length === editableRows.length
|
|
|
+ allIndeterminate.value = checkedRows.length > 0 && checkedRows.length < editableRows.length
|
|
|
+}
|
|
|
+
|
|
|
+function delTasks() {
|
|
|
+ const ids = getChooseIds()
|
|
|
+ if (!ids.length) {
|
|
|
+ ElMessage.warning('请先选择任务')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ ElMessageBox.confirm('确认批量删除已选择任务吗?', '系统提示', {
|
|
|
+ confirmButtonText: '提交',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(() => {
|
|
|
+ pageData.value = pageData.value.filter((item) => !ids.includes(item.id))
|
|
|
+ pageData.value.forEach((item) => {
|
|
|
+ item.sonTasks = (item.sonTasks || []).filter((child: TaskRow) => !ids.includes(child.id))
|
|
|
+ })
|
|
|
+ pagination.total = Math.max(0, pagination.total - ids.length)
|
|
|
+ syncCheckState()
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ }).catch(() => undefined)
|
|
|
+}
|
|
|
+
|
|
|
+function del(id?: string | number) {
|
|
|
+ if (!id) return
|
|
|
+ ElMessageBox.confirm('确认删除该任务吗?', '系统提示', {
|
|
|
+ confirmButtonText: '提交',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(() => {
|
|
|
+ pageData.value = pageData.value.filter((item) => item.id !== id)
|
|
|
+ pageData.value.forEach((item) => {
|
|
|
+ item.sonTasks = (item.sonTasks || []).filter((child: TaskRow) => child.id !== id)
|
|
|
+ })
|
|
|
+ pagination.total = Math.max(0, pagination.total - 1)
|
|
|
+ syncCheckState()
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ }).catch(() => undefined)
|
|
|
+}
|
|
|
+
|
|
|
+function toNumber(value: any) {
|
|
|
+ const raw = String(value ?? '0').replace('%', '')
|
|
|
+ const num = Number(raw)
|
|
|
+ return Number.isNaN(num) ? 0 : Math.max(0, Math.min(100, num))
|
|
|
+}
|
|
|
+
|
|
|
+function formatPercent(value: any) {
|
|
|
+ return `${toNumber(value)}%`
|
|
|
+}
|
|
|
+
|
|
|
+function inferStatus(label?: string) {
|
|
|
+ if (!label) return 1
|
|
|
+ if (label.includes('未开始') || label.includes('未读')) return 1
|
|
|
+ if (label.includes('进行中') || label.includes('待处理') || label.includes('待确认')) return 2
|
|
|
+ if (label.includes('已完成') || label.includes('已发布')) return 3
|
|
|
+ if (label.includes('已逾期')) return 5
|
|
|
+ return 1
|
|
|
+}
|
|
|
+
|
|
|
+function formatStatus(status: any, statusLabel?: string) {
|
|
|
+ if (statusLabel) return statusLabel
|
|
|
+ switch (Number(status)) {
|
|
|
+ case 1:
|
|
|
+ return '未开始'
|
|
|
+ case 2:
|
|
|
+ return '进行中'
|
|
|
+ case 3:
|
|
|
+ case 4:
|
|
|
+ return '已完成'
|
|
|
+ case 5:
|
|
|
+ return '已逾期'
|
|
|
+ default:
|
|
|
+ return '-'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function formatTaskLabel(label: any) {
|
|
|
+ const value = Number(label)
|
|
|
+ if (!value || value === 1) return ''
|
|
|
+ if (value === 2) return '主任务'
|
|
|
+ if (value === 3) return '子任务'
|
|
|
+ return '任务'
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeTags(tags: any) {
|
|
|
+ if (!Array.isArray(tags)) return []
|
|
|
+ return tags.map((item) => item?.name || item?.label || item?.title || String(item)).filter(Boolean)
|
|
|
+}
|
|
|
+
|
|
|
+function getAvatarText(name?: string) {
|
|
|
+ return name ? name.slice(-1) : '人'
|
|
|
+}
|
|
|
+
|
|
|
+function isMorning(item: TaskRow) {
|
|
|
+ const dateText = item.dateStartDate || item.startDate || item.deadline || item.createdAt || item.dateName || ''
|
|
|
+ if (!dateText) return true
|
|
|
+ const hour = moment(dateText).hour()
|
|
|
+
|
|
|
+ if (Number.isNaN(hour)) return true
|
|
|
+ return hour < 12
|
|
|
+}
|
|
|
+
|
|
|
+function formatPriority(priorityType: any) {
|
|
|
+ const num = Number(priorityType)
|
|
|
+ if (num === 1) return 'purple'
|
|
|
+ if (num === 2) return 'blue'
|
|
|
+ if (num === 3) return 'yellow'
|
|
|
+ return 'green'
|
|
|
+}
|
|
|
+
|
|
|
+function buildWeekData(records: TaskRow[], startDate: string) {
|
|
|
+ const start = moment(startDate).startOf('isoWeek')
|
|
|
+
|
|
|
+ return new Array(7).fill(null).map((_, index) => {
|
|
|
+ const current = start.add(index, 'day')
|
|
|
+ const dayKey = current.format('YYYY-MM-DD')
|
|
|
+ return {
|
|
|
+ dayKey,
|
|
|
+ day: current.format('MM.DD'),
|
|
|
+ weekdayLabel: monthWeekLabels[index].replace('周', ''),
|
|
|
+ data: records.filter((item) => matchDay(item, dayKey)),
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function buildMonthData(records: TaskRow[], startDate: string) {
|
|
|
+ const base = moment(startDate).startOf('month')
|
|
|
+
|
|
|
+ const start = base.startOf('isoWeek')
|
|
|
+ return new Array(42).fill(null).map((_, index) => {
|
|
|
+ const current = start.add(index, 'day')
|
|
|
+ const dayKey = current.format('YYYY-MM-DD')
|
|
|
+ return {
|
|
|
+ dayKey,
|
|
|
+ day: current.format('DD'),
|
|
|
+ data: records.filter((item) => matchDay(item, dayKey)),
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function matchDay(item: TaskRow, dayKey: string) {
|
|
|
+ const candidates = [item.dateStartDate, item.startDate, item.deadline, item.createdAt, item.dateName]
|
|
|
+ return candidates.some((value) => {
|
|
|
+ if (!value) return false
|
|
|
+ return moment(value).format('YYYY-MM-DD') === dayKey
|
|
|
+
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ const defaultDate = getDateDefault(toggleActive.value)
|
|
|
+ dt.value = defaultDate
|
|
|
+ initData(toggleActive.value, defaultDate)
|
|
|
+ loadData()
|
|
|
+})
|
|
|
+
|
|
|
+onActivated(() => {
|
|
|
+ loadData()
|
|
|
+})
|
|
|
</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.task-page {
|
|
|
+ .frame-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .frame-card-header {
|
|
|
+ padding: 16px 20px 10px;
|
|
|
+ border-bottom: 1px solid #f0f2f5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .frame-card-content {
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .frame-search {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-line {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ :deep(.el-form-item) {
|
|
|
+ margin-bottom: 0;
|
|
|
+ margin-right: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-select) {
|
|
|
+ width: 140px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-date-editor) {
|
|
|
+ width: 240px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-line-toggle {
|
|
|
+ margin-top: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toggle-group {
|
|
|
+ display: inline-flex;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toggle-btn {
|
|
|
+ min-width: 44px;
|
|
|
+ height: 32px;
|
|
|
+ border: 0;
|
|
|
+ background: #fff;
|
|
|
+ color: #606266;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 0 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toggle-btn + .toggle-btn {
|
|
|
+ border-left: 1px solid #dcdfe6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toggle-btn.is-active {
|
|
|
+ background: #409eff;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-card {
|
|
|
+ margin-top: 0;
|
|
|
+ overflow: auto;
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-day-view,
|
|
|
+ .calendar-week-view,
|
|
|
+ .calendar-month-view {
|
|
|
+ padding: 16px 20px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-header,
|
|
|
+ .calendar-week-header,
|
|
|
+ .calendar-month-header {
|
|
|
+ display: grid;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ background: #f8fafc;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #606266;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-header {
|
|
|
+ grid-template-columns: 120px 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-item {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 120px 1fr;
|
|
|
+ border-left: 1px solid #ebeef5;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ min-height: 88px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .column-time,
|
|
|
+ .column-content,
|
|
|
+ .column-week,
|
|
|
+ .column-day {
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-content-list {
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-empty {
|
|
|
+ color: #b1b7c3;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-task-chip {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px 10px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #f5f8fd;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-task-chip__title {
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-task-chip__meta {
|
|
|
+ color: #409eff;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-week-header,
|
|
|
+ .calendar-week-body {
|
|
|
+ grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-week-header {
|
|
|
+ display: grid;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-week-body {
|
|
|
+ display: grid;
|
|
|
+ border-left: 1px solid #ebeef5;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-week-column {
|
|
|
+ min-height: 220px;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-week-column:last-child {
|
|
|
+ border-right: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-card {
|
|
|
+ position: relative;
|
|
|
+ padding: 10px 12px 10px 16px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-card__line {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ bottom: 0;
|
|
|
+ width: 4px;
|
|
|
+ border-radius: 6px 0 0 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .priority-purple {
|
|
|
+ background: #8b5cf6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .priority-blue {
|
|
|
+ background: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .priority-yellow {
|
|
|
+ background: #e6a23c;
|
|
|
+ }
|
|
|
+
|
|
|
+ .priority-green {
|
|
|
+ background: #67c23a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-card__title {
|
|
|
+ color: #303133;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-card__meta {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-month-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
|
+ border-left: 1px solid #ebeef5;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-month-cell {
|
|
|
+ min-height: 160px;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .calendar-month-cell:nth-child(7n) {
|
|
|
+ border-right: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .column-day {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .day {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+
|
|
|
+ .month-task-list {
|
|
|
+ padding-top: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-card--month {
|
|
|
+ padding-top: 8px;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-area {
|
|
|
+ background: #f8fafc;
|
|
|
+ margin: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .target_list {
|
|
|
+ padding: 0 20px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .list-header,
|
|
|
+ .card-row-main,
|
|
|
+ .card-row-sub {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 32px minmax(320px, 2.4fr) 140px 180px 100px 180px 80px 120px;
|
|
|
+ align-items: center;
|
|
|
+ column-gap: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .list-header {
|
|
|
+ height: 56px;
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name-col,
|
|
|
+ .normal-name-col,
|
|
|
+ .small-name-col,
|
|
|
+ .box-target-col,
|
|
|
+ .content-target-col {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name-col {
|
|
|
+ grid-column: 1 / 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .icon-col {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .icon-col-placeholder {
|
|
|
+ visibility: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .card-content {
|
|
|
+ min-height: 320px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-box {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-row-main,
|
|
|
+ .card-row-sub {
|
|
|
+ min-height: 68px;
|
|
|
+ padding: 0 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-row-main {
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .card-row-sub {
|
|
|
+ border-top: 1px solid #f3f4f6;
|
|
|
+ background: #fbfcfe;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title {
|
|
|
+ margin: 0;
|
|
|
+ color: #303133;
|
|
|
+ cursor: pointer;
|
|
|
+ line-height: 20px;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .text-overflow .title {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ max-width: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .task-label {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 22px;
|
|
|
+ margin-left: 8px;
|
|
|
+ padding: 0 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #409eff;
|
|
|
+ background: #ecf5ff;
|
|
|
+ border-radius: 11px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-tag {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 22px;
|
|
|
+ margin-left: 8px;
|
|
|
+ padding: 0 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #606266;
|
|
|
+ background: #f4f4f5;
|
|
|
+ border-radius: 11px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .person-col-flex {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .person-name {
|
|
|
+ margin-left: 8px;
|
|
|
+ color: #606266;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .progress-click {
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.progress-col .el-progress) {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .overdue-text {
|
|
|
+ color: #e71e19;
|
|
|
+ font-size: 12px;
|
|
|
+ margin-top: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .expand-icon {
|
|
|
+ cursor: pointer;
|
|
|
+ color: #909399;
|
|
|
+ transition: transform 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .expand-icon.is-close {
|
|
|
+ transform: rotate(-90deg);
|
|
|
+ }
|
|
|
+
|
|
|
+ .handle-col-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ justify-content: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .img-btn {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ border: 0;
|
|
|
+ border-radius: 50%;
|
|
|
+ color: #fff;
|
|
|
+ background: #409eff;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .look-btn {
|
|
|
+ background: #67c23a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .del-btn {
|
|
|
+ background: #f56c6c;
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-col,
|
|
|
+ .progress-col,
|
|
|
+ .handle-col,
|
|
|
+ .person-col {
|
|
|
+ min-width: 0;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-col {
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ .pager-wrap {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ margin-top: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @media (max-width: 1280px) {
|
|
|
+ .list-header,
|
|
|
+ .card-row-main,
|
|
|
+ .card-row-sub {
|
|
|
+ grid-template-columns: 28px minmax(240px, 2fr) 120px 150px 88px 160px 72px 108px;
|
|
|
+ column-gap: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|