feat(web): update views
- AuditView.vue: update audit view - EmployeeManagementView.vue: update employee management view - RequestsView.vue: update requests view - TravelRequestDetailView.vue: update travel request detail view
This commit is contained in:
@@ -551,10 +551,19 @@
|
||||
<button type="button" class="state-action" @click="loadAssets">重新加载</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!visibleSkills.length" class="table-state empty">
|
||||
<i class="mdi mdi-database-search-outline"></i>
|
||||
<p>没有匹配的资产数据</p>
|
||||
</div>
|
||||
<TableEmptyState
|
||||
v-else-if="!visibleSkills.length"
|
||||
:eyebrow="auditEmptyState.eyebrow"
|
||||
:title="auditEmptyState.title"
|
||||
:description="auditEmptyState.desc"
|
||||
:icon="auditEmptyState.icon"
|
||||
:action-label="auditEmptyState.actionLabel"
|
||||
:action-icon="auditEmptyState.actionIcon"
|
||||
:tone="auditEmptyState.tone"
|
||||
:art-label="auditEmptyState.artLabel"
|
||||
:tips="auditEmptyState.tips"
|
||||
@action="handleAuditEmptyAction"
|
||||
/>
|
||||
|
||||
<table v-else>
|
||||
<thead>
|
||||
|
||||
@@ -422,10 +422,19 @@
|
||||
<button type="button" class="state-action" @click="loadEmployees">重新加载</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!visibleEmployees.length" class="table-state empty">
|
||||
<i class="mdi mdi-account-search-outline"></i>
|
||||
<p>没有匹配的员工数据</p>
|
||||
</div>
|
||||
<TableEmptyState
|
||||
v-else-if="!visibleEmployees.length"
|
||||
:eyebrow="employeeEmptyState.eyebrow"
|
||||
:title="employeeEmptyState.title"
|
||||
:description="employeeEmptyState.desc"
|
||||
:icon="employeeEmptyState.icon"
|
||||
:action-label="employeeEmptyState.actionLabel"
|
||||
:action-icon="employeeEmptyState.actionIcon"
|
||||
:tone="employeeEmptyState.tone"
|
||||
:art-label="employeeEmptyState.artLabel"
|
||||
:tips="employeeEmptyState.tips"
|
||||
@action="handleEmployeeEmptyAction"
|
||||
/>
|
||||
|
||||
<table v-else>
|
||||
<colgroup>
|
||||
|
||||
@@ -74,11 +74,19 @@
|
||||
<button class="retry-btn" type="button" @click="emit('reload')">重新加载</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="showEmpty" class="table-state empty">
|
||||
<i class="mdi mdi-inbox-arrow-down-outline"></i>
|
||||
<strong>{{ emptyState.title }}</strong>
|
||||
<p>{{ emptyState.desc }}</p>
|
||||
</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>
|
||||
<colgroup>
|
||||
|
||||
@@ -3,18 +3,22 @@
|
||||
<div class="approval-detail">
|
||||
<div class="detail-scroll">
|
||||
<article class="detail-hero panel">
|
||||
<div class="applicant-card">
|
||||
<div class="portrait">{{ profile.avatar }}</div>
|
||||
<div>
|
||||
<h2>{{ profile.name }} <span>{{ request.typeLabel }}</span></h2>
|
||||
<p>{{ profile.department }} <strong>申请时间 {{ request.applyTime }}</strong></p>
|
||||
<div class="hero-topline">
|
||||
<div class="applicant-card">
|
||||
<div class="portrait">{{ profile.avatar }}</div>
|
||||
<div>
|
||||
<h2>{{ profile.name }} <span>{{ request.typeLabel }}</span></h2>
|
||||
<p>{{ profile.department }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="stat in heroStats" :key="stat.label" class="hero-stat">
|
||||
<span>{{ stat.label }}</span>
|
||||
<strong v-if="stat.kind === 'text'">{{ stat.value }}</strong>
|
||||
<b v-else :class="[stat.className, stat.tone]">{{ stat.value }}</b>
|
||||
<div class="hero-stat-strip">
|
||||
<div v-for="stat in heroStats" :key="stat.label" :class="['hero-stat', { emphasis: stat.emphasis }]">
|
||||
<span>{{ stat.label }}</span>
|
||||
<strong v-if="stat.kind === 'text'">{{ stat.value }}</strong>
|
||||
<b v-else :class="[stat.className, stat.tone]">{{ stat.value }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-summary-panel">
|
||||
@@ -36,7 +40,7 @@
|
||||
v-for="step in progressSteps"
|
||||
:key="step.label"
|
||||
class="progress-step"
|
||||
:class="{ active: step.active, current: step.current }"
|
||||
:class="{ active: step.active, current: step.current, done: step.done }"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
@@ -67,29 +71,41 @@
|
||||
{{ isTravelRequest ? '按出行时间逐笔核对票据与差旅规则。' : '按业务发生时间逐笔核对票据、用途说明与系统校验。' }}
|
||||
</p>
|
||||
</div>
|
||||
<button class="smart-entry-btn" type="button" @click="openAiEntry">
|
||||
<i class="mdi mdi-robot-outline"></i>
|
||||
<span>智能录入</span>
|
||||
</button>
|
||||
<div class="detail-card-actions">
|
||||
<button class="smart-entry-btn" type="button" @click="openAiEntry">
|
||||
<i class="mdi mdi-robot-outline"></i>
|
||||
<span>智能录入</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="isDraftRequest"
|
||||
class="smart-entry-btn secondary"
|
||||
type="button"
|
||||
:disabled="actionBusy"
|
||||
@click="handleAddExpenseItem"
|
||||
>
|
||||
<i class="mdi mdi-plus-circle-outline"></i>
|
||||
<span>{{ creatingExpense ? '新增中' : '增加明细' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-expense-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>费用项目</th>
|
||||
<th>说明</th>
|
||||
<th>金额</th>
|
||||
<th>附件材料</th>
|
||||
<th>系统校验</th>
|
||||
<th v-if="isDraftRequest">操作</th>
|
||||
<th class="col-time">时间</th>
|
||||
<th class="col-type">费用项目</th>
|
||||
<th class="col-desc">说明</th>
|
||||
<th class="col-amount">金额</th>
|
||||
<th class="col-attachment">附件材料</th>
|
||||
<th v-if="hasExpenseRiskColumn" class="col-risk">系统校验</th>
|
||||
<th v-if="isDraftRequest" class="col-action">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="item in expenseItems" :key="item.id">
|
||||
<tr>
|
||||
<td class="expense-time">
|
||||
<td class="expense-time col-time">
|
||||
<template v-if="editingExpenseId === item.id">
|
||||
<div class="cell-editor">
|
||||
<input v-model="expenseEditor.itemDate" class="editor-input" type="date" />
|
||||
@@ -101,7 +117,7 @@
|
||||
<span>{{ item.dayLabel }}</span>
|
||||
</template>
|
||||
</td>
|
||||
<td class="expense-type">
|
||||
<td class="expense-type col-type">
|
||||
<template v-if="editingExpenseId === item.id">
|
||||
<div class="cell-editor">
|
||||
<select v-model="expenseEditor.itemType" class="editor-select">
|
||||
@@ -117,11 +133,11 @@
|
||||
<span>{{ item.category }}</span>
|
||||
</template>
|
||||
</td>
|
||||
<td class="expense-desc">
|
||||
<td class="expense-desc col-desc">
|
||||
<template v-if="editingExpenseId === item.id">
|
||||
<div class="cell-editor editor-stack">
|
||||
<input v-model="expenseEditor.itemReason" class="editor-input" type="text" placeholder="输入费用说明" />
|
||||
<input v-model="expenseEditor.itemLocation" class="editor-input" type="text" placeholder="输入业务地点" />
|
||||
<input v-model="expenseEditor.itemLocation" class="editor-input" type="text" :placeholder="locationInputPlaceholder" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -129,10 +145,20 @@
|
||||
<span>{{ item.detail }}</span>
|
||||
</template>
|
||||
</td>
|
||||
<td class="expense-amount">
|
||||
<td class="expense-amount col-amount">
|
||||
<template v-if="editingExpenseId === item.id">
|
||||
<div class="cell-editor">
|
||||
<input v-model="expenseEditor.itemAmount" class="editor-input" type="number" min="0" step="0.01" placeholder="输入金额" />
|
||||
<label class="currency-editor">
|
||||
<span>¥</span>
|
||||
<input
|
||||
v-model="expenseEditor.itemAmount"
|
||||
class="editor-input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="输入金额"
|
||||
/>
|
||||
</label>
|
||||
<span>保存后自动格式化为人民币</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -141,42 +167,101 @@
|
||||
<span v-if="item.tone !== 'ok'" :class="['over-tag', item.tone]">{{ item.status }}</span>
|
||||
</template>
|
||||
</td>
|
||||
<td class="expense-attachment">
|
||||
<td class="expense-attachment col-attachment">
|
||||
<template v-if="editingExpenseId === item.id">
|
||||
<div class="cell-editor editor-stack">
|
||||
<input v-model="expenseEditor.invoiceId" class="editor-input" type="text" placeholder="输入票据标识或附件名称" />
|
||||
<span>示例:invoice-2026-0513.jpg</span>
|
||||
<div class="attachment-action-group">
|
||||
<button
|
||||
class="icon-action upload"
|
||||
type="button"
|
||||
title="上传附件"
|
||||
:disabled="actionBusy"
|
||||
@click="triggerExpenseUpload(item)"
|
||||
>
|
||||
<i :class="uploadingExpenseId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-file-upload-outline'"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="canPreviewAttachment(item)"
|
||||
class="icon-action preview"
|
||||
type="button"
|
||||
title="查看附件"
|
||||
@click="openAttachmentPreview(item)"
|
||||
>
|
||||
<i class="mdi mdi-eye-outline"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="item.invoiceId"
|
||||
class="icon-action danger"
|
||||
type="button"
|
||||
title="删除附件"
|
||||
:disabled="deletingAttachmentId === item.id"
|
||||
@click="removeExpenseAttachment(item)"
|
||||
>
|
||||
<i :class="deletingAttachmentId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-close-thick'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<span class="attachment-hint compact">
|
||||
{{ resolveAttachmentDisplayName(item) || '支持上传 JPG、PNG、PDF,未上传也可先保存草稿。' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="expense-attachment-main">
|
||||
<span :class="['attachment-pill', item.attachmentTone]">{{ item.attachmentStatus }}</span>
|
||||
<div class="attachment-action-group">
|
||||
<button
|
||||
v-if="item.attachments.length"
|
||||
class="inline-action"
|
||||
class="icon-action upload"
|
||||
type="button"
|
||||
@click="toggleExpenseAttachments(item.id)"
|
||||
title="上传附件"
|
||||
:disabled="actionBusy"
|
||||
@click="triggerExpenseUpload(item)"
|
||||
>
|
||||
{{ expandedExpenseId === item.id ? '收起附件' : '查看附件' }}
|
||||
<i :class="uploadingExpenseId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-file-upload-outline'"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="canPreviewAttachment(item)"
|
||||
class="icon-action preview"
|
||||
type="button"
|
||||
title="查看附件"
|
||||
@click="openAttachmentPreview(item)"
|
||||
>
|
||||
<i class="mdi mdi-eye-outline"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="item.invoiceId"
|
||||
class="icon-action danger"
|
||||
type="button"
|
||||
title="删除附件"
|
||||
:disabled="deletingAttachmentId === item.id"
|
||||
@click="removeExpenseAttachment(item)"
|
||||
>
|
||||
<i :class="deletingAttachmentId === item.id ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-close-thick'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<span class="attachment-hint">{{ item.attachmentHint }}</span>
|
||||
</template>
|
||||
</td>
|
||||
<td class="expense-risk">
|
||||
<template v-if="showExpenseRisk(item)">
|
||||
<span :class="['risk-inline-tag', resolveExpenseIssues(item).length ? 'medium' : item.riskTone]">
|
||||
{{ resolveExpenseIssues(item).length ? '待补充' : item.riskLabel }}
|
||||
<span class="attachment-hint compact">
|
||||
{{ resolveAttachmentDisplayName(item) || '未上传附件' }}
|
||||
</span>
|
||||
<p>{{ resolveExpenseIssues(item).length ? resolveExpenseIssues(item).join(',') : item.riskText }}</p>
|
||||
</template>
|
||||
</td>
|
||||
<td v-if="isDraftRequest" class="expense-action-cell">
|
||||
<td v-if="hasExpenseRiskColumn" class="expense-risk col-risk">
|
||||
<template v-if="showExpenseRisk(item)">
|
||||
<span :class="['risk-inline-tag', resolveExpenseRiskState(item).tone]">
|
||||
{{ resolveExpenseRiskState(item).label }}
|
||||
</span>
|
||||
<strong class="risk-headline">{{ resolveExpenseRiskState(item).headline }}</strong>
|
||||
<p>{{ resolveExpenseRiskState(item).summary }}</p>
|
||||
<ul v-if="resolveExpenseRiskState(item).points.length" class="risk-point-list">
|
||||
<li v-for="point in resolveExpenseRiskState(item).points" :key="point">{{ point }}</li>
|
||||
</ul>
|
||||
<p v-if="resolveExpenseRiskState(item).suggestion" class="risk-suggestion">
|
||||
{{ resolveExpenseRiskState(item).suggestion }}
|
||||
</p>
|
||||
</template>
|
||||
</td>
|
||||
<td v-if="isDraftRequest" class="expense-action-cell col-action">
|
||||
<div v-if="editingExpenseId === item.id" class="row-action-group">
|
||||
<button
|
||||
class="inline-action primary"
|
||||
type="button"
|
||||
:disabled="savingExpenseId === item.id || submitBusy || deleteBusy"
|
||||
:disabled="savingExpenseId === item.id || submitBusy || deleteBusy || deletingExpenseId === item.id"
|
||||
@click="saveExpenseEdit(item)"
|
||||
>
|
||||
{{ savingExpenseId === item.id ? '保存中' : '保存' }}
|
||||
@@ -184,40 +269,56 @@
|
||||
<button
|
||||
class="inline-action"
|
||||
type="button"
|
||||
:disabled="savingExpenseId === item.id || submitBusy || deleteBusy"
|
||||
:disabled="savingExpenseId === item.id || submitBusy || deleteBusy || deletingExpenseId === item.id"
|
||||
@click="cancelExpenseEdit"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
class="inline-action danger"
|
||||
type="button"
|
||||
:disabled="savingExpenseId === item.id || submitBusy || deleteBusy || deletingExpenseId === item.id"
|
||||
@click="removeExpenseItem(item)"
|
||||
>
|
||||
{{ deletingExpenseId === item.id ? '删除中' : '删除' }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-else
|
||||
class="inline-action"
|
||||
type="button"
|
||||
:disabled="actionBusy"
|
||||
@click="startExpenseEdit(item)"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="expandedExpenseId === item.id" class="expense-expand-row">
|
||||
<td :colspan="isDraftRequest ? 7 : 6">
|
||||
<div class="expense-files">
|
||||
<span v-for="file in item.attachments" :key="file" class="expense-file-chip">
|
||||
<i class="mdi mdi-paperclip"></i>
|
||||
{{ file }}
|
||||
</span>
|
||||
<div v-else class="row-action-group">
|
||||
<button
|
||||
class="inline-action"
|
||||
type="button"
|
||||
:disabled="actionBusy"
|
||||
@click="startExpenseEdit(item)"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="inline-action danger"
|
||||
type="button"
|
||||
:disabled="actionBusy"
|
||||
@click="removeExpenseItem(item)"
|
||||
>
|
||||
{{ deletingExpenseId === item.id ? '删除中' : '删除' }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="!expenseItems.length" class="empty-row">
|
||||
<td :colspan="expenseTableColumnCount" class="empty-row-cell">
|
||||
当前还没有费用明细,点击右上角“增加明细”继续补充。
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="total-row">
|
||||
<td :colspan="isDraftRequest ? 4 : 3">合计</td>
|
||||
<td>{{ expenseTotal }}</td>
|
||||
<td>{{ uploadedExpenseCount }} 项已关联票据</td>
|
||||
<td>{{ expenseSummaryText }}</td>
|
||||
<td v-if="isDraftRequest">-</td>
|
||||
<td :colspan="expenseTableColumnCount">
|
||||
<div class="expense-total-bar">
|
||||
<strong>合计 {{ expenseTotal }}</strong>
|
||||
<div class="expense-total-meta">
|
||||
<span>{{ uploadedExpenseCount }} 项已关联票据</span>
|
||||
<span>{{ expenseSummaryText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -226,17 +327,20 @@
|
||||
|
||||
<article v-if="isDraftRequest" class="detail-card panel validation-card">
|
||||
<div class="validation-head">
|
||||
<h3>提交校验</h3>
|
||||
<span :class="['validation-pill', validationTone]">{{ canSubmit ? '可提交' : '待完善' }}</span>
|
||||
<div>
|
||||
<h3>AI建议</h3>
|
||||
<p>按建议顺序补齐信息或处理风险后,再发起审批。</p>
|
||||
</div>
|
||||
<span :class="['validation-pill', aiAdvice.tone]">{{ aiAdvice.badge }}</span>
|
||||
</div>
|
||||
<p class="validation-summary">{{ validationSummary }}</p>
|
||||
<ul v-if="draftBlockingIssues.length" class="validation-list">
|
||||
<li v-for="issue in draftBlockingIssues" :key="issue">{{ issue }}</li>
|
||||
<p class="validation-summary">{{ aiAdvice.summary }}</p>
|
||||
<ul v-if="aiAdvice.items.length" class="validation-list">
|
||||
<li v-for="item in aiAdvice.items" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="detail-card panel">
|
||||
<h3>申请说明</h3>
|
||||
<h3>附加说明</h3>
|
||||
<div class="detail-note">{{ detailNote }}</div>
|
||||
</article>
|
||||
</section>
|
||||
@@ -262,6 +366,62 @@
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<input
|
||||
ref="expenseUploadInput"
|
||||
class="expense-upload-input"
|
||||
type="file"
|
||||
accept="image/*,.pdf"
|
||||
@change="handleExpenseFileChange"
|
||||
/>
|
||||
|
||||
<Transition name="shared-confirm">
|
||||
<div
|
||||
v-if="attachmentPreviewOpen"
|
||||
class="attachment-preview-mask"
|
||||
role="presentation"
|
||||
@click.self="closeAttachmentPreview"
|
||||
>
|
||||
<section class="attachment-preview-card" role="dialog" aria-modal="true" @click.stop>
|
||||
<div class="attachment-preview-head">
|
||||
<div>
|
||||
<span class="attachment-preview-badge">附件预览</span>
|
||||
<h4>{{ attachmentPreviewName || '当前附件' }}</h4>
|
||||
</div>
|
||||
<button class="attachment-preview-close" type="button" @click="closeAttachmentPreview">
|
||||
<i class="mdi mdi-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="attachment-preview-body">
|
||||
<div v-if="attachmentPreviewLoading" class="attachment-preview-state">
|
||||
<i class="mdi mdi-loading mdi-spin"></i>
|
||||
<span>正在加载附件预览…</span>
|
||||
</div>
|
||||
<div v-else-if="attachmentPreviewError" class="attachment-preview-state error">
|
||||
<i class="mdi mdi-alert-circle-outline"></i>
|
||||
<span>{{ attachmentPreviewError }}</span>
|
||||
</div>
|
||||
<img
|
||||
v-else-if="attachmentPreviewUrl && attachmentPreviewMediaType.startsWith('image/')"
|
||||
:src="attachmentPreviewUrl"
|
||||
:alt="attachmentPreviewName || '附件图片'"
|
||||
class="attachment-preview-image"
|
||||
/>
|
||||
<iframe
|
||||
v-else-if="attachmentPreviewUrl && attachmentPreviewMediaType === 'application/pdf'"
|
||||
:src="attachmentPreviewUrl"
|
||||
class="attachment-preview-frame"
|
||||
title="附件预览"
|
||||
></iframe>
|
||||
<div v-else class="attachment-preview-state">
|
||||
<i class="mdi mdi-file-outline"></i>
|
||||
<span>当前附件暂不支持直接预览。</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<ConfirmDialog
|
||||
:open="deleteDialogOpen"
|
||||
badge="删除草稿"
|
||||
|
||||
Reference in New Issue
Block a user