refactor(ui): introduce shared list detail shells
This commit is contained in:
@@ -10,66 +10,43 @@
|
||||
@request-deleted="handleDetailDeleted"
|
||||
/>
|
||||
|
||||
<article v-else class="approval-list panel">
|
||||
<nav class="status-tabs" aria-label="审批状态">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab"
|
||||
type="button"
|
||||
:class="{ active: activeTab === tab }"
|
||||
@click="activeTab = tab"
|
||||
>
|
||||
{{ tab }}
|
||||
<EnterpriseListPage
|
||||
v-else
|
||||
v-model:active-tab="activeTab"
|
||||
class="approval-list"
|
||||
:tabs="tabs"
|
||||
tabs-label="审批状态"
|
||||
:loading="loading"
|
||||
:error="error"
|
||||
:empty="showEmpty"
|
||||
:empty-state="approvalEmptyState"
|
||||
loading-title="审批待办同步中"
|
||||
loading-message="正在加载当前可见的待审批报销单据"
|
||||
loading-icon="mdi mdi-clipboard-check-outline"
|
||||
error-title="审批列表加载失败"
|
||||
:show-pagination="false"
|
||||
@retry="reload"
|
||||
@empty-action="handleEmptyAction"
|
||||
>
|
||||
<template #filters>
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input v-model="listKeyword" type="search" placeholder="搜索单号、申请人、部门、报销类型..." />
|
||||
</div>
|
||||
|
||||
<button v-for="filter in filters" :key="filter" type="button" class="filter-btn">
|
||||
<span>{{ filter }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="filter-set">
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input v-model="listKeyword" type="search" placeholder="搜索单号、申请人、部门、报销类型..." />
|
||||
</div>
|
||||
<template #hint>
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
点击单据行查看审批详情
|
||||
</template>
|
||||
|
||||
<button v-for="filter in filters" :key="filter" type="button" class="filter-btn">
|
||||
<span>{{ filter }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="hint"><i class="mdi mdi-information-outline"></i> 点击单据行查看审批详情</p>
|
||||
|
||||
<div class="table-wrap" :class="{ 'is-empty': showEmpty }">
|
||||
<div v-if="loading" class="table-state">
|
||||
<TableLoadingState
|
||||
title="审批待办同步中"
|
||||
message="正在加载当前可见的待审报销单据"
|
||||
icon="mdi mdi-clipboard-check-outline"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="table-state error">
|
||||
<i class="mdi mdi-alert-circle-outline"></i>
|
||||
<strong>审批列表加载失败</strong>
|
||||
<p>{{ error }}</p>
|
||||
<button class="state-action" type="button" @click="reload">重新加载</button>
|
||||
</div>
|
||||
|
||||
<TableEmptyState
|
||||
v-else-if="showEmpty"
|
||||
:eyebrow="approvalEmptyState.eyebrow"
|
||||
:title="approvalEmptyState.title"
|
||||
:description="approvalEmptyState.desc"
|
||||
:icon="approvalEmptyState.icon"
|
||||
:action-label="approvalEmptyState.actionLabel"
|
||||
:action-icon="approvalEmptyState.actionIcon"
|
||||
:tone="approvalEmptyState.tone"
|
||||
:art-label="approvalEmptyState.artLabel"
|
||||
:tips="approvalEmptyState.tips"
|
||||
@action="handleEmptyAction"
|
||||
/>
|
||||
|
||||
<table v-else>
|
||||
<template #table>
|
||||
<table>
|
||||
<colgroup>
|
||||
<col><col><col><col><col><col><col><col><col><col><col>
|
||||
</colgroup>
|
||||
@@ -113,8 +90,8 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</EnterpriseListPage>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,91 +9,68 @@
|
||||
@request-deleted="reload"
|
||||
/>
|
||||
|
||||
<article v-else class="approval-list panel">
|
||||
<nav class="status-tabs" aria-label="归档分类">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab"
|
||||
type="button"
|
||||
:class="{ active: activeTab === tab }"
|
||||
@click="activeTab = tab"
|
||||
<EnterpriseListPage
|
||||
v-else
|
||||
v-model:active-tab="activeTab"
|
||||
class="approval-list"
|
||||
:tabs="tabs"
|
||||
tabs-label="归档分类"
|
||||
:loading="loading"
|
||||
:error="error"
|
||||
:empty="showEmpty"
|
||||
:empty-state="archiveEmptyState"
|
||||
loading-title="归档数据同步中"
|
||||
loading-message="正在加载公司已归档的报销单据"
|
||||
loading-icon="mdi mdi-archive-check-outline"
|
||||
error-title="归档列表加载失败"
|
||||
:show-pagination="false"
|
||||
@retry="reload"
|
||||
@empty-action="handleEmptyAction"
|
||||
>
|
||||
<template #filters>
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input v-model="listKeyword" type="search" placeholder="搜索单号、申请人、部门、归档类型..." />
|
||||
</div>
|
||||
|
||||
<el-dropdown
|
||||
v-for="menu in filterMenus"
|
||||
:key="menu.key"
|
||||
class="archive-filter-control"
|
||||
trigger="click"
|
||||
placement="bottom-start"
|
||||
popper-class="archive-filter-menu"
|
||||
@command="selectFilterValue(menu.key, $event)"
|
||||
>
|
||||
{{ tab }}
|
||||
</button>
|
||||
</nav>
|
||||
<button class="filter-btn archive-filter-trigger" type="button">
|
||||
<span>{{ menu.label }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="option in menu.options"
|
||||
:key="`${menu.key}-${option.value}`"
|
||||
:command="option.value"
|
||||
:class="{ 'is-active': menu.activeValue === option.value }"
|
||||
class="archive-filter-option"
|
||||
:aria-current="menu.activeValue === option.value ? 'true' : undefined"
|
||||
>
|
||||
<i v-if="menu.activeValue === option.value" class="mdi mdi-check"></i>
|
||||
<span>{{ option.label }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="filter-set">
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input v-model="listKeyword" type="search" placeholder="搜索单号、申请人、部门、归档类型..." />
|
||||
</div>
|
||||
<template #hint>
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
归档中心保存公司已付款或已归档的报销数据,点击单据行查看详情
|
||||
</template>
|
||||
|
||||
<el-dropdown
|
||||
v-for="menu in filterMenus"
|
||||
:key="menu.key"
|
||||
class="archive-filter-control"
|
||||
trigger="click"
|
||||
placement="bottom-start"
|
||||
popper-class="archive-filter-menu"
|
||||
@command="selectFilterValue(menu.key, $event)"
|
||||
>
|
||||
<button class="filter-btn archive-filter-trigger" type="button">
|
||||
<span>{{ menu.label }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="option in menu.options"
|
||||
:key="`${menu.key}-${option.value}`"
|
||||
:command="option.value"
|
||||
:class="{ 'is-active': menu.activeValue === option.value }"
|
||||
class="archive-filter-option"
|
||||
:aria-current="menu.activeValue === option.value ? 'true' : undefined"
|
||||
>
|
||||
<i v-if="menu.activeValue === option.value" class="mdi mdi-check"></i>
|
||||
<span>{{ option.label }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="hint"><i class="mdi mdi-information-outline"></i> 归档中心保存公司已付款或已归档的报销数据,点击单据行查看详情</p>
|
||||
|
||||
<div class="table-wrap" :class="{ 'is-empty': showEmpty }">
|
||||
<div v-if="loading" class="table-state">
|
||||
<TableLoadingState
|
||||
title="归档数据同步中"
|
||||
message="正在加载公司已归档的报销单据"
|
||||
icon="mdi mdi-archive-check-outline"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="table-state error">
|
||||
<i class="mdi mdi-alert-circle-outline"></i>
|
||||
<strong>归档列表加载失败</strong>
|
||||
<p>{{ error }}</p>
|
||||
<button class="state-action" type="button" @click="reload">重新加载</button>
|
||||
</div>
|
||||
|
||||
<TableEmptyState
|
||||
v-else-if="showEmpty"
|
||||
:eyebrow="archiveEmptyState.eyebrow"
|
||||
:title="archiveEmptyState.title"
|
||||
:description="archiveEmptyState.desc"
|
||||
:icon="archiveEmptyState.icon"
|
||||
:action-label="archiveEmptyState.actionLabel"
|
||||
:action-icon="archiveEmptyState.actionIcon"
|
||||
:tone="archiveEmptyState.tone"
|
||||
:art-label="archiveEmptyState.artLabel"
|
||||
:tips="archiveEmptyState.tips"
|
||||
@action="handleEmptyAction"
|
||||
/>
|
||||
|
||||
<table v-else>
|
||||
<template #table>
|
||||
<table>
|
||||
<colgroup>
|
||||
<col><col><col><col><col><col><col><col><col>
|
||||
</colgroup>
|
||||
@@ -129,8 +106,8 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</EnterpriseListPage>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,112 +6,124 @@
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<article class="system-logs-list panel">
|
||||
<div class="document-toolbar">
|
||||
<div class="filter-set">
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input
|
||||
v-model.trim="systemSearchKeyword"
|
||||
type="search"
|
||||
placeholder="搜索摘要、模块、请求路径或 Request ID"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="document-filter status-dropdown-filter" :class="{ open: openFilterKey === 'level' }">
|
||||
<button
|
||||
class="filter-btn status-filter-trigger"
|
||||
type="button"
|
||||
:aria-expanded="openFilterKey === 'level'"
|
||||
@click="toggleFilter('level')"
|
||||
>
|
||||
<i class="mdi mdi-filter-variant"></i>
|
||||
<span>{{ systemLevelFilterLabel }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<div
|
||||
v-if="openFilterKey === 'level'"
|
||||
class="document-filter-menu status-filter-menu"
|
||||
role="listbox"
|
||||
aria-label="日志级别"
|
||||
>
|
||||
<button
|
||||
v-for="option in systemLevelFilterOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
role="option"
|
||||
:aria-selected="systemLevelFilter === option.value"
|
||||
:class="{ active: systemLevelFilter === option.value }"
|
||||
@click="selectLevelFilter(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document-filter" :class="{ open: openFilterKey === 'eventType' }">
|
||||
<button
|
||||
class="filter-btn"
|
||||
type="button"
|
||||
:aria-expanded="openFilterKey === 'eventType'"
|
||||
@click="toggleFilter('eventType')"
|
||||
>
|
||||
<span>{{ systemEventTypeFilterLabel }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<div
|
||||
v-if="openFilterKey === 'eventType'"
|
||||
class="document-filter-menu"
|
||||
role="listbox"
|
||||
aria-label="事件类型"
|
||||
>
|
||||
<button
|
||||
v-for="option in systemEventTypeFilterOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
role="option"
|
||||
:aria-selected="systemEventTypeFilter === option.value"
|
||||
:class="{ active: systemEventTypeFilter === option.value }"
|
||||
@click="selectEventTypeFilter(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document-actions">
|
||||
<button v-if="hasActiveFilters" class="create-request-btn secondary" type="button" @click="resetFilters">
|
||||
<i class="mdi mdi-filter-remove-outline"></i>
|
||||
<span>清空筛选</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="create-request-btn"
|
||||
:disabled="systemLogLoading"
|
||||
@click="loadSystemLogs(true)"
|
||||
>
|
||||
<i class="mdi mdi-refresh"></i>
|
||||
<span>{{ systemLogLoading ? '刷新中...' : '刷新日志' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
点击任意行可查看系统日志详情
|
||||
</p>
|
||||
|
||||
<div class="table-wrap" :class="{ 'is-empty': !systemLogLoading && !filteredSystemLogEntries.length }">
|
||||
<div v-if="systemLogLoading && !visibleSystemLogEntries.length" class="table-state">
|
||||
<TableLoadingState
|
||||
title="系统日志同步中"
|
||||
message="正在加载系统运行日志记录"
|
||||
icon="mdi mdi-text-box-search-outline"
|
||||
<EnterpriseListPage
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
class="system-logs-list"
|
||||
:loading="systemLogLoading && !visibleSystemLogEntries.length"
|
||||
:empty="!systemLogLoading && !visibleSystemLogEntries.length"
|
||||
:total="totalCount"
|
||||
:total-pages="totalPages"
|
||||
:pages="visiblePageItems"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:summary="`共 ${totalCount} 条系统日志,当前第 ${currentPage} 页`"
|
||||
:show-pagination="!systemLogLoading && filteredSystemLogEntries.length > 0"
|
||||
loading-title="系统日志同步中"
|
||||
loading-message="正在加载系统运行日志记录"
|
||||
loading-icon="mdi mdi-text-box-search-outline"
|
||||
@page-size-change="changePageSize"
|
||||
>
|
||||
<template #filters>
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input
|
||||
v-model.trim="systemSearchKeyword"
|
||||
type="search"
|
||||
placeholder="搜索摘要、模块、请求路径或 Request ID"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<table v-else-if="visibleSystemLogEntries.length" class="system-log-table">
|
||||
<div class="document-filter status-dropdown-filter" :class="{ open: openFilterKey === 'level' }">
|
||||
<button
|
||||
class="filter-btn status-filter-trigger"
|
||||
type="button"
|
||||
:aria-expanded="openFilterKey === 'level'"
|
||||
@click="toggleFilter('level')"
|
||||
>
|
||||
<i class="mdi mdi-filter-variant"></i>
|
||||
<span>{{ systemLevelFilterLabel }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<div
|
||||
v-if="openFilterKey === 'level'"
|
||||
class="document-filter-menu status-filter-menu"
|
||||
role="listbox"
|
||||
aria-label="日志级别"
|
||||
>
|
||||
<button
|
||||
v-for="option in systemLevelFilterOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
role="option"
|
||||
:aria-selected="systemLevelFilter === option.value"
|
||||
:class="{ active: systemLevelFilter === option.value }"
|
||||
@click="selectLevelFilter(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document-filter" :class="{ open: openFilterKey === 'eventType' }">
|
||||
<button
|
||||
class="filter-btn"
|
||||
type="button"
|
||||
:aria-expanded="openFilterKey === 'eventType'"
|
||||
@click="toggleFilter('eventType')"
|
||||
>
|
||||
<span>{{ systemEventTypeFilterLabel }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<div
|
||||
v-if="openFilterKey === 'eventType'"
|
||||
class="document-filter-menu"
|
||||
role="listbox"
|
||||
aria-label="事件类型"
|
||||
>
|
||||
<button
|
||||
v-for="option in systemEventTypeFilterOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
role="option"
|
||||
:aria-selected="systemEventTypeFilter === option.value"
|
||||
:class="{ active: systemEventTypeFilter === option.value }"
|
||||
@click="selectEventTypeFilter(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<button v-if="hasActiveFilters" class="create-request-btn secondary" type="button" @click="resetFilters">
|
||||
<i class="mdi mdi-filter-remove-outline"></i>
|
||||
<span>清空筛选</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="create-request-btn"
|
||||
:disabled="systemLogLoading"
|
||||
@click="loadSystemLogs(true)"
|
||||
>
|
||||
<i class="mdi mdi-refresh"></i>
|
||||
<span>{{ systemLogLoading ? '刷新中...' : '刷新日志' }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template #hint>
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
点击任意行可查看系统日志详情
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<div class="inline-empty">
|
||||
当前筛选条件下没有系统日志记录。
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #table>
|
||||
<table class="system-log-table">
|
||||
<colgroup>
|
||||
<col class="col-time">
|
||||
<col class="col-level">
|
||||
@@ -155,48 +167,12 @@
|
||||
<strong>{{ entry.summary || entry.message }}</strong>
|
||||
<span>{{ formatSummary(entry.message) }}</span>
|
||||
</td>
|
||||
<td class="trace-cell">{{ entry.request_id || '—' }}</td>
|
||||
<td class="trace-cell">{{ entry.request_id || '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-else class="inline-empty">
|
||||
当前筛选条件下没有系统日志记录。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer v-if="!systemLogLoading && filteredSystemLogEntries.length" class="list-foot">
|
||||
<span class="page-summary">共 {{ totalCount }} 条系统日志,目前第 {{ currentPage }} 页</span>
|
||||
<div class="pager" aria-label="分页">
|
||||
<button class="page-nav" type="button" :disabled="currentPage === 1" aria-label="上一页" @click="currentPage--">
|
||||
<i class="mdi mdi-chevron-left"></i>
|
||||
</button>
|
||||
<template v-for="page in visiblePageItems" :key="page === 'ellipsis' ? 'ellipsis' : page">
|
||||
<span v-if="page === 'ellipsis'" class="page-ellipsis" aria-hidden="true">...</span>
|
||||
<button
|
||||
v-else
|
||||
class="page-number"
|
||||
:class="{ active: currentPage === page }"
|
||||
type="button"
|
||||
:aria-current="currentPage === page ? 'page' : undefined"
|
||||
@click="currentPage = page"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
</template>
|
||||
<button class="page-nav" type="button" :disabled="currentPage === totalPages" aria-label="下一页" @click="currentPage++">
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<EnterpriseSelect
|
||||
v-model="pageSize"
|
||||
class="page-size-select"
|
||||
:options="pageSizeOptions"
|
||||
size="small"
|
||||
@change="changePageSize"
|
||||
/>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
</EnterpriseListPage>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -1,96 +1,81 @@
|
||||
<template>
|
||||
<section class="travel-page">
|
||||
<article class="travel-list panel">
|
||||
<nav class="status-tabs" aria-label="个人报销状态">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab"
|
||||
type="button"
|
||||
:class="{ active: activeTab === tab }"
|
||||
@click="activeTab = tab"
|
||||
>
|
||||
{{ tab }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="filter-set">
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input v-model="listKeyword" type="search" placeholder="搜索单号、事由、报销类型..." />
|
||||
</div>
|
||||
|
||||
<div class="date-range-filter" :class="{ open: datePopover }">
|
||||
<button class="filter-btn date-range-trigger" type="button" @click="datePopover = !datePopover">
|
||||
<span class="date-range-label">{{ dateRangeLabel }}</span>
|
||||
<i class="mdi mdi-calendar"></i>
|
||||
</button>
|
||||
<div v-if="datePopover" class="date-range-popover" role="dialog" aria-label="选择时间段">
|
||||
<header>
|
||||
<strong>选择时间段</strong>
|
||||
<button type="button" aria-label="关闭" @click="datePopover = false"><i class="mdi mdi-close"></i></button>
|
||||
</header>
|
||||
<div class="date-range-fields">
|
||||
<label>
|
||||
<span>开始日期</span>
|
||||
<input v-model="rangeStart" type="date" />
|
||||
</label>
|
||||
<label>
|
||||
<span>结束日期</span>
|
||||
<input v-model="rangeEnd" type="date" />
|
||||
</label>
|
||||
</div>
|
||||
<footer>
|
||||
<button class="ghost-btn" type="button" @click="datePopover = false">取消</button>
|
||||
<button class="apply-btn" type="button" :disabled="!rangeStart || !rangeEnd" @click="applyDateRange">应用</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button v-for="filter in filters" :key="filter" type="button" class="filter-btn">
|
||||
<span>{{ filter }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<EnterpriseListPage
|
||||
v-model:active-tab="activeTab"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
class="travel-list"
|
||||
:tabs="tabs"
|
||||
tabs-label="个人报销状态"
|
||||
:loading="loading"
|
||||
:error="error"
|
||||
:empty="showEmpty"
|
||||
:empty-state="emptyState"
|
||||
:total="totalCount"
|
||||
:total-pages="totalPages"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:show-pagination="showTable"
|
||||
loading-title="真实报销数据同步中"
|
||||
loading-message="正在加载后端返回的个人报销单据"
|
||||
loading-icon="mdi mdi-file-document-outline"
|
||||
error-title="报销列表加载失败"
|
||||
@retry="emit('reload')"
|
||||
@empty-action="handleEmptyAction"
|
||||
@page-size-change="changePageSize"
|
||||
>
|
||||
<template #filters>
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input v-model="listKeyword" type="search" placeholder="搜索单号、事由、报销类型..." />
|
||||
</div>
|
||||
|
||||
<div class="date-range-filter" :class="{ open: datePopover }">
|
||||
<button class="filter-btn date-range-trigger" type="button" @click="datePopover = !datePopover">
|
||||
<span class="date-range-label">{{ dateRangeLabel }}</span>
|
||||
<i class="mdi mdi-calendar"></i>
|
||||
</button>
|
||||
<div v-if="datePopover" class="date-range-popover" role="dialog" aria-label="选择时间段">
|
||||
<header>
|
||||
<strong>选择时间段</strong>
|
||||
<button type="button" aria-label="关闭" @click="datePopover = false"><i class="mdi mdi-close"></i></button>
|
||||
</header>
|
||||
<div class="date-range-fields">
|
||||
<label>
|
||||
<span>开始日期</span>
|
||||
<input v-model="rangeStart" type="date" />
|
||||
</label>
|
||||
<label>
|
||||
<span>结束日期</span>
|
||||
<input v-model="rangeEnd" type="date" />
|
||||
</label>
|
||||
</div>
|
||||
<footer>
|
||||
<button class="ghost-btn" type="button" @click="datePopover = false">取消</button>
|
||||
<button class="apply-btn" type="button" :disabled="!rangeStart || !rangeEnd" @click="applyDateRange">应用</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button v-for="filter in filters" :key="filter" type="button" class="filter-btn">
|
||||
<span>{{ filter }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<button class="create-request-btn" type="button" @click="emit('create-request')">
|
||||
<i class="mdi mdi-plus-circle-outline"></i>
|
||||
<span>发起报销</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p class="hint"><i class="mdi mdi-information-outline"></i> 点击任意行可查看单据详情</p>
|
||||
<template #hint>
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
点击任意行可查看单据详情
|
||||
</template>
|
||||
|
||||
<div class="table-wrap" :class="{ 'is-empty': showEmpty }">
|
||||
<div v-if="loading" class="table-state">
|
||||
<TableLoadingState
|
||||
title="真实报销数据同步中"
|
||||
message="正在加载后端返回的个人报销单据"
|
||||
icon="mdi mdi-file-document-outline"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="table-state error">
|
||||
<i class="mdi mdi-alert-circle-outline"></i>
|
||||
<strong>报销列表加载失败</strong>
|
||||
<p>{{ error }}</p>
|
||||
<button class="retry-btn" type="button" @click="emit('reload')">重新加载</button>
|
||||
</div>
|
||||
|
||||
<TableEmptyState
|
||||
v-else-if="showEmpty"
|
||||
:eyebrow="emptyState.eyebrow"
|
||||
:title="emptyState.title"
|
||||
:description="emptyState.desc"
|
||||
:icon="emptyState.icon"
|
||||
:action-label="emptyState.actionLabel"
|
||||
:action-icon="emptyState.actionIcon"
|
||||
:tone="emptyState.tone"
|
||||
:art-label="emptyState.artLabel"
|
||||
:tips="emptyState.tips"
|
||||
@action="handleEmptyAction"
|
||||
/>
|
||||
|
||||
<table v-else>
|
||||
<template #table>
|
||||
<table>
|
||||
<colgroup>
|
||||
<col class="col-id">
|
||||
<col class="col-type">
|
||||
@@ -126,24 +111,8 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer v-if="showTable" class="list-foot">
|
||||
<span class="page-summary">共 {{ totalCount }} 条,目前第 {{ currentPage }} 页</span>
|
||||
<div class="pager" aria-label="分页">
|
||||
<button class="page-nav" type="button" :disabled="currentPage === 1" aria-label="上一页" @click="currentPage--"><i class="mdi mdi-chevron-left"></i></button>
|
||||
<button v-for="p in totalPages" :key="p" class="page-number" :class="{ active: currentPage === p }" type="button" :aria-current="currentPage === p ? 'page' : undefined" @click="currentPage = p">{{ p }}</button>
|
||||
<button class="page-nav" type="button" :disabled="currentPage === totalPages" aria-label="下一页" @click="currentPage++"><i class="mdi mdi-chevron-right"></i></button>
|
||||
</div>
|
||||
<EnterpriseSelect
|
||||
v-model="pageSize"
|
||||
class="page-size-select"
|
||||
:options="pageSizeOptions"
|
||||
size="small"
|
||||
@change="changePageSize"
|
||||
/>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
</EnterpriseListPage>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
|
||||
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
|
||||
import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue'
|
||||
import { useApprovalInbox } from '../../composables/useApprovalInbox.js'
|
||||
import { useSystemState } from '../../composables/useSystemState.js'
|
||||
import { fetchApprovalExpenseClaims } from '../../services/reimbursements.js'
|
||||
@@ -115,9 +114,8 @@ function buildApprovalRow(request) {
|
||||
export default {
|
||||
name: 'ApprovalCenterView',
|
||||
components: {
|
||||
TravelRequestDetailView,
|
||||
TableLoadingState,
|
||||
TableEmptyState
|
||||
EnterpriseListPage,
|
||||
TravelRequestDetailView
|
||||
},
|
||||
setup() {
|
||||
const { currentUser } = useSystemState()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
|
||||
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
|
||||
import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue'
|
||||
import { mapExpenseClaimToRequest } from '../../composables/useRequests.js'
|
||||
import { fetchArchivedExpenseClaims } from '../../services/reimbursements.js'
|
||||
import {
|
||||
@@ -92,9 +91,8 @@ function resolveFilterLabel(options, activeValue, fallbackLabel) {
|
||||
export default {
|
||||
name: 'ArchiveCenterView',
|
||||
components: {
|
||||
TravelRequestDetailView,
|
||||
TableLoadingState,
|
||||
TableEmptyState
|
||||
EnterpriseListPage,
|
||||
TravelRequestDetailView
|
||||
},
|
||||
setup() {
|
||||
const activeTab = ref(ARCHIVE_TAB_ALL)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
|
||||
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
|
||||
import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue'
|
||||
import { useSystemState } from '../../composables/useSystemState.js'
|
||||
import { useToast } from '../../composables/useToast.js'
|
||||
import { fetchSystemLogEntries } from '../../services/systemLogs.js'
|
||||
@@ -62,8 +61,7 @@ function resolveSystemOutcomeTone(outcome) {
|
||||
export default {
|
||||
name: 'LogsView',
|
||||
components: {
|
||||
EnterpriseSelect,
|
||||
TableLoadingState
|
||||
EnterpriseListPage
|
||||
},
|
||||
emits: ['summary-change'],
|
||||
setup(_, { emit }) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
|
||||
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
|
||||
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
|
||||
import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue'
|
||||
import { normalizeRequestForUi } from '../../utils/requestViewModel.js'
|
||||
|
||||
function extractRowDate(value) {
|
||||
@@ -13,9 +11,7 @@ function extractRowDate(value) {
|
||||
export default {
|
||||
name: 'RequestsView',
|
||||
components: {
|
||||
EnterpriseSelect,
|
||||
TableLoadingState,
|
||||
TableEmptyState
|
||||
EnterpriseListPage
|
||||
},
|
||||
props: {
|
||||
filteredRequests: { type: Array, required: true },
|
||||
|
||||
Reference in New Issue
Block a user