<script lang="ts" setup>
|
import { onMounted, ref } from 'vue';
|
|
import { IconifyIcon } from '@vben/icons';
|
|
import { getTaskTodoPage } from '#/api/bpm/task';
|
|
defineOptions({ name: 'DashboardTodoPanel' });
|
|
const emit = defineEmits<{
|
navigate: [name: string];
|
}>();
|
|
interface TodoItem {
|
id: string;
|
name: string;
|
createTime?: number;
|
}
|
|
const loading = ref(true);
|
const items = ref<TodoItem[]>([]);
|
|
/** createTime 为毫秒时间戳,格式化成易读文本 */
|
function formatTime(ts?: number) {
|
if (!ts) return '-';
|
const d = new Date(ts);
|
const p = (n: number) => String(n).padStart(2, '0');
|
return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
}
|
|
async function load() {
|
loading.value = true;
|
try {
|
const result = await getTaskTodoPage({ pageNo: 1, pageSize: 6 });
|
items.value = (result.list ?? []).map((t: any) => ({
|
id: String(t.id),
|
name: t.name ?? '未命名任务',
|
createTime: t.createTime,
|
}));
|
} catch {
|
items.value = [];
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
onMounted(load);
|
</script>
|
|
<template>
|
<div class="elite-section h-full">
|
<div class="elite-head">
|
<h3 class="elite-title">
|
<span class="elite-bar"></span>
|
我的待办
|
</h3>
|
<span class="elite-more" @click="emit('navigate', 'BpmTaskTodo')">
|
全部
|
<IconifyIcon class="size-3.5" icon="lucide:chevron-right" />
|
</span>
|
</div>
|
|
<!-- 骨架 -->
|
<div v-if="loading" class="elite-empty">加载中…</div>
|
|
<!-- 空状态 -->
|
<div v-else-if="items.length === 0" class="elite-empty">
|
<IconifyIcon class="mb-2 size-8 opacity-40" icon="lucide:inbox" />
|
<div>暂无待办任务</div>
|
</div>
|
|
<!-- 列表 -->
|
<ul v-else class="elite-list">
|
<li
|
v-for="item in items"
|
:key="item.id"
|
class="elite-row"
|
@click="emit('navigate', 'BpmTaskTodo')"
|
>
|
<span class="elite-dot"></span>
|
<span class="elite-row-name">{{ item.name }}</span>
|
<span class="elite-row-time">{{ formatTime(item.createTime) }}</span>
|
</li>
|
</ul>
|
</div>
|
</template>
|
|
<style scoped>
|
.elite-section {
|
border-radius: 14px;
|
padding: 20px;
|
background: #fdfcfa;
|
border: 1px solid rgba(212, 175, 55, 0.22);
|
box-shadow: 0 2px 14px rgba(15, 36, 64, 0.05);
|
}
|
|
.dark .elite-section {
|
background: #101a2b;
|
border-color: rgba(212, 175, 55, 0.2);
|
box-shadow: 0 2px 14px rgba(0, 0, 0, 0.3);
|
}
|
|
.elite-head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 14px;
|
}
|
|
.elite-title {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
margin: 0;
|
font-size: 15px;
|
font-weight: 600;
|
letter-spacing: 1px;
|
color: #0f2440;
|
}
|
|
.dark .elite-title {
|
color: #f0ead8;
|
}
|
|
.elite-bar {
|
display: inline-block;
|
width: 3px;
|
height: 15px;
|
border-radius: 2px;
|
background: linear-gradient(180deg, #e3c567, #b8901f);
|
}
|
|
.elite-more {
|
display: inline-flex;
|
cursor: pointer;
|
align-items: center;
|
gap: 2px;
|
font-size: 12px;
|
color: #b8901f;
|
transition: color 0.2s ease;
|
}
|
|
.elite-more:hover {
|
color: #8a6a12;
|
}
|
|
.elite-empty {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 34px 0;
|
font-size: 13px;
|
color: #8a94a6;
|
}
|
|
.elite-list {
|
margin: 0;
|
padding: 0;
|
list-style: none;
|
}
|
|
.elite-row {
|
display: flex;
|
cursor: pointer;
|
align-items: center;
|
gap: 10px;
|
padding: 11px 10px;
|
border-radius: 8px;
|
border-bottom: 1px solid rgba(15, 36, 64, 0.05);
|
transition: background-color 0.2s ease;
|
}
|
|
.elite-row:last-child {
|
border-bottom: none;
|
}
|
|
.elite-row:hover {
|
background: rgba(212, 175, 55, 0.07);
|
}
|
|
.elite-dot {
|
width: 6px;
|
height: 6px;
|
flex-shrink: 0;
|
border-radius: 999px;
|
background: #d4af37;
|
}
|
|
.elite-row-name {
|
overflow: hidden;
|
flex: 1;
|
font-size: 13.5px;
|
color: #0f2440;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.dark .elite-row-name {
|
color: #e6e9ef;
|
}
|
|
.elite-row-time {
|
flex-shrink: 0;
|
font-size: 12px;
|
color: #8a94a6;
|
}
|
</style>
|