Compare commits

..

2 Commits

5 changed files with 128 additions and 92 deletions

View File

@ -167,7 +167,7 @@ export const systemRoutes: RouteRecordRaw[] = [
component: () => import('@/views/task/task-gantt/TaskGantt.vue'), component: () => import('@/views/task/task-gantt/TaskGantt.vue'),
meta: { meta: {
title: '人力甘特图', title: '人力甘特图',
icon: 'workload', // 进度相关图标 icon: 'eye', // 进度相关图标
hidden: false, hidden: false,
sort: 3.6 sort: 3.6
} }

View File

@ -194,7 +194,7 @@ const initChart = (personIndex: number) => {
const categoryIndex = api.value(3); const categoryIndex = api.value(3);
const startCoord = api.coord([api.value(0), categoryIndex]); const startCoord = api.coord([api.value(0), categoryIndex]);
const endCoord = api.coord([api.value(1), categoryIndex]); const endCoord = api.coord([api.value(1), categoryIndex]);
const barHeight = 20; const barHeight = 40;
return { return {
type: "rect", type: "rect",
shape: { shape: {
@ -220,7 +220,7 @@ const initChart = (personIndex: number) => {
const categoryIndex = api.value(3); const categoryIndex = api.value(3);
const startCoord = api.coord([api.value(0), categoryIndex]); const startCoord = api.coord([api.value(0), categoryIndex]);
const endCoord = api.coord([api.value(1), categoryIndex]); const endCoord = api.coord([api.value(1), categoryIndex]);
const barHeight = 12; const barHeight = 40;
return { return {
type: "rect", type: "rect",
shape: { shape: {

View File

@ -19,7 +19,7 @@
@click="handleStatClick('low')" @click="handleStatClick('low')"
:class="{ 'active': searchForm.priority === 'low' }" :class="{ 'active': searchForm.priority === 'low' }"
> >
<div class="stat-text">低优先级</div> <div class="stat-text">不重要不紧急</div>
<div class="stat-number">{{ priorityStats.low }}</div> <div class="stat-number">{{ priorityStats.low }}</div>
</div> </div>
<div <div
@ -27,7 +27,7 @@
@click="handleStatClick('medium')" @click="handleStatClick('medium')"
:class="{ 'active': searchForm.priority === 'medium' }" :class="{ 'active': searchForm.priority === 'medium' }"
> >
<div class="stat-text">中优先级</div> <div class="stat-text">重要不紧急</div>
<div class="stat-number">{{ priorityStats.medium }}</div> <div class="stat-number">{{ priorityStats.medium }}</div>
</div> </div>
<div <div
@ -35,7 +35,7 @@
@click="handleStatClick('high')" @click="handleStatClick('high')"
:class="{ 'active': searchForm.priority === 'high' }" :class="{ 'active': searchForm.priority === 'high' }"
> >
<div class="stat-text">高优先级</div> <div class="stat-text">紧急不重要</div>
<div class="stat-number">{{ priorityStats.high }}</div> <div class="stat-number">{{ priorityStats.high }}</div>
</div> </div>
<div <div
@ -43,7 +43,7 @@
@click="handleStatClick('urgent')" @click="handleStatClick('urgent')"
:class="{ 'active': searchForm.priority === 'urgent' }" :class="{ 'active': searchForm.priority === 'urgent' }"
> >
<div class="stat-text">紧急</div> <div class="stat-text">重要紧急</div>
<div class="stat-number">{{ priorityStats.urgent }}</div> <div class="stat-number">{{ priorityStats.urgent }}</div>
</div> </div>
</div> </div>
@ -52,10 +52,6 @@
<div class="history-tasks"> <div class="history-tasks">
<div class="table-controls"> <div class="table-controls">
<h3>历史任务</h3> <h3>历史任务</h3>
<!-- <div class="table-actions">
<button class="refresh-btn" @click="resetSearch">刷新</button>
<button class="export-btn">导出</button>
</div> -->
</div> </div>
<!-- 搜索区域 --> <!-- 搜索区域 -->
@ -74,10 +70,10 @@
<label>优先级:</label> <label>优先级:</label>
<select v-model="searchForm.priority" class="search-select"> <select v-model="searchForm.priority" class="search-select">
<option value="">全部</option> <option value="">全部</option>
<option value="low"></option> <option value="low">不重要不紧急</option>
<option value="medium"></option> <option value="medium">重要不紧急</option>
<option value="high"></option> <option value="high">紧急不重要</option>
<option value="urgent">紧急</option> <option value="urgent">重要紧急</option>
</select> </select>
</div> </div>
<div class="search-item"> <div class="search-item">
@ -134,8 +130,9 @@
<td><span class="priority-tag" :class="task.priority">{{ getPriorityText(task.priority) }}</span></td> <td><span class="priority-tag" :class="task.priority">{{ getPriorityText(task.priority) }}</span></td>
<td><span class="task-status" :class="task.status">{{ task.statusText }}</span></td> <td><span class="task-status" :class="task.status">{{ task.statusText }}</span></td>
<td class="operation-btns"> <td class="operation-btns">
<button class="operate-btn">查看</button> <button class="operate-btn" @click="handleView(task)">查看</button>
<button class="operate-btn">编辑</button> <button class="operate-btn" @click="handleEdit(task)">编辑</button>
<button class="operate-btn" @click="handleDelete(task.id)">删除</button>
</td> </td>
</tr> </tr>
<tr v-if="filteredTasks.length === 0"> <tr v-if="filteredTasks.length === 0">
@ -166,6 +163,34 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 查看/编辑任务弹窗居中表单只读/可编辑 -->
<div class="modal-overlay" v-if="showTaskModal" @click.self="showTaskModal = false">
<div class="publish-modal">
<button class="modal-close" @click="showTaskModal = false">
<i class="icon-close"></i>
</button>
<h3>{{ taskModalMode === 'view' ? '查看任务' : '编辑任务' }}</h3>
<div class="modal-content" style="display: block;">
<div style="margin-bottom: 10px;">
<label>任务名称:</label>
<input type="text" v-model="currentTask.title" :disabled="taskModalMode === 'view'" class="search-input">
</div>
<div style="margin-bottom: 10px;">
<label>任务描述:</label>
<textarea v-model="currentTask.description" :disabled="taskModalMode === 'view'" class="search-input" style="width: 100%; height: 80px;"></textarea>
</div>
<div style="margin-bottom: 10px;">
<label>截止日期:</label>
<input type="date" v-model="currentTask.dueDate" :disabled="taskModalMode === 'view'" class="search-input">
</div>
</div>
<div class="modal-actions">
<button class="cancel-btn" @click="showTaskModal = false">关闭</button>
<button v-if="taskModalMode === 'edit'" class="submit-btn" @click="handleSaveTask">保存</button>
</div>
</div>
</div>
</div> </div>
</GiPageLayout> </GiPageLayout>
</template> </template>
@ -173,14 +198,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message, Modal } from '@arco-design/web-vue';
import TaskForm from './components/TaskForm.vue'; import TaskForm from './components/TaskForm.vue';
import AssigneeSelector from './components/AssigneeSelector.vue'; import AssigneeSelector from './components/AssigneeSelector.vue';
const taskFormRef = ref<InstanceType<typeof TaskForm> | null>(null); const taskFormRef = ref<InstanceType<typeof TaskForm> | null>(null);
const assigneeRef = ref<InstanceType<typeof AssigneeSelector> | null>(null); const assigneeRef = ref<InstanceType<typeof AssigneeSelector> | null>(null);
const router = useRouter(); const router = useRouter();
const showPublishModal = ref(false); const showPublishModal = ref(false);
// /
const showTaskModal = ref(false);
const taskModalMode = ref<'view' | 'edit'>('view');
const currentTask = ref<any>({});
// //
const historyTasks = ref([ const historyTasks = ref([
{ {
@ -234,62 +265,39 @@ const searchForm = ref({
// //
const filteredTasks = computed(() => { const filteredTasks = computed(() => {
return historyTasks.value.filter(task => { return historyTasks.value.filter(task => {
// if (searchForm.value.name && !task.title.includes(searchForm.value.name)) return false;
if (searchForm.value.name && !task.title.includes(searchForm.value.name)) { if (searchForm.value.priority && task.priority !== searchForm.value.priority) return false;
return false; if (searchForm.value.publisher && task.publisher !== searchForm.value.publisher) return false;
} if (searchForm.value.assignee && task.assignee !== searchForm.value.assignee) return false;
// if (searchForm.value.startDate && task.publishDate < searchForm.value.startDate) return false;
if (searchForm.value.priority && task.priority !== searchForm.value.priority) { if (searchForm.value.endDate && task.publishDate > searchForm.value.endDate) return false;
return false;
}
//
if (searchForm.value.publisher && task.publisher !== searchForm.value.publisher) {
return false;
}
//
if (searchForm.value.assignee && task.assignee !== searchForm.value.assignee) {
return false;
}
//
if (searchForm.value.startDate && task.publishDate < searchForm.value.startDate) {
return false;
}
//
if (searchForm.value.endDate && task.publishDate > searchForm.value.endDate) {
return false;
}
return true; return true;
}); });
}); });
// //
const handleStatClick = (priority: string) => { const handleStatClick = (priority: string) => {
//
if (searchForm.value.priority === priority) { if (searchForm.value.priority === priority) {
searchForm.value.priority = ''; searchForm.value.priority = '';
} else { } else {
//
searchForm.value.priority = priority; searchForm.value.priority = priority;
} }
//
handleSearch(); handleSearch();
}; };
// // /
const allPublishers = computed(() => { const allPublishers = computed(() => {
const publishers = new Set<string>(); const publishers = new Set<string>();
historyTasks.value.forEach(task => publishers.add(task.publisher)); historyTasks.value.forEach(task => publishers.add(task.publisher));
return Array.from(publishers); return Array.from(publishers);
}); });
//
const allAssignees = computed(() => { const allAssignees = computed(() => {
const assignees = new Set<string>(); const assignees = new Set<string>();
historyTasks.value.forEach(task => assignees.add(task.assignee)); historyTasks.value.forEach(task => assignees.add(task.assignee));
return Array.from(assignees); return Array.from(assignees);
}); });
// //
const priorityStats = computed(() => { const priorityStats = computed(() => {
return { return {
low: historyTasks.value.filter(task => task.priority === 'low').length, low: historyTasks.value.filter(task => task.priority === 'low').length,
@ -299,25 +307,22 @@ const priorityStats = computed(() => {
}; };
}); });
// //
const getPriorityText = (priority: string) => { const getPriorityText = (priority: string) => {
const map: Record<string, string> = { const map: Record<string, string> = {
low: '', low: '不重要不紧急',
medium: '', medium: '重要不紧急',
high: '', high: '紧急不重要',
urgent: '紧急' urgent: '重要紧急'
}; };
return map[priority] || ''; return map[priority] || '';
}; };
// //
const handleSearch = () => { const handleSearch = () => {
console.log('搜索条件:', searchForm.value); console.log('搜索条件:', searchForm.value);
//
document.querySelector('.task-table')?.scrollIntoView({ behavior: 'smooth' }); document.querySelector('.task-table')?.scrollIntoView({ behavior: 'smooth' });
}; };
//
const resetSearch = () => { const resetSearch = () => {
searchForm.value = { searchForm.value = {
name: '', name: '',
@ -329,6 +334,7 @@ const resetSearch = () => {
}; };
}; };
//
const handleSubmit = () => { const handleSubmit = () => {
if (!taskFormRef.value?.form.taskName) { if (!taskFormRef.value?.form.taskName) {
Message.error('请填写任务名称'); Message.error('请填写任务名称');
@ -365,6 +371,53 @@ const handleSubmit = () => {
Message.success('任务发布成功!'); Message.success('任务发布成功!');
showPublishModal.value = false; showPublishModal.value = false;
}; };
//
const handleView = (task: any) => {
currentTask.value = { ...task };
taskModalMode.value = 'view';
showTaskModal.value = true;
};
//
const handleEdit = (task: any) => {
currentTask.value = { ...task };
taskModalMode.value = 'edit';
showTaskModal.value = true;
};
// 使 Arco Modal.confirm
const handleDelete = (taskId: string) => {
Modal.confirm({
title: '确认删除',
content: '删除后将无法恢复,是否确认删除该任务?',
okText: '确认删除',
cancelText: '取消',
closable: true,
maskClosable: true,
onOk: () => {
historyTasks.value = historyTasks.value.filter(t => t.id !== taskId);
Message.success('任务已删除');
},
onCancel: () => {
Message.info('已取消删除');
}
});
};
//
const handleSaveTask = () => {
if (!currentTask.value.title) {
Message.error('任务名称不能为空');
return;
}
const index = historyTasks.value.findIndex(t => t.id === currentTask.value.id);
if (index !== -1) {
historyTasks.value[index] = { ...currentTask.value };
Message.success('任务更新成功');
showTaskModal.value = false;
}
};
</script> </script>
<style scoped> <style scoped>
@ -585,24 +638,6 @@ h3 {
margin: 0; margin: 0;
} }
/* .table-actions {
display: flex;
gap: 8px;
}
.refresh-btn, .export-btn {
padding: 4px 10px;
background-color: #fff;
border: 1px solid #d1d5db;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
}
.refresh-btn:hover, .export-btn:hover {
background-color: #f3f4f6;
} */
.task-table { .task-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@ -636,26 +671,29 @@ h3 {
padding: 2px 6px; padding: 2px 6px;
font-size: 12px; font-size: 12px;
border-radius: 2px; border-radius: 2px;
font-weight: 500;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
color: #fff;
} }
.priority-tag.low { .priority-tag.low {
background-color: #e2e8f0; background-color: #4ade80;
color: #475569; background-image: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
} }
.priority-tag.medium { .priority-tag.medium {
background-color: #dbeafe; background-color: #3b82f6;
color: #1e40af; background-image: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
} }
.priority-tag.high { .priority-tag.high {
background-color: #fef3c7; background-color: #f59e0b;
color: #92400e; background-image: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
} }
.priority-tag.urgent { .priority-tag.urgent {
background-color: #fee2e2; background-color: #ef4444;
color: #b91c1c; background-image: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
} }
/* 状态标签样式 */ /* 状态标签样式 */
@ -788,4 +826,4 @@ h3 {
.submit-btn:hover { .submit-btn:hover {
background-color: #0663f8; background-color: #0663f8;
} }
</style> </style>

View File

@ -33,9 +33,7 @@
{{ user.name }} {{ user.name }}
</option> </option>
</select> </select>
</div> </div>
</div> </div>
</template> </template>

View File

@ -60,10 +60,10 @@
<div class="form-group"> <div class="form-group">
<label for="priority">优先级</label> <label for="priority">优先级</label>
<select id="priority" v-model="form.priority"> <select id="priority" v-model="form.priority">
<option value="low"></option> <option value="low">不重要不紧急</option>
<option value="medium" selected></option> <option value="medium" selected>重要不紧急</option>
<option value="high"></option> <option value="high">紧急不重要</option>
<option value="urgent">紧急</option> <option value="urgent">重要紧急</option>
</select> </select>
</div> </div>
</div> </div>