<script lang="ts" setup>
|
import { onMounted, ref } from 'vue';
|
|
import { IconifyIcon } from '@vben/icons';
|
|
import { getApplicationPage } from '#/api/research/application';
|
|
defineOptions({ name: 'DashboardRecentApplications' });
|
|
const emit = defineEmits<{
|
navigate: [name: string];
|
}>();
|
|
interface Row {
|
id?: number;
|
no?: string;
|
title: string;
|
applicantName: string;
|
status?: number;
|
createTime?: string;
|
}
|
|
/** 状态码 → 展示文案与配色,与 research_application_status 字典保持一致 */
|
const STATUS_MAP: Record<number, { text: string; tone: string }> = {
|
0: { text: '草稿', tone: 'gray' },
|
15: { text: '审批中', tone: 'gold' },
|
20: { text: '已立项', tone: 'green' },
|
35: { text: '已驳回', tone: 'red' },
|
};
|
|
function statusOf(code?: number) {
|
if (code === undefined || code === null) {
|
return { text: '—', tone: 'gray' };
|
}
|
return STATUS_MAP[code] ?? { text: String(code), tone: 'gray' };
|
}
|
|
const loading = ref(true);
|
const rows = ref<Row[]>([]);
|
|
async function load() {
|
loading.value = true;
|
try {
|
const result = await getApplicationPage({ pageNo: 1, pageSize: 6 });
|
rows.value = (result.list ?? []) as Row[];
|
} catch {
|
rows.value = [];
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
onMounted(load);
|
</script>
|
|
<template>
|
<div class="elite-section">
|
<div class="elite-head">
|
<h3 class="elite-title">
|
<span class="elite-bar"></span>
|
最近课题申报
|
</h3>
|
<span class="elite-more" @click="emit('navigate', 'ResearchApplication')">
|
全部
|
<IconifyIcon class="size-3.5" icon="lucide:chevron-right" />
|
</span>
|
</div>
|
|
<div v-if="loading" class="elite-empty">加载中…</div>
|
|
<div v-else-if="rows.length === 0" class="elite-empty">
|
<IconifyIcon class="mb-2 size-8 opacity-40" icon="lucide:file-text" />
|
<div>暂无课题申报记录</div>
|
</div>
|
|
<table v-else class="elite-table">
|
<thead>
|
<tr>
|
<th class="col-title">课题名称</th>
|
<th class="col-applicant">申报人</th>
|
<th class="col-status">状态</th>
|
<th class="col-time">申报时间</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr
|
v-for="row in rows"
|
:key="row.id ?? row.no"
|
@click="emit('navigate', 'ResearchApplication')"
|
>
|
<td class="col-title">
|
<span class="elite-ellipsis">{{ row.title }}</span>
|
</td>
|
<td class="col-applicant">{{ row.applicantName || '—' }}</td>
|
<td class="col-status">
|
<span class="elite-chip" :class="`tone-${statusOf(row.status).tone}`">
|
{{ statusOf(row.status).text }}
|
</span>
|
</td>
|
<td class="col-time">{{ row.createTime || '—' }}</td>
|
</tr>
|
</tbody>
|
</table>
|
</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-table {
|
width: 100%;
|
border-collapse: collapse;
|
table-layout: fixed;
|
}
|
|
.elite-table th {
|
padding: 0 8px 10px;
|
font-size: 12px;
|
font-weight: 500;
|
letter-spacing: 1px;
|
color: #8a94a6;
|
text-align: left;
|
border-bottom: 1px solid rgba(15, 36, 64, 0.08);
|
}
|
|
.elite-table td {
|
padding: 12px 8px;
|
font-size: 13.5px;
|
color: #0f2440;
|
border-bottom: 1px solid rgba(15, 36, 64, 0.05);
|
}
|
|
.dark .elite-table td {
|
color: #e6e9ef;
|
}
|
|
.elite-table tbody tr {
|
cursor: pointer;
|
transition: background-color 0.2s ease;
|
}
|
|
.elite-table tbody tr:hover {
|
background: rgba(212, 175, 55, 0.07);
|
}
|
|
.elite-table tbody tr:last-child td {
|
border-bottom: none;
|
}
|
|
.col-title {
|
width: 44%;
|
}
|
|
.col-applicant {
|
width: 18%;
|
}
|
|
.col-status {
|
width: 16%;
|
}
|
|
.col-time {
|
width: 22%;
|
color: #8a94a6;
|
}
|
|
.elite-ellipsis {
|
display: block;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
/* 状态标签:金/绿/红/灰四档 */
|
.elite-chip {
|
display: inline-block;
|
padding: 2px 10px;
|
font-size: 12px;
|
border-radius: 999px;
|
border: 1px solid transparent;
|
}
|
|
.tone-gold {
|
color: #b8901f;
|
background: rgba(212, 175, 55, 0.1);
|
border-color: rgba(212, 175, 55, 0.35);
|
}
|
|
.tone-green {
|
color: #2f6b4f;
|
background: rgba(47, 107, 79, 0.1);
|
border-color: rgba(47, 107, 79, 0.3);
|
}
|
|
.tone-red {
|
color: #b4453c;
|
background: rgba(180, 69, 60, 0.1);
|
border-color: rgba(180, 69, 60, 0.3);
|
}
|
|
.tone-gray {
|
color: #8a94a6;
|
background: rgba(138, 148, 166, 0.1);
|
border-color: rgba(138, 148, 166, 0.28);
|
}
|
</style>
|