chore: remove prototype files and unused UI assets
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lxgw-wenkai/1.501/lxgw-wenkai.min.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" />
|
||||
<title>ReimburseOps - 企业报销智能运营台</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ReimburseOps - 企业报销智能运营台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
21
src/App.vue
21
src/App.vue
@@ -21,7 +21,9 @@
|
||||
:class="{
|
||||
'chat-main': activeView === 'chat',
|
||||
'overview-main': activeView === 'overview',
|
||||
'requests-main': activeView === 'requests'
|
||||
'requests-main': activeView === 'requests',
|
||||
'approval-main': activeView === 'approval',
|
||||
'policies-main': activeView === 'policies'
|
||||
}"
|
||||
>
|
||||
<TopBar
|
||||
@@ -40,7 +42,7 @@
|
||||
/>
|
||||
|
||||
<FilterBar
|
||||
v-if="activeView !== 'chat' && activeView !== 'overview' && activeView !== 'requests'"
|
||||
v-if="activeView !== 'chat' && activeView !== 'overview' && activeView !== 'requests' && activeView !== 'approval' && activeView !== 'policies'"
|
||||
:compact="activeView === 'overview'"
|
||||
:filters="filters"
|
||||
:ranges="ranges"
|
||||
@@ -52,7 +54,9 @@
|
||||
class="workarea"
|
||||
:class="{
|
||||
'chat-workarea': activeView === 'chat',
|
||||
'requests-workarea': activeView === 'requests'
|
||||
'requests-workarea': activeView === 'requests',
|
||||
'approval-workarea': activeView === 'approval',
|
||||
'policies-workarea': activeView === 'policies'
|
||||
}"
|
||||
>
|
||||
<OverviewView
|
||||
@@ -95,6 +99,8 @@
|
||||
@create-request="openTravelCreate"
|
||||
/>
|
||||
|
||||
<ApprovalCenterView v-else-if="activeView === 'approval'" />
|
||||
|
||||
<PoliciesView v-else-if="activeView === 'policies'" />
|
||||
|
||||
<AuditView v-else />
|
||||
@@ -118,6 +124,7 @@ import OverviewView from './views/OverviewView.vue'
|
||||
import ChatView from './views/ChatView.vue'
|
||||
import TravelReimbursementCreateView from './views/TravelReimbursementCreateView.vue'
|
||||
import RequestsView from './views/RequestsView.vue'
|
||||
import ApprovalCenterView from './views/ApprovalCenterView.vue'
|
||||
import PoliciesView from './views/PoliciesView.vue'
|
||||
import AuditView from './views/AuditView.vue'
|
||||
|
||||
@@ -219,7 +226,9 @@ function backToRequests() {
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
overflow: hidden;
|
||||
}
|
||||
.main.requests-main {
|
||||
.main.requests-main,
|
||||
.main.approval-main,
|
||||
.main.policies-main {
|
||||
height: 100dvh;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
overflow: hidden;
|
||||
@@ -229,7 +238,9 @@ function backToRequests() {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.workarea.requests-workarea {
|
||||
.workarea.requests-workarea,
|
||||
.workarea.approval-workarea,
|
||||
.workarea.policies-workarea {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 20px 24px;
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
--nav-muted: #7d89a5;
|
||||
--radius: 8px;
|
||||
--ease: cubic-bezier(.2, .8, .2, 1);
|
||||
font-family: Inter, "SF Pro Display", "Segoe UI", "PingFang SC", "Microsoft YaHei", Arial, sans-serif;
|
||||
font-family: "LXGW WenKai", Inter, "SF Pro Display", "PingFang SC", sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; min-height: 100dvh; color: var(--text); background: var(--bg); }
|
||||
.mdi { line-height: 1; vertical-align: middle; }
|
||||
button, input, select, textarea { font: inherit; }
|
||||
button { cursor: pointer; }
|
||||
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 3px solid rgba(16,185,129,.20); outline-offset: 2px; }
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Tooltip,
|
||||
Legend
|
||||
} from 'chart.js'
|
||||
import { useAnimationProgress } from '../../composables/useAnimationProgress.js'
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend)
|
||||
|
||||
@@ -35,10 +36,12 @@ const props = defineProps({
|
||||
centerLabel: { type: String, required: true }
|
||||
})
|
||||
|
||||
const progress = useAnimationProgress([() => props.items], 1150)
|
||||
|
||||
const chartData = computed(() => ({
|
||||
labels: props.items.map((i) => i.name),
|
||||
datasets: [{
|
||||
data: props.items.map((i) => i.value),
|
||||
data: props.items.map((i) => Math.max(Number((i.value * progress.value).toFixed(1)), 0.001)),
|
||||
backgroundColor: props.items.map((i) => i.color),
|
||||
borderWidth: 0,
|
||||
cutout: '68%',
|
||||
@@ -50,6 +53,19 @@ const chartData = computed(() => ({
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
animateRotate: true,
|
||||
animateScale: true,
|
||||
duration: 900,
|
||||
easing: 'easeOutQuart'
|
||||
},
|
||||
transitions: {
|
||||
active: {
|
||||
animation: {
|
||||
duration: 180
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
@@ -64,7 +80,7 @@ const chartOptions = {
|
||||
usePointStyle: true,
|
||||
callbacks: {
|
||||
label: (ctx) => {
|
||||
const total = ctx.dataset.data.reduce((a, b) => a + b, 0)
|
||||
const total = ctx.dataset.data.reduce((a, b) => a + b, 0) || 1
|
||||
const pct = ((ctx.parsed / total) * 100).toFixed(1)
|
||||
return ` ${ctx.label}: ${pct}%`
|
||||
}
|
||||
@@ -98,6 +114,8 @@ const chartOptions = {
|
||||
place-content: center;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
animation: donutCenterIn 620ms ease both;
|
||||
animation-delay: 360ms;
|
||||
}
|
||||
|
||||
.donut-center strong {
|
||||
@@ -117,6 +135,8 @@ const chartOptions = {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px 16px;
|
||||
animation: donutLegendIn 560ms ease both;
|
||||
animation-delay: 480ms;
|
||||
}
|
||||
|
||||
.legend-row {
|
||||
@@ -142,4 +162,33 @@ const chartOptions = {
|
||||
color: #94a3b8;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@keyframes donutCenterIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(.92);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes donutLegendIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.donut-center,
|
||||
.donut-legend {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="gauge-body">
|
||||
<Doughnut :data="chartData" :options="chartOptions" />
|
||||
<div class="gauge-center">
|
||||
<strong>{{ ratio }}%</strong>
|
||||
<strong>{{ animatedRatio }}%</strong>
|
||||
<span>已执行</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ArcElement,
|
||||
Tooltip
|
||||
} from 'chart.js'
|
||||
import { useAnimationProgress } from '../../composables/useAnimationProgress.js'
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip)
|
||||
|
||||
@@ -43,11 +44,13 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const ratioValue = computed(() => Number(props.ratio))
|
||||
const progress = useAnimationProgress([() => props.ratio], 1150)
|
||||
const animatedRatio = computed(() => Number((ratioValue.value * progress.value).toFixed(0)))
|
||||
|
||||
const chartData = computed(() => ({
|
||||
labels: ['已执行', '剩余'],
|
||||
datasets: [{
|
||||
data: [ratioValue.value, 100 - ratioValue.value],
|
||||
data: [animatedRatio.value, 100 - animatedRatio.value],
|
||||
backgroundColor: ['#10b981', '#e2e8f0'],
|
||||
borderWidth: 0
|
||||
}]
|
||||
@@ -59,6 +62,11 @@ const chartOptions = {
|
||||
rotation: -90,
|
||||
circumference: 180,
|
||||
cutout: '65%',
|
||||
animation: {
|
||||
animateRotate: true,
|
||||
duration: 900,
|
||||
easing: 'easeOutQuart'
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: { enabled: false }
|
||||
@@ -88,6 +96,8 @@ const chartOptions = {
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
animation: gaugeCenterIn 620ms ease both;
|
||||
animation-delay: 360ms;
|
||||
}
|
||||
|
||||
.gauge-center strong {
|
||||
@@ -109,6 +119,8 @@ const chartOptions = {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
animation: gaugeSummaryIn 560ms ease both;
|
||||
animation-delay: 500ms;
|
||||
}
|
||||
|
||||
.gauge-summary span {
|
||||
@@ -124,4 +136,33 @@ const chartOptions = {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
||||
@keyframes gaugeCenterIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes gaugeSummaryIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.gauge-center,
|
||||
.gauge-summary {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
Tooltip,
|
||||
Legend
|
||||
} from 'chart.js'
|
||||
import { useAnimationProgress } from '../../composables/useAnimationProgress.js'
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, PointElement, LineElement, Filler, Tooltip, Legend)
|
||||
|
||||
@@ -35,12 +36,22 @@ const props = defineProps({
|
||||
avgHours: { type: Array, required: true }
|
||||
})
|
||||
|
||||
const progress = useAnimationProgress([
|
||||
() => props.labels,
|
||||
() => props.applications,
|
||||
() => props.approved,
|
||||
() => props.avgHours
|
||||
], 1200)
|
||||
|
||||
const scaleSeries = (series, decimals = 0) =>
|
||||
series.map((value) => Number((Number(value) * progress.value).toFixed(decimals)))
|
||||
|
||||
const chartData = computed(() => ({
|
||||
labels: props.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '申请量(单)',
|
||||
data: props.applications,
|
||||
data: scaleSeries(props.applications),
|
||||
backgroundColor: '#10b981',
|
||||
borderRadius: 4,
|
||||
barPercentage: 0.6,
|
||||
@@ -49,7 +60,7 @@ const chartData = computed(() => ({
|
||||
},
|
||||
{
|
||||
label: '审批完成量(单)',
|
||||
data: props.approved,
|
||||
data: scaleSeries(props.approved),
|
||||
backgroundColor: '#3b82f6',
|
||||
borderRadius: 4,
|
||||
barPercentage: 0.6,
|
||||
@@ -58,7 +69,7 @@ const chartData = computed(() => ({
|
||||
},
|
||||
{
|
||||
label: '平均审批时长(小时)',
|
||||
data: props.avgHours,
|
||||
data: scaleSeries(props.avgHours, 1),
|
||||
borderColor: '#8b5cf6',
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 2,
|
||||
@@ -77,6 +88,10 @@ const chartData = computed(() => ({
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: 900,
|
||||
easing: 'easeOutQuart'
|
||||
},
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<strong class="brand-name">星海科技</strong>
|
||||
<button class="brand-toggle" type="button" aria-label="打开 AI 助手" @click="emit('openChat')">
|
||||
<i class="pi pi-angle-double-left"></i>
|
||||
<i class="mdi mdi-chevron-double-left"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<strong>张晓明</strong>
|
||||
<span>财务管理员</span>
|
||||
</span>
|
||||
<i class="pi pi-angle-down"></i>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</aside>
|
||||
</template>
|
||||
@@ -52,8 +52,9 @@ const emit = defineEmits(['navigate', 'openChat'])
|
||||
const sidebarMeta = {
|
||||
overview: { label: '总览' },
|
||||
requests: { label: '差旅申请/报销' },
|
||||
approval: { label: '审批中心', badge: '12' },
|
||||
chat: { label: 'AI助手' },
|
||||
policies: { label: '政策规则' },
|
||||
policies: { label: '知识管理' },
|
||||
audit: { label: '审计追踪' }
|
||||
}
|
||||
|
||||
@@ -278,7 +279,7 @@ const decoratedNavItems = computed(() =>
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rail-user .pi {
|
||||
.rail-user .mdi {
|
||||
justify-self: end;
|
||||
color: #718096;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</div>
|
||||
|
||||
<div class="top-actions">
|
||||
<span v-if="isChat || !isOverview" class="search-wrap" :class="{ wide: isChat }">
|
||||
<i class="pi pi-search search-icon"></i>
|
||||
<span v-if="isChat" class="search-wrap" :class="{ wide: isChat }">
|
||||
<i class="mdi mdi-magnify search-icon"></i>
|
||||
<input
|
||||
:value="search"
|
||||
type="search"
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<template v-if="isChat">
|
||||
<button class="icon-btn notification" type="button" aria-label="查看通知">
|
||||
<i class="pi pi-bell"></i>
|
||||
<i class="mdi mdi-bell-outline"></i>
|
||||
<span>1</span>
|
||||
</button>
|
||||
<button class="profile-btn" type="button" aria-label="打开用户菜单">
|
||||
@@ -28,7 +28,7 @@
|
||||
<strong>张晓明</strong>
|
||||
<small>财务管理员</small>
|
||||
</span>
|
||||
<i class="pi pi-angle-down"></i>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div v-if="isOverview" class="range-combo" aria-label="首页时间范围">
|
||||
<div class="range-shell">
|
||||
<span class="range-meta">
|
||||
<i class="pi pi-calendar"></i>
|
||||
<i class="mdi mdi-calendar"></i>
|
||||
<span>{{ activeDateLabel }}</span>
|
||||
</span>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
aria-haspopup="dialog"
|
||||
@click="calendarOpen = !calendarOpen"
|
||||
>
|
||||
<i class="pi pi-calendar-plus"></i>
|
||||
<i class="mdi mdi-calendar-plus"></i>
|
||||
<span>选择时间段</span>
|
||||
</button>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<header>
|
||||
<strong>选择看板时间段</strong>
|
||||
<button type="button" aria-label="关闭日期选择" @click="calendarOpen = false">
|
||||
<i class="pi pi-times"></i>
|
||||
<i class="mdi mdi-close"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
@@ -87,12 +87,6 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="quick-presets" aria-label="快捷时间段">
|
||||
<button type="button" @click="applyDraft('2024-07-12', '2024-07-12')">今日</button>
|
||||
<button type="button" @click="applyDraft('2024-07-06', '2024-07-12')">本周</button>
|
||||
<button type="button" @click="applyDraft('2024-07-01', '2024-07-31')">本月</button>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button class="ghost-btn" type="button" @click="calendarOpen = false">取消</button>
|
||||
<button class="apply-btn" type="button" :disabled="!canApplyCustomRange" @click="applyCustomRange">
|
||||
@@ -103,35 +97,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else-if="isRequests">
|
||||
<button class="month-chip" type="button" aria-label="选择报销月份">
|
||||
<i class="pi pi-calendar"></i>
|
||||
<span>2024-07</span>
|
||||
<i class="pi pi-angle-down"></i>
|
||||
<template v-if="isPolicies || isRequests"></template>
|
||||
|
||||
<template v-else-if="isApproval">
|
||||
<button class="month-chip" type="button" aria-label="筛选审批日期">
|
||||
<i class="mdi mdi-calendar"></i>
|
||||
<span>筛选 / 日期</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<button class="icon-btn notification" type="button" aria-label="查看通知">
|
||||
<i class="pi pi-bell"></i>
|
||||
<span>3</span>
|
||||
<i class="mdi mdi-bell-outline"></i>
|
||||
<span>9</span>
|
||||
</button>
|
||||
<button class="profile-btn" type="button" aria-label="打开用户菜单">
|
||||
<span class="profile-avatar">张</span>
|
||||
<span class="profile-copy">
|
||||
<strong>张晓明</strong>
|
||||
<small>财务管理员</small>
|
||||
<small>财务管理部</small>
|
||||
</span>
|
||||
<i class="pi pi-angle-down"></i>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<div v-else class="date-chip">
|
||||
<i class="pi pi-calendar"></i>
|
||||
<span>2024-07-06 ~ 2024-07-12</span>
|
||||
</div>
|
||||
<template v-else-if="isRequests"></template>
|
||||
|
||||
<button v-if="!isRequests" class="top-btn primary" type="button" @click="emit('openChat')">
|
||||
<i class="pi pi-sparkles"></i>
|
||||
<span>AI 助手</span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
@@ -164,6 +153,8 @@ const emit = defineEmits([
|
||||
const isChat = computed(() => props.activeView === 'chat')
|
||||
const isOverview = computed(() => props.activeView === 'overview')
|
||||
const isRequests = computed(() => props.activeView === 'requests')
|
||||
const isApproval = computed(() => props.activeView === 'approval')
|
||||
const isPolicies = computed(() => props.activeView === 'policies')
|
||||
const calendarOpen = ref(false)
|
||||
const draftStart = ref(props.customRange.start)
|
||||
const draftEnd = ref(props.customRange.end)
|
||||
@@ -357,7 +348,7 @@ function formatRangeLabel(start, end) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.range-meta .pi {
|
||||
.range-meta .mdi {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
@@ -514,28 +505,6 @@ function formatRangeLabel(start, end) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.quick-presets {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.quick-presets button {
|
||||
height: 34px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
color: #334155;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.quick-presets button:hover {
|
||||
border-color: rgba(16, 185, 129, .28);
|
||||
background: #ecfdf5;
|
||||
color: #0f9f78;
|
||||
}
|
||||
|
||||
.ghost-btn,
|
||||
.apply-btn {
|
||||
height: 36px;
|
||||
@@ -562,20 +531,6 @@ function formatRangeLabel(start, end) {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
|
||||
.date-chip {
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 32px 0 12px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.month-chip {
|
||||
height: 42px;
|
||||
display: inline-flex;
|
||||
@@ -590,7 +545,7 @@ function formatRangeLabel(start, end) {
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.month-chip .pi:first-child {
|
||||
.month-chip .mdi:first-child {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
@@ -599,32 +554,6 @@ function formatRangeLabel(start, end) {
|
||||
color: #0f9f78;
|
||||
}
|
||||
|
||||
.date-chip .pi {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.top-btn {
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
transition: background 160ms ease;
|
||||
}
|
||||
|
||||
.top-btn.primary {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.top-btn.primary:hover {
|
||||
background: #0ea672;
|
||||
}
|
||||
|
||||
.icon-btn,
|
||||
.profile-btn {
|
||||
border: 0;
|
||||
@@ -705,7 +634,7 @@ function formatRangeLabel(start, end) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.profile-btn .pi {
|
||||
.profile-btn .mdi {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ export const navItems = [
|
||||
title: '差旅申请/报销',
|
||||
desc: '查看员工差旅报销单据、跟踪进度、发起新申请'
|
||||
},
|
||||
{
|
||||
id: 'approval',
|
||||
label: '审批中心',
|
||||
navHint: '待审批单据与批量处理',
|
||||
icon: icons.approval,
|
||||
title: '审批中心',
|
||||
desc: '统一处理待审批单据,聚焦效率、风险与 SLA'
|
||||
},
|
||||
{
|
||||
id: 'chat',
|
||||
label: 'AI助手',
|
||||
@@ -28,11 +36,11 @@ export const navItems = [
|
||||
},
|
||||
{
|
||||
id: 'policies',
|
||||
label: '政策规则',
|
||||
navHint: '制度与校验规则',
|
||||
label: '知识管理',
|
||||
navHint: '制度、文档与知识图谱',
|
||||
icon: icons.file,
|
||||
title: '政策规则中心',
|
||||
desc: '维护差旅、招待、采购和发票校验规则。'
|
||||
title: '财务知识管理中心',
|
||||
desc: '上传制度文档、沉淀财务知识、构建知识图谱,面向员工问答与知识管理'
|
||||
},
|
||||
{
|
||||
id: 'audit',
|
||||
|
||||
@@ -3,6 +3,7 @@ const iconPath = (content) => `<svg viewBox="0 0 24 24" aria-hidden="true">${con
|
||||
export const icons = {
|
||||
dashboard: iconPath('<path d="M3 13h8V3H3z"/><path d="M13 21h8V11h-8z"/><path d="M13 3h8v6h-8z"/><path d="M3 21h8v-6H3z"/>'),
|
||||
list: iconPath('<path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/>'),
|
||||
approval: iconPath('<path d="M9 11l2 2 4-5"/><path d="M20 12v5a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h8"/><path d="M17 3h4v4"/>'),
|
||||
file: iconPath('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8"/><path d="M8 17h5"/>'),
|
||||
audit: iconPath('<path d="M12 8v4l3 3"/><path d="M3.05 11a9 9 0 1 1 .5 4"/><path d="M3 4v7h7"/>'),
|
||||
search: iconPath('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>'),
|
||||
|
||||
@@ -4,7 +4,7 @@ export const metricBlueprints = [
|
||||
label: '待审批单据',
|
||||
unit: '单',
|
||||
accent: '#10b981',
|
||||
icon: 'pi pi-file',
|
||||
icon: 'mdi mdi-file-document-outline',
|
||||
trend: 'down',
|
||||
change: '12.5%',
|
||||
delta: '较昨日 -18 单'
|
||||
@@ -13,7 +13,7 @@ export const metricBlueprints = [
|
||||
key: 'pendingAmount',
|
||||
label: '待处理金额',
|
||||
accent: '#3b82f6',
|
||||
icon: 'pi pi-wallet',
|
||||
icon: 'mdi mdi-wallet',
|
||||
trend: 'up',
|
||||
change: '8.3%',
|
||||
delta: '较昨日 +¥27,400'
|
||||
@@ -23,7 +23,7 @@ export const metricBlueprints = [
|
||||
label: '平均审批时长',
|
||||
unit: 'h',
|
||||
accent: '#8b5cf6',
|
||||
icon: 'pi pi-clock',
|
||||
icon: 'mdi mdi-clock-outline',
|
||||
trend: 'down',
|
||||
change: '14.8%',
|
||||
delta: '较昨日 -1.2h'
|
||||
@@ -33,7 +33,7 @@ export const metricBlueprints = [
|
||||
label: '自动审单通过率',
|
||||
unit: '%',
|
||||
accent: '#16a34a',
|
||||
icon: 'pi pi-shield',
|
||||
icon: 'mdi mdi-shield-outline',
|
||||
trend: 'up',
|
||||
change: '6.2%',
|
||||
delta: '较昨日 +4.6%'
|
||||
@@ -43,7 +43,7 @@ export const metricBlueprints = [
|
||||
label: '异常预警单',
|
||||
unit: '单',
|
||||
accent: '#ef4444',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
icon: 'mdi mdi-alert',
|
||||
trend: 'up',
|
||||
change: '16.7%',
|
||||
delta: '较昨日 +2 单'
|
||||
@@ -53,7 +53,7 @@ export const metricBlueprints = [
|
||||
label: 'SLA 达成率',
|
||||
unit: '%',
|
||||
accent: '#10b981',
|
||||
icon: 'pi pi-check-circle',
|
||||
icon: 'mdi mdi-check-circle',
|
||||
trend: 'up',
|
||||
change: '3.1%',
|
||||
delta: '较昨日 +2.9%'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<header class="panel-head">
|
||||
<h2>今日你可以这样问我</h2>
|
||||
<button class="text-action" type="button" @click="rotatePrompts">
|
||||
<i class="pi pi-refresh"></i>
|
||||
<i class="mdi mdi-refresh"></i>
|
||||
<span>换一换</span>
|
||||
</button>
|
||||
</header>
|
||||
@@ -29,11 +29,11 @@
|
||||
<p>今天我最应该关注哪些问题?</p>
|
||||
<time>09:41</time>
|
||||
</div>
|
||||
<span class="chat-avatar user-avatar"><i class="pi pi-user"></i></span>
|
||||
<span class="chat-avatar user-avatar"><i class="mdi mdi-account-outline"></i></span>
|
||||
</div>
|
||||
|
||||
<div class="message-row assistant">
|
||||
<span class="chat-avatar assistant-avatar"><i class="pi pi-sparkles"></i></span>
|
||||
<span class="chat-avatar assistant-avatar"><i class="mdi mdi-sparkles"></i></span>
|
||||
<div class="message-bubble">
|
||||
<p>基于当前数据,你优先关注以下 3 项:</p>
|
||||
<ol>
|
||||
@@ -46,25 +46,25 @@
|
||||
</div>
|
||||
|
||||
<div class="message-row assistant">
|
||||
<span class="chat-avatar assistant-avatar"><i class="pi pi-sparkles"></i></span>
|
||||
<span class="chat-avatar assistant-avatar"><i class="mdi mdi-sparkles"></i></span>
|
||||
<div class="message-bubble">
|
||||
<p>为你生成行动建议:</p>
|
||||
<ul class="action-list">
|
||||
<li><i class="pi pi-exclamation-circle danger"></i><strong>紧急处理:</strong>处理即将超时的 3 笔单据,避免 SLA 逾期。</li>
|
||||
<li><i class="pi pi-info-circle warning"></i><strong>风险关注:</strong>审核市场部高风险差旅报销,重点关注差旅与超标费用。</li>
|
||||
<li><i class="pi pi-arrow-circle-up info"></i><strong>信息补齐:</strong>提醒申请人补齐酒店入住清单,加快审批进度。</li>
|
||||
<li><i class="pi pi-check-circle success"></i><strong>效率优化:</strong>当前审批瓶颈在财务审批,建议分配或优先处理。</li>
|
||||
<li><i class="mdi mdi-alert-circle danger"></i><strong>紧急处理:</strong>处理即将超时的 3 笔单据,避免 SLA 逾期。</li>
|
||||
<li><i class="mdi mdi-information-outline warning"></i><strong>风险关注:</strong>审核市场部高风险差旅报销,重点关注差旅与超标费用。</li>
|
||||
<li><i class="mdi mdi-arrow-up-circle info"></i><strong>信息补齐:</strong>提醒申请人补齐酒店入住清单,加快审批进度。</li>
|
||||
<li><i class="mdi mdi-check-circle success"></i><strong>效率优化:</strong>当前审批瓶颈在财务审批,建议分配或优先处理。</li>
|
||||
</ul>
|
||||
<time>09:42</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="message in messages" :key="message.id" class="message-row" :class="message.role === 'user' ? 'user' : 'assistant'">
|
||||
<span v-if="message.role !== 'user'" class="chat-avatar assistant-avatar"><i class="pi pi-sparkles"></i></span>
|
||||
<span v-if="message.role !== 'user'" class="chat-avatar assistant-avatar"><i class="mdi mdi-sparkles"></i></span>
|
||||
<div class="message-bubble">
|
||||
<p>{{ message.text }}</p>
|
||||
</div>
|
||||
<span v-if="message.role === 'user'" class="chat-avatar user-avatar"><i class="pi pi-user"></i></span>
|
||||
<span v-if="message.role === 'user'" class="chat-avatar user-avatar"><i class="mdi mdi-account-outline"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,19 +79,19 @@
|
||||
<div class="composer-actions">
|
||||
<div class="input-tools">
|
||||
<button type="button" aria-label="上传附件" @click="uploadInput?.click()">
|
||||
<i class="pi pi-paperclip"></i>
|
||||
<i class="mdi mdi-paperclip"></i>
|
||||
</button>
|
||||
<button type="button" aria-label="上传图片" @click="uploadInput?.click()">
|
||||
<i class="pi pi-image"></i>
|
||||
<i class="mdi mdi-image-outline"></i>
|
||||
</button>
|
||||
<button type="button" aria-label="语音输入">
|
||||
<i class="pi pi-microphone"></i>
|
||||
<i class="mdi mdi-microphone"></i>
|
||||
</button>
|
||||
<input ref="uploadInput" class="sr-only" type="file" multiple @change="emit('upload', $event)" />
|
||||
</div>
|
||||
<span class="counter">{{ draft.length }}/2000</span>
|
||||
<button class="send-btn" type="button" aria-label="发送消息" @click="emit('send')">
|
||||
<i class="pi pi-send"></i>
|
||||
<i class="mdi mdi-send"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,13 +115,13 @@
|
||||
<article class="insight-card panel">
|
||||
<header>
|
||||
<h3>B. 场景建议</h3>
|
||||
<button type="button">查看全部 <i class="pi pi-angle-right"></i></button>
|
||||
<button type="button">查看全部 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<div class="suggestion-list">
|
||||
<button v-for="item in suggestions" :key="item.text" type="button" @click="applyPrompt(item.text)">
|
||||
<i :class="item.icon"></i>
|
||||
<span>{{ item.text }}</span>
|
||||
<i class="pi pi-angle-right"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
@@ -142,7 +142,7 @@
|
||||
<header>
|
||||
<h3>D. 最近提问 / 常用问题</h3>
|
||||
<button type="button" @click="rotatePrompts">
|
||||
<i class="pi pi-refresh"></i>
|
||||
<i class="mdi mdi-refresh"></i>
|
||||
<span>换一换</span>
|
||||
</button>
|
||||
</header>
|
||||
@@ -176,14 +176,14 @@ const uploadInput = ref(null)
|
||||
const promptPage = ref(0)
|
||||
|
||||
const prompts = [
|
||||
{ icon: 'pi pi-chart-line', text: '今天有哪些关键指标异常?' },
|
||||
{ icon: 'pi pi-file-check', text: '哪些单据最需要优先处理?' },
|
||||
{ icon: 'pi pi-shield', text: '高风险报销主要集中在哪些部门?' },
|
||||
{ icon: 'pi pi-clock', text: '本周审批效率相比昨天如何?' },
|
||||
{ icon: 'pi pi-lightbulb', text: '给我当前报销场景的处理建议' },
|
||||
{ icon: 'pi pi-building', text: '生成一份运营简报' },
|
||||
{ icon: 'pi pi-filter', text: '找出即将超时的单据' },
|
||||
{ icon: 'pi pi-wallet', text: '分析本月预算执行压力' }
|
||||
{ icon: 'mdi mdi-chart-line', text: '今天有哪些关键指标异常?' },
|
||||
{ icon: 'mdi mdi-file-document-outline-check', text: '哪些单据最需要优先处理?' },
|
||||
{ icon: 'mdi mdi-shield-outline', text: '高风险报销主要集中在哪些部门?' },
|
||||
{ icon: 'mdi mdi-clock-outline', text: '本周审批效率相比昨天如何?' },
|
||||
{ icon: 'mdi mdi-lightbulb-outline', text: '给我当前报销场景的处理建议' },
|
||||
{ icon: 'mdi mdi-office-building', text: '生成一份运营简报' },
|
||||
{ icon: 'mdi mdi-filter-outline', text: '找出即将超时的单据' },
|
||||
{ icon: 'mdi mdi-wallet', text: '分析本月预算执行压力' }
|
||||
]
|
||||
|
||||
const visiblePrompts = computed(() => {
|
||||
@@ -192,23 +192,23 @@ const visiblePrompts = computed(() => {
|
||||
})
|
||||
|
||||
const focusItems = [
|
||||
{ icon: 'pi pi-star-fill', tone: 'danger', label: '3 单即将超时', value: '30 分钟内超时' },
|
||||
{ icon: 'pi pi-exclamation-triangle', tone: 'warning', label: '市场部高风险占比最高', value: '高风险 2 单' },
|
||||
{ icon: 'pi pi-arrow-right-arrow-left', tone: 'info', label: '重复报销风险 1 笔', value: '待核查' },
|
||||
{ icon: 'pi pi-check-circle', tone: 'success', label: '1 笔缺失附件', value: '待补充' }
|
||||
{ icon: 'mdi mdi-star', tone: 'danger', label: '3 单即将超时', value: '30 分钟内超时' },
|
||||
{ icon: 'mdi mdi-alert', tone: 'warning', label: '市场部高风险占比最高', value: '高风险 2 单' },
|
||||
{ icon: 'mdi mdi-swap-horizontal', tone: 'info', label: '重复报销风险 1 笔', value: '待核查' },
|
||||
{ icon: 'mdi mdi-check-circle', tone: 'success', label: '1 笔缺失附件', value: '待补充' }
|
||||
]
|
||||
|
||||
const suggestions = [
|
||||
{ icon: 'pi pi-send', text: '优先处理差旅报销(占待审 62%)' },
|
||||
{ icon: 'pi pi-file-plus', text: '先补齐缺失附件再提交审批' },
|
||||
{ icon: 'pi pi-car', text: '对超标出租车费用要求补充说明' }
|
||||
{ icon: 'mdi mdi-send', text: '优先处理差旅报销(占待审 62%)' },
|
||||
{ icon: 'mdi mdi-file-document-outline-plus', text: '先补齐缺失附件再提交审批' },
|
||||
{ icon: 'mdi mdi-car', text: '对超标出租车费用要求补充说明' }
|
||||
]
|
||||
|
||||
const analysisActions = [
|
||||
{ icon: 'pi pi-wave-pulse', text: '异常原因分析' },
|
||||
{ icon: 'pi pi-building', text: '部门对比' },
|
||||
{ icon: 'pi pi-shield', text: '风险趋势' },
|
||||
{ icon: 'pi pi-filter', text: '审批瓶颈' }
|
||||
{ icon: 'mdi mdi-waveform', text: '异常原因分析' },
|
||||
{ icon: 'mdi mdi-office-building', text: '部门对比' },
|
||||
{ icon: 'mdi mdi-shield-outline', text: '风险趋势' },
|
||||
{ icon: 'mdi mdi-filter-outline', text: '审批瓶颈' }
|
||||
]
|
||||
|
||||
const recentQuestions = [
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="document-card">
|
||||
<span>报销单</span>
|
||||
<i></i><i></i><i></i>
|
||||
<b class="doc-check"><i class="pi pi-check"></i></b>
|
||||
<b class="doc-check"><i class="mdi mdi-check"></i></b>
|
||||
</div>
|
||||
|
||||
<img class="shield-art" src="../assets/security-shield.png" alt="" />
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<div class="metric-card risk">
|
||||
<span>风险预警</span>
|
||||
<strong><i class="pi pi-exclamation-triangle"></i> 14 单</strong>
|
||||
<strong><i class="mdi mdi-alert"></i> 14 单</strong>
|
||||
<small>较昨日 <b class="danger">↑ 16.7%</b></small>
|
||||
</div>
|
||||
|
||||
@@ -76,13 +76,13 @@
|
||||
<form class="login-form" @submit.prevent="emit('login', { username, password })">
|
||||
<label class="field">
|
||||
<span class="sr-only">账号</span>
|
||||
<i class="pi pi-user"></i>
|
||||
<i class="mdi mdi-account-outline"></i>
|
||||
<input v-model="username" type="text" placeholder="请输入账号 / 邮箱 / 手机号" autocomplete="username" required />
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span class="sr-only">密码</span>
|
||||
<i class="pi pi-lock"></i>
|
||||
<i class="mdi mdi-lock-outline"></i>
|
||||
<input
|
||||
v-model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
@@ -96,16 +96,16 @@
|
||||
:aria-label="showPassword ? '隐藏密码' : '显示密码'"
|
||||
@click="showPassword = !showPassword"
|
||||
>
|
||||
<i :class="showPassword ? 'pi pi-eye' : 'pi pi-eye-slash'"></i>
|
||||
<i :class="showPassword ? 'mdi mdi-eye' : 'mdi mdi-eye-slash'"></i>
|
||||
</button>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span class="sr-only">企业或租户</span>
|
||||
<i class="pi pi-building"></i>
|
||||
<i class="mdi mdi-office-building"></i>
|
||||
<input v-model="tenant" type="text" placeholder="请输入企业 / 租户(选填)" />
|
||||
<button class="field-icon-btn" type="button" aria-label="展开企业列表">
|
||||
<i class="pi pi-angle-down"></i>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</label>
|
||||
|
||||
@@ -122,13 +122,13 @@
|
||||
<div class="divider"><span>或</span></div>
|
||||
|
||||
<button class="sso-btn" type="button" @click="emit('sso-login')">
|
||||
<i class="pi pi-shield"></i>
|
||||
<i class="mdi mdi-shield-outline"></i>
|
||||
<span>SSO 单点登录</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<footer class="security-note">
|
||||
<i class="pi pi-lock"></i>
|
||||
<i class="mdi mdi-lock-outline"></i>
|
||||
<span>安全登录 · 数据加密传输 · 如需帮助请联系管理员</span>
|
||||
</footer>
|
||||
</section>
|
||||
@@ -147,9 +147,9 @@ const remember = ref(true)
|
||||
const showPassword = ref(false)
|
||||
|
||||
const features = [
|
||||
{ title: '智能审单', desc: 'AI 自动识别票据与规则,提升准确率与效率', icon: 'pi pi-file', tone: 'green' },
|
||||
{ title: '异常预警', desc: '多维风险识别与预警,主动防控风险', icon: 'pi pi-bell', tone: 'red' },
|
||||
{ title: 'SLA 监控', desc: '实时监控服务水平协议,保障审批及时性', icon: 'pi pi-sync', tone: 'blue' }
|
||||
{ title: '智能审单', desc: 'AI 自动识别票据与规则,提升准确率与效率', icon: 'mdi mdi-file-document-outline', tone: 'green' },
|
||||
{ title: '异常预警', desc: '多维风险识别与预警,主动防控风险', icon: 'mdi mdi-bell-outline', tone: 'red' },
|
||||
{ title: 'SLA 监控', desc: '实时监控服务水平协议,保障审批及时性', icon: 'mdi mdi-sync', tone: 'blue' }
|
||||
]
|
||||
|
||||
const LogoMark = {
|
||||
@@ -593,7 +593,7 @@ const LogoMark = {
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.field > .pi {
|
||||
.field > .mdi {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
color: #64748b;
|
||||
@@ -740,7 +740,7 @@ const LogoMark = {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.security-note .pi {
|
||||
.security-note .mdi {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,23 +5,19 @@
|
||||
v-for="metric in kpiMetrics"
|
||||
:key="metric.label"
|
||||
class="kpi-card panel"
|
||||
:style="{ '--accent': metric.accent }"
|
||||
:style="{ '--accent': metric.accent, '--delay': `${metric.delay}ms` }"
|
||||
>
|
||||
<div class="kpi-top">
|
||||
<div class="kpi-icon">
|
||||
<i :class="metric.icon"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ metric.label }}</p>
|
||||
<strong>{{ metric.displayValue }}</strong>
|
||||
</div>
|
||||
<div class="kpi-head">
|
||||
<span class="kpi-icon"><i :class="metric.icon"></i></span>
|
||||
<span class="kpi-label">{{ metric.label }}</span>
|
||||
</div>
|
||||
<div class="kpi-bottom" :class="metric.trend">
|
||||
<span>
|
||||
<i :class="metric.trend === 'down' ? 'pi pi-arrow-down' : 'pi pi-arrow-up'"></i>
|
||||
<strong class="kpi-value">{{ metric.displayValue }}</strong>
|
||||
<div class="kpi-trend">
|
||||
<span class="kpi-badge" :class="metric.trend">
|
||||
<i :class="metric.trend === 'down' ? 'mdi mdi-arrow-down' : 'mdi mdi-arrow-up'"></i>
|
||||
{{ metric.changeText }}
|
||||
</span>
|
||||
<small>{{ metric.delta }}</small>
|
||||
<span class="kpi-delta">{{ metric.delta }}</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@@ -29,7 +25,7 @@
|
||||
<div class="content-grid top-grid">
|
||||
<article class="panel dashboard-card trend-panel">
|
||||
<div class="card-head">
|
||||
<h3>报销申请与审批趋势 <i class="pi pi-info-circle"></i></h3>
|
||||
<h3>报销申请与审批趋势 <i class="mdi mdi-information-outline"></i></h3>
|
||||
<select v-model="activeTrendRange" class="card-select" aria-label="趋势时间范围">
|
||||
<option v-for="range in trendRanges" :key="range">{{ range }}</option>
|
||||
</select>
|
||||
@@ -45,7 +41,7 @@
|
||||
|
||||
<article class="panel dashboard-card donut-panel">
|
||||
<div class="card-head">
|
||||
<h3>费用结构 <i class="pi pi-info-circle"></i></h3>
|
||||
<h3>费用结构 <i class="mdi mdi-information-outline"></i></h3>
|
||||
</div>
|
||||
<DonutChart :items="spendLegend" center-value="¥361.6K" center-label="待处理金额" />
|
||||
<p class="panel-note">* 百分比为占待处理金额比例</p>
|
||||
@@ -53,7 +49,7 @@
|
||||
|
||||
<article class="panel dashboard-card donut-panel">
|
||||
<div class="card-head">
|
||||
<h3>风险异常分布 <i class="pi pi-info-circle"></i></h3>
|
||||
<h3>风险异常分布 <i class="mdi mdi-information-outline"></i></h3>
|
||||
</div>
|
||||
<DonutChart :items="riskLegend" :center-value="`${riskTotal}`" center-label="异常预警单" />
|
||||
<p class="panel-note">* 近30天数据</p>
|
||||
@@ -63,7 +59,7 @@
|
||||
<div class="content-grid bottom-grid">
|
||||
<article class="panel dashboard-card rank-panel">
|
||||
<div class="card-head">
|
||||
<h3>部门报销排行(待处理金额) <i class="pi pi-info-circle"></i></h3>
|
||||
<h3>部门报销排行(待处理金额) <i class="mdi mdi-information-outline"></i></h3>
|
||||
<select v-model="activeDepartmentRange" class="card-select" aria-label="部门排行时间范围">
|
||||
<option v-for="range in departmentRangeOptions" :key="range">{{ range }}</option>
|
||||
</select>
|
||||
@@ -74,11 +70,16 @@
|
||||
|
||||
<article class="panel dashboard-card bottleneck-panel">
|
||||
<div class="card-head">
|
||||
<h3>审批瓶颈(平均处理时长) <i class="pi pi-info-circle"></i></h3>
|
||||
<h3>审批瓶颈(平均处理时长) <i class="mdi mdi-information-outline"></i></h3>
|
||||
</div>
|
||||
|
||||
<div class="bottleneck-list">
|
||||
<div v-for="item in bottlenecks" :key="item.name" class="bottleneck-row">
|
||||
<div
|
||||
v-for="(item, index) in bottlenecks"
|
||||
:key="item.name"
|
||||
class="bottleneck-row"
|
||||
:style="{ '--delay': `${index * 70}ms` }"
|
||||
>
|
||||
<div class="reviewer">
|
||||
<div class="reviewer-avatar">{{ item.avatar }}</div>
|
||||
<div>
|
||||
@@ -93,12 +94,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="text-link">查看全部 <i class="pi pi-angle-right"></i></button>
|
||||
<button type="button" class="text-link">查看全部 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</article>
|
||||
|
||||
<article class="panel dashboard-card budget-panel">
|
||||
<div class="card-head">
|
||||
<h3>预算执行率(本月) <i class="pi pi-info-circle"></i></h3>
|
||||
<h3>预算执行率(本月) <i class="mdi mdi-information-outline"></i></h3>
|
||||
</div>
|
||||
|
||||
<GaugeChart
|
||||
@@ -108,7 +109,7 @@
|
||||
:left="budgetSummary.left"
|
||||
/>
|
||||
|
||||
<button type="button" class="text-link">查看详情 <i class="pi pi-angle-right"></i></button>
|
||||
<button type="button" class="text-link">查看详情 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@@ -163,18 +164,23 @@ const formatCompact = (value) => {
|
||||
|
||||
const formatCurrency = (value) => formatCompact(value)
|
||||
|
||||
const kpiMetrics = computed(() => metricBlueprints.map((metric) => {
|
||||
const formatMetricValue = (metric, value) => {
|
||||
if (metric.key === 'pendingAmount') return formatCurrency(Math.round(value))
|
||||
if (metric.key === 'avgSla') return `${value.toFixed(1)} ${metric.unit}`
|
||||
if (metric.unit === '%') return `${Math.round(value)} ${metric.unit}`
|
||||
if (metric.unit) return `${Math.round(value)} ${metric.unit}`
|
||||
return `${Math.round(value)}`
|
||||
}
|
||||
|
||||
const kpiMetrics = computed(() => metricBlueprints.map((metric, index) => {
|
||||
const rawValue = demoTotals[metric.key]
|
||||
const displayValue = metric.key === 'pendingAmount'
|
||||
? formatCurrency(rawValue)
|
||||
: metric.unit && !String(rawValue).endsWith(metric.unit)
|
||||
? `${rawValue} ${metric.unit}`
|
||||
: `${rawValue}`
|
||||
const displayValue = formatMetricValue(metric, rawValue)
|
||||
|
||||
return {
|
||||
...metric,
|
||||
displayValue,
|
||||
changeText: metric.change
|
||||
changeText: metric.change,
|
||||
delay: index * 55
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -222,86 +228,104 @@ const rankedDepartments = computed(() => {
|
||||
}
|
||||
|
||||
.kpi-card {
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
padding: 20px 20px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
border-left: 3px solid var(--accent);
|
||||
animation: dashboardItemIn 520ms var(--ease) both;
|
||||
animation-delay: var(--delay, 0ms);
|
||||
transition: box-shadow 200ms ease, transform 200ms ease;
|
||||
}
|
||||
|
||||
.kpi-top {
|
||||
flex: 1;
|
||||
.kpi-card:hover {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.kpi-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-top > div {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
.kpi-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--accent) 10%, white);
|
||||
color: var(--accent);
|
||||
font-size: 18px;
|
||||
flex: 0 0 auto;
|
||||
animation: iconPop 560ms var(--ease) both;
|
||||
animation-delay: calc(var(--delay, 0ms) + 100ms);
|
||||
}
|
||||
|
||||
.kpi-top p {
|
||||
margin: 0 0 6px;
|
||||
.kpi-label {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.kpi-top strong {
|
||||
.kpi-value {
|
||||
display: block;
|
||||
color: #1e293b;
|
||||
font-size: clamp(18px, 1.6vw, 22px);
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
color: #0f172a;
|
||||
font-size: clamp(20px, 1.6vw, 26px);
|
||||
line-height: 1;
|
||||
font-weight: 800;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 10px;
|
||||
background: color-mix(in srgb, var(--accent) 10%, white);
|
||||
color: var(--accent);
|
||||
font-size: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.kpi-bottom {
|
||||
.kpi-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.kpi-bottom span {
|
||||
.kpi-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
gap: 3px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.kpi-bottom.up span {
|
||||
.kpi-badge.up {
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.kpi-bottom.down span {
|
||||
.kpi-badge.down {
|
||||
background: rgba(22, 163, 74, 0.08);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.kpi-bottom small {
|
||||
.kpi-badge .mdi {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kpi-delta {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
@@ -313,8 +337,16 @@ const rankedDepartments = computed(() => {
|
||||
.dashboard-card {
|
||||
padding: 20px;
|
||||
transition: box-shadow 200ms ease, transform 200ms ease;
|
||||
animation: dashboardItemIn 560ms var(--ease) both;
|
||||
}
|
||||
|
||||
.top-grid .dashboard-card:nth-child(1) { animation-delay: 80ms; }
|
||||
.top-grid .dashboard-card:nth-child(2) { animation-delay: 150ms; }
|
||||
.top-grid .dashboard-card:nth-child(3) { animation-delay: 220ms; }
|
||||
.bottom-grid .dashboard-card:nth-child(1) { animation-delay: 290ms; }
|
||||
.bottom-grid .dashboard-card:nth-child(2) { animation-delay: 360ms; }
|
||||
.bottom-grid .dashboard-card:nth-child(3) { animation-delay: 430ms; }
|
||||
|
||||
.dashboard-card:hover {
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||||
transform: translateY(-1px);
|
||||
@@ -346,7 +378,7 @@ const rankedDepartments = computed(() => {
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.card-head .pi {
|
||||
.card-head .mdi {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
vertical-align: 1px;
|
||||
@@ -393,6 +425,8 @@ const rankedDepartments = computed(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
animation: listRowIn 460ms var(--ease) both;
|
||||
animation-delay: var(--delay, 0ms);
|
||||
}
|
||||
|
||||
.reviewer {
|
||||
@@ -472,6 +506,51 @@ const rankedDepartments = computed(() => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes dashboardItemIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes listRowIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes iconPop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(.82);
|
||||
}
|
||||
70% {
|
||||
opacity: 1;
|
||||
transform: scale(1.04);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.kpi-card,
|
||||
.dashboard-card,
|
||||
.bottleneck-row {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1320px) {
|
||||
.kpi-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<article v-for="item in metrics" :key="item.label" class="metric-card" :style="{ '--accent': item.accent }">
|
||||
<span class="metric-icon"><i :class="item.icon"></i></span>
|
||||
<div>
|
||||
<p>{{ item.label }} <i v-if="item.help" class="pi pi-question-circle"></i></p>
|
||||
<p>{{ item.label }} <i v-if="item.help" class="mdi mdi-help-circle-outline"></i></p>
|
||||
<strong>{{ item.value }}</strong>
|
||||
<small>{{ item.meta }}</small>
|
||||
</div>
|
||||
@@ -21,9 +21,9 @@
|
||||
<div class="library-body">
|
||||
<aside class="folder-rail">
|
||||
<label class="folder-search">
|
||||
<i class="pi pi-search"></i>
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input type="search" placeholder="搜索文件夹" />
|
||||
<button type="button" aria-label="新增文件夹"><i class="pi pi-plus"></i></button>
|
||||
<button type="button" aria-label="新增文件夹"><i class="mdi mdi-plus"></i></button>
|
||||
</label>
|
||||
|
||||
<nav class="folder-tree" aria-label="知识库文件夹">
|
||||
@@ -35,14 +35,14 @@
|
||||
</nav>
|
||||
|
||||
<button class="new-folder-btn" type="button">
|
||||
<i class="pi pi-plus"></i>
|
||||
<i class="mdi mdi-plus"></i>
|
||||
<span>新建文件夹</span>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<section class="document-area">
|
||||
<div class="upload-zone">
|
||||
<i class="pi pi-cloud-upload"></i>
|
||||
<i class="mdi mdi-cloud-upload"></i>
|
||||
<strong>拖拽文档到此处,或点击上传</strong>
|
||||
<span>支持 PDF / Word / Excel / PPT 文档,单个文件不超过 100MB</span>
|
||||
</div>
|
||||
@@ -53,7 +53,7 @@
|
||||
<tr>
|
||||
<th>文件名称</th>
|
||||
<th>标签</th>
|
||||
<th>上传时间 <i class="pi pi-arrow-down"></i></th>
|
||||
<th>上传时间 <i class="mdi mdi-arrow-down"></i></th>
|
||||
<th>版本</th>
|
||||
<th>状态</th>
|
||||
<th>上传人</th>
|
||||
@@ -75,7 +75,7 @@
|
||||
<td>{{ doc.version }}</td>
|
||||
<td><span class="state-tag" :class="doc.stateTone">{{ doc.state }}</span></td>
|
||||
<td>{{ doc.owner }}</td>
|
||||
<td><button class="more-btn" type="button" aria-label="更多操作"><i class="pi pi-ellipsis-h"></i></button></td>
|
||||
<td><button class="more-btn" type="button" aria-label="更多操作"><i class="mdi mdi-dots-horizontal"></i></button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -83,12 +83,12 @@
|
||||
|
||||
<footer class="list-foot">
|
||||
<span>共 18 条</span>
|
||||
<button type="button">10条/页 <i class="pi pi-angle-down"></i></button>
|
||||
<button type="button">10条/页 <i class="mdi mdi-chevron-down"></i></button>
|
||||
<div class="pager" aria-label="分页">
|
||||
<button type="button" aria-label="上一页"><i class="pi pi-angle-left"></i></button>
|
||||
<button type="button" aria-label="上一页"><i class="mdi mdi-chevron-left"></i></button>
|
||||
<button class="active" type="button" aria-current="page">1</button>
|
||||
<button type="button">2</button>
|
||||
<button type="button" aria-label="下一页"><i class="pi pi-angle-right"></i></button>
|
||||
<button type="button" aria-label="下一页"><i class="mdi mdi-chevron-right"></i></button>
|
||||
</div>
|
||||
<label>前往 <input value="1" aria-label="页码" /> 页</label>
|
||||
</footer>
|
||||
@@ -100,7 +100,7 @@
|
||||
<aside class="analytics-column">
|
||||
<article class="ops-card panel">
|
||||
<header class="card-head">
|
||||
<h2>知识运营概览 <i class="pi pi-question-circle"></i></h2>
|
||||
<h2>知识运营概览 <i class="mdi mdi-help-circle-outline"></i></h2>
|
||||
</header>
|
||||
<div class="ops-grid">
|
||||
<div v-for="item in opsMetrics" :key="item.label" class="ops-item" :style="{ '--accent': item.accent }">
|
||||
@@ -116,8 +116,8 @@
|
||||
|
||||
<article class="top-card panel">
|
||||
<header class="card-head">
|
||||
<h2>热门问题 TOP5 <i class="pi pi-question-circle"></i></h2>
|
||||
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
|
||||
<h2>热门问题 TOP5 <i class="mdi mdi-help-circle-outline"></i></h2>
|
||||
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<ol class="hot-list">
|
||||
<li v-for="item in hotQuestions" :key="item.title">
|
||||
@@ -130,28 +130,28 @@
|
||||
|
||||
<article class="feedback-card panel">
|
||||
<header class="card-head">
|
||||
<h2>用户点赞最多 <i class="pi pi-question-circle"></i></h2>
|
||||
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
|
||||
<h2>用户点赞最多 <i class="mdi mdi-help-circle-outline"></i></h2>
|
||||
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<ul class="feedback-list positive">
|
||||
<li v-for="(item, idx) in likedAnswers" :key="item.title">
|
||||
<span class="rank-badge" :class="idx === 0 ? 'hot' : idx < 3 ? 'warm' : 'normal'">{{ idx + 1 }}</span>
|
||||
<span>{{ item.title }}</span>
|
||||
<b><i class="pi pi-thumbs-up-fill"></i>{{ item.count }}</b>
|
||||
<b><i class="mdi mdi-thumb-up"></i>{{ item.count }}</b>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="feedback-card panel">
|
||||
<header class="card-head">
|
||||
<h2>用户点踩较多 / 待优化 <i class="pi pi-question-circle"></i></h2>
|
||||
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
|
||||
<h2>用户点踩较多 / 待优化 <i class="mdi mdi-help-circle-outline"></i></h2>
|
||||
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<ul class="feedback-list negative">
|
||||
<li v-for="(item, idx) in dislikedAnswers" :key="item.title">
|
||||
<span class="rank-badge" :class="idx === 0 ? 'hot' : idx < 3 ? 'warm' : 'normal'">{{ idx + 1 }}</span>
|
||||
<span>{{ item.title }}</span>
|
||||
<b><i class="pi pi-thumbs-down-fill"></i>{{ item.count }}</b>
|
||||
<b><i class="mdi mdi-thumb-down"></i>{{ item.count }}</b>
|
||||
<em>待优化</em>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -159,8 +159,8 @@
|
||||
|
||||
<article class="trend-card panel">
|
||||
<header class="card-head">
|
||||
<h2>近7天提问趋势 <i class="pi pi-question-circle"></i></h2>
|
||||
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
|
||||
<h2>近7天提问趋势 <i class="mdi mdi-help-circle-outline"></i></h2>
|
||||
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<svg class="trend-chart" viewBox="0 0 420 190" role="img" aria-label="近7天提问趋势">
|
||||
<line v-for="tick in chartTicks" :key="tick" x1="38" :y1="tick.y" x2="398" :y2="tick.y" />
|
||||
@@ -173,8 +173,8 @@
|
||||
|
||||
<article class="recent-card panel">
|
||||
<header class="card-head">
|
||||
<h2>最近更新知识 <i class="pi pi-question-circle"></i></h2>
|
||||
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
|
||||
<h2>最近更新知识 <i class="mdi mdi-help-circle-outline"></i></h2>
|
||||
<button type="button">更多 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<ul class="recent-list">
|
||||
<li v-for="item in recentKnowledge" :key="item.name">
|
||||
@@ -193,40 +193,40 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
const metrics = [
|
||||
{ label: '文档总数', value: '1,248', meta: '较上周 +68', icon: 'pi pi-file', accent: '#10b981', help: true },
|
||||
{ label: '文件夹总数', value: '36', meta: '较上周 +2', icon: 'pi pi-folder', accent: '#3b82f6' },
|
||||
{ label: '问答总量', value: '8,562', meta: '较上周 +321', icon: 'pi pi-comments', accent: '#8b5cf6' },
|
||||
{ label: '知识命中率', value: '87.3%', meta: '较上周 +1.2%', icon: 'pi pi-bullseye', accent: '#f59e0b' }
|
||||
{ label: '文档总数', value: '1,248', meta: '较上周 +68', icon: 'mdi mdi-file-document-outline', accent: '#10b981', help: true },
|
||||
{ label: '文件夹总数', value: '36', meta: '较上周 +2', icon: 'mdi mdi-folder', accent: '#3b82f6' },
|
||||
{ label: '问答总量', value: '8,562', meta: '较上周 +321', icon: 'mdi mdi-comment-text-multiple-outline', accent: '#8b5cf6' },
|
||||
{ label: '知识命中率', value: '87.3%', meta: '较上周 +1.2%', icon: 'mdi mdi-bullseye-arrow', accent: '#f59e0b' }
|
||||
]
|
||||
|
||||
const folders = [
|
||||
{ name: '财务知识库', count: 36, icon: 'pi pi-folder', active: false },
|
||||
{ name: '制度政策', count: 8, icon: 'pi pi-folder', active: false },
|
||||
{ name: '报销制度', count: 12, icon: 'pi pi-folder-open', active: false },
|
||||
{ name: '差旅规范', count: 18, icon: 'pi pi-folder', active: true },
|
||||
{ name: '发票管理', count: 14, icon: 'pi pi-folder', active: false },
|
||||
{ name: '税务合规', count: 16, icon: 'pi pi-folder', active: false },
|
||||
{ name: '预算管理', count: 9, icon: 'pi pi-folder', active: false },
|
||||
{ name: '财务共享', count: 7, icon: 'pi pi-folder', active: false },
|
||||
{ name: '培训资料', count: 6, icon: 'pi pi-folder', active: false },
|
||||
{ name: '常见问答', count: 11, icon: 'pi pi-folder', active: false }
|
||||
{ name: '财务知识库', count: 36, icon: 'mdi mdi-folder', active: false },
|
||||
{ name: '制度政策', count: 8, icon: 'mdi mdi-folder', active: false },
|
||||
{ name: '报销制度', count: 12, icon: 'mdi mdi-folder-open', active: false },
|
||||
{ name: '差旅规范', count: 18, icon: 'mdi mdi-folder', active: true },
|
||||
{ name: '发票管理', count: 14, icon: 'mdi mdi-folder', active: false },
|
||||
{ name: '税务合规', count: 16, icon: 'mdi mdi-folder', active: false },
|
||||
{ name: '预算管理', count: 9, icon: 'mdi mdi-folder', active: false },
|
||||
{ name: '财务共享', count: 7, icon: 'mdi mdi-folder', active: false },
|
||||
{ name: '培训资料', count: 6, icon: 'mdi mdi-folder', active: false },
|
||||
{ name: '常见问答', count: 11, icon: 'mdi mdi-folder', active: false }
|
||||
]
|
||||
|
||||
const documents = [
|
||||
{ name: '差旅报销管理办法(2024版)', tag: '差旅 / 制度', time: '2024-05-12 14:35', version: 'v3.2', state: '已生效', stateTone: 'success', owner: '张明', icon: 'pi pi-file-pdf pdf' },
|
||||
{ name: '发票查验规范及操作指引', tag: '发票 / 操作', time: '2024-05-10 10:22', version: 'v1.5', state: '已生效', stateTone: 'success', owner: '李娜', icon: 'pi pi-file-word word' },
|
||||
{ name: '费用报销标准细则(2024)', tag: '报销 / 标准', time: '2024-05-08 09:16', version: 'v2.1', state: '已生效', stateTone: 'success', owner: '王磊', icon: 'pi pi-file-pdf pdf' },
|
||||
{ name: '差旅费用标准对照表(国内)', tag: '差旅 / 标准', time: '2024-05-05 08:20', version: 'v1.3', state: '审批中', stateTone: 'warning', owner: '陈杰', icon: 'pi pi-file-excel excel' },
|
||||
{ name: '借款管理办法及流程', tag: '借款 / 流程', time: '2024-05-03 11:05', version: 'v1.0', state: '已生效', stateTone: 'success', owner: '刘洋', icon: 'pi pi-file-pdf pdf' }
|
||||
{ name: '差旅报销管理办法(2024版)', tag: '差旅 / 制度', time: '2024-05-12 14:35', version: 'v3.2', state: '已生效', stateTone: 'success', owner: '张明', icon: 'mdi mdi-file-document-outline-pdf pdf' },
|
||||
{ name: '发票查验规范及操作指引', tag: '发票 / 操作', time: '2024-05-10 10:22', version: 'v1.5', state: '已生效', stateTone: 'success', owner: '李娜', icon: 'mdi mdi-file-document-outline-word word' },
|
||||
{ name: '费用报销标准细则(2024)', tag: '报销 / 标准', time: '2024-05-08 09:16', version: 'v2.1', state: '已生效', stateTone: 'success', owner: '王磊', icon: 'mdi mdi-file-document-outline-pdf pdf' },
|
||||
{ name: '差旅费用标准对照表(国内)', tag: '差旅 / 标准', time: '2024-05-05 08:20', version: 'v1.3', state: '审批中', stateTone: 'warning', owner: '陈杰', icon: 'mdi mdi-file-document-outline-excel excel' },
|
||||
{ name: '借款管理办法及流程', tag: '借款 / 流程', time: '2024-05-03 11:05', version: 'v1.0', state: '已生效', stateTone: 'success', owner: '刘洋', icon: 'mdi mdi-file-document-outline-pdf pdf' }
|
||||
]
|
||||
|
||||
const opsMetrics = [
|
||||
{ label: '本周新增问题', value: '328', meta: '较上周 +46', icon: 'pi pi-question-circle', accent: '#3b82f6' },
|
||||
{ label: '已解决问题', value: '286', meta: '解决率 87.2%', icon: 'pi pi-check', accent: '#10b981' },
|
||||
{ label: '平均响应时长', value: '2.4h', meta: '较上周 -0.6h', icon: 'pi pi-clock', accent: '#3b82f6' },
|
||||
{ label: '用户满意度', value: '91.2%', meta: '较上周 +2.1%', icon: 'pi pi-face-smile', accent: '#10b981' },
|
||||
{ label: '收藏次数', value: '1,236', meta: '较上周 +128', icon: 'pi pi-star', accent: '#f59e0b' },
|
||||
{ label: '点赞总数', value: '3,582', meta: '较上周 +312', icon: 'pi pi-thumbs-up', accent: '#f59e0b' }
|
||||
{ label: '本周新增问题', value: '328', meta: '较上周 +46', icon: 'mdi mdi-help-circle-outline', accent: '#3b82f6' },
|
||||
{ label: '已解决问题', value: '286', meta: '解决率 87.2%', icon: 'mdi mdi-check', accent: '#10b981' },
|
||||
{ label: '平均响应时长', value: '2.4h', meta: '较上周 -0.6h', icon: 'mdi mdi-clock-outline', accent: '#3b82f6' },
|
||||
{ label: '用户满意度', value: '91.2%', meta: '较上周 +2.1%', icon: 'mdi mdi-emoticon-happy-outline', accent: '#10b981' },
|
||||
{ label: '收藏次数', value: '1,236', meta: '较上周 +128', icon: 'mdi mdi-star-outline', accent: '#f59e0b' },
|
||||
{ label: '点赞总数', value: '3,582', meta: '较上周 +312', icon: 'mdi mdi-thumb-up-outline', accent: '#f59e0b' }
|
||||
]
|
||||
|
||||
const hotQuestions = [
|
||||
@@ -277,9 +277,9 @@ const chartTicks = [
|
||||
const trendPoints = computed(() => trendData.map((point) => `${point.x},${point.y}`).join(' '))
|
||||
|
||||
const recentKnowledge = [
|
||||
{ name: '差旅报销管理办法(2024版)v3.2', time: '05-12 14:35', icon: 'pi pi-file-pdf pdf' },
|
||||
{ name: '发票查验规范及操作指引 v1.5', time: '05-10 10:22', icon: 'pi pi-file-word word' },
|
||||
{ name: '费用报销标准细则(2024)v2.1', time: '05-08 09:16', icon: 'pi pi-file-pdf pdf' }
|
||||
{ name: '差旅报销管理办法(2024版)v3.2', time: '05-12 14:35', icon: 'mdi mdi-file-document-outline-pdf pdf' },
|
||||
{ name: '发票查验规范及操作指引 v1.5', time: '05-10 10:22', icon: 'mdi mdi-file-document-outline-word word' },
|
||||
{ name: '费用报销标准细则(2024)v2.1', time: '05-08 09:16', icon: 'mdi mdi-file-document-outline-pdf pdf' }
|
||||
]
|
||||
</script>
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
<article class="travel-list panel">
|
||||
<header class="list-head">
|
||||
<h2>我的差旅报销单</h2>
|
||||
</header>
|
||||
<h2>我的差旅报销单</h2>
|
||||
</header>
|
||||
|
||||
<nav class="status-tabs" aria-label="差旅报销状态">
|
||||
<button
|
||||
@@ -30,34 +30,79 @@
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="filter-set">
|
||||
<div class="list-search">
|
||||
<i class="mdi mdi-magnify"></i>
|
||||
<input 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="filter === '出差月份' ? 'pi pi-calendar' : 'pi pi-angle-down'"></i>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
<button class="export-btn" type="button">
|
||||
<i class="pi pi-upload"></i>
|
||||
<i class="mdi mdi-upload"></i>
|
||||
<span>导出</span>
|
||||
</button>
|
||||
<button class="create-btn" type="button" @click="emit('createRequest')">
|
||||
<i class="pi pi-plus"></i>
|
||||
<i class="mdi mdi-plus"></i>
|
||||
<span>发起报销</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="hint"><i class="pi pi-info-circle"></i> 点击任意行可查看单据详情</p>
|
||||
<p class="hint"><i class="mdi mdi-information-outline"></i> 点击任意行可查看单据详情</p>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col class="col-id">
|
||||
<col class="col-reason">
|
||||
<col class="col-city">
|
||||
<col class="col-period">
|
||||
<col class="col-apply">
|
||||
<col class="col-amount">
|
||||
<col class="col-node">
|
||||
<col class="col-approval">
|
||||
<col class="col-travel">
|
||||
<col class="col-actions">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>单号</th>
|
||||
<th>出差事由</th>
|
||||
<th>出差城市</th>
|
||||
<th>出差时间</th>
|
||||
<th>申请时间</th>
|
||||
<th>申请金额</th>
|
||||
<th>当前节点</th>
|
||||
<th>审批状态</th>
|
||||
@@ -71,6 +116,7 @@
|
||||
<td>{{ row.reason }}</td>
|
||||
<td>{{ row.city }}</td>
|
||||
<td>{{ row.period }}</td>
|
||||
<td>{{ row.applyTime }}</td>
|
||||
<td>{{ row.amount }}</td>
|
||||
<td>{{ row.node }}</td>
|
||||
<td><span class="status-tag" :class="row.approvalTone">{{ row.approval }}</span></td>
|
||||
@@ -79,7 +125,7 @@
|
||||
<div class="row-actions">
|
||||
<button type="button" @click.stop="emit('ask', row)">查看</button>
|
||||
<button type="button" aria-label="更多操作" @click.stop>
|
||||
<i class="pi pi-ellipsis-h"></i>
|
||||
<i class="mdi mdi-dots-horizontal"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -89,22 +135,20 @@
|
||||
</div>
|
||||
|
||||
<footer class="list-foot">
|
||||
<span class="page-summary">共 26 条,目前第 1 页</span>
|
||||
<span class="page-summary">共 {{ totalCount }} 条,目前第 {{ currentPage }} 页</span>
|
||||
<div class="pager" aria-label="分页">
|
||||
<button class="page-nav" type="button" aria-label="上一页"><i class="pi pi-angle-left"></i></button>
|
||||
<button class="page-number active" type="button" aria-current="page">1</button>
|
||||
<button class="page-number" type="button">2</button>
|
||||
<button class="page-number" type="button">3</button>
|
||||
<button class="page-nav" type="button" aria-label="下一页"><i class="pi pi-angle-right"></i></button>
|
||||
<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>
|
||||
<button class="page-size" type="button">10 条/页 <i class="pi pi-angle-down"></i></button>
|
||||
<button class="page-size" type="button">{{ pageSize }} 条/页 <i class="mdi mdi-chevron-down"></i></button>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
defineProps({
|
||||
filteredRequests: { type: Array, required: true }
|
||||
@@ -114,30 +158,83 @@ const emit = defineEmits(['ask', 'approve', 'reject', 'createRequest'])
|
||||
|
||||
const activeTab = ref('全部')
|
||||
const tabs = ['全部', '待提交', '审批中', '待出行', '已完成']
|
||||
const filters = ['出差月份', '报销状态', '出差城市', '费用类型']
|
||||
const filters = ['报销状态', '出差城市', '费用类型']
|
||||
|
||||
const datePopover = ref(false)
|
||||
const rangeStart = ref('')
|
||||
const rangeEnd = ref('')
|
||||
const appliedStart = ref('')
|
||||
const appliedEnd = ref('')
|
||||
|
||||
const dateRangeLabel = computed(() => {
|
||||
if (appliedStart.value && appliedEnd.value) return `${appliedStart.value} ~ ${appliedEnd.value}`
|
||||
return '选择时间段'
|
||||
})
|
||||
|
||||
function applyDateRange() {
|
||||
if (!rangeStart.value || !rangeEnd.value) return
|
||||
appliedStart.value = rangeStart.value
|
||||
appliedEnd.value = rangeEnd.value
|
||||
datePopover.value = false
|
||||
}
|
||||
|
||||
const kpis = [
|
||||
{ label: '全部单据', value: 26, delta: '+8', trend: 'up good', arrow: 'pi pi-arrow-up', icon: 'pi pi-clipboard', accent: '#10b981' },
|
||||
{ label: '待提交', value: 4, delta: '-1', trend: 'down good', arrow: 'pi pi-arrow-down', icon: 'pi pi-send', accent: '#f59e0b' },
|
||||
{ label: '审批中', value: 7, delta: '+2', trend: 'up bad', arrow: 'pi pi-arrow-up', icon: 'pi pi-clock', accent: '#3b82f6' },
|
||||
{ label: '已完成', value: 15, delta: '+7', trend: 'up good', arrow: 'pi pi-arrow-up', icon: 'pi pi-check', accent: '#10b981' }
|
||||
{ label: '全部单据', value: 30, delta: '+8', trend: 'up good', arrow: 'mdi mdi-arrow-up', icon: 'mdi mdi-clipboard-text-outline', accent: '#10b981' },
|
||||
{ label: '待提交', value: 5, delta: '-1', trend: 'down good', arrow: 'mdi mdi-arrow-down', icon: 'mdi mdi-send', accent: '#f59e0b' },
|
||||
{ label: '审批中', value: 8, delta: '+2', trend: 'up bad', arrow: 'mdi mdi-arrow-up', icon: 'mdi mdi-clock-outline', accent: '#3b82f6' },
|
||||
{ label: '已完成', value: 17, delta: '+7', trend: 'up good', arrow: 'mdi mdi-arrow-up', icon: 'mdi mdi-check', accent: '#10b981' }
|
||||
]
|
||||
|
||||
const rows = [
|
||||
{ id: 'BR240712001', reason: '华东区域客户拜访', city: '上海、苏州、杭州', period: '07-08~07-11 (4天)', amount: '¥3,680.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240711008', reason: '产品培训与交流', city: '深圳', period: '07-10~07-12 (3天)', amount: '¥2,150.00', node: '待提交', approval: '待提交', approvalTone: 'info', travel: '待订机票', travelTone: 'warning' },
|
||||
{ id: 'BR240709005', reason: '客户方案汇报', city: '北京', period: '07-06~07-08 (3天)', amount: '¥1,980.00', node: '财务审核', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240708012', reason: '供应商现场考察', city: '广州', period: '07-04~07-05 (2天)', amount: '¥860.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240707003', reason: '项目启动会', city: '成都', period: '07-01~07-03 (3天)', amount: '¥2,420.00', node: '财务审核', approval: '审批中', approvalTone: 'info', travel: '待预订酒店', travelTone: 'warning' },
|
||||
{ id: 'BR240706009', reason: '客户拜访与市场调研', city: '南京、合肥', period: '06-28~06-30 (3天)', amount: '¥1,750.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240705007', reason: '技术交流会', city: '武汉', period: '06-25~06-26 (2天)', amount: '¥1,120.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240704004', reason: '渠道合作洽谈', city: '西安', period: '06-20~06-21 (2天)', amount: '¥780.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' }
|
||||
{ id: 'BR240715001', reason: '华东区域客户拜访', city: '上海、苏州、杭州', period: '07-14~07-17 (4天)', applyTime: '2024-07-13', amount: '¥4,280.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '待预订酒店', travelTone: 'warning' },
|
||||
{ id: 'BR240714010', reason: '年度战略合作伙伴会议', city: '北京', period: '07-15~07-16 (2天)', applyTime: '2024-07-12', amount: '¥1,860.00', node: '待提交', approval: '待提交', approvalTone: 'info', travel: '待订机票', travelTone: 'warning' },
|
||||
{ id: 'BR240713008', reason: '产品培训与交流', city: '深圳', period: '07-10~07-12 (3天)', applyTime: '2024-07-09', amount: '¥2,150.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240712001', reason: '客户方案汇报', city: '上海', period: '07-08~07-11 (4天)', applyTime: '2024-07-07', amount: '¥3,680.00', node: '财务审核', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240711005', reason: '华南区域市场调研', city: '广州、佛山', period: '07-09~07-11 (3天)', applyTime: '2024-07-06', amount: '¥1,920.00', node: '待提交', approval: '待提交', approvalTone: 'info', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240710003', reason: '供应商现场考察', city: '东莞', period: '07-06~07-07 (2天)', applyTime: '2024-07-05', amount: '¥680.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240709005', reason: '客户方案汇报', city: '北京', period: '07-06~07-08 (3天)', applyTime: '2024-07-05', amount: '¥1,980.00', node: '财务审核', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240708012', reason: '供应商现场考察', city: '广州', period: '07-04~07-05 (2天)', applyTime: '2024-07-03', amount: '¥860.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240707003', reason: '项目启动会', city: '成都', period: '07-01~07-03 (3天)', applyTime: '2024-06-29', amount: '¥2,420.00', node: '财务审核', approval: '审批中', approvalTone: 'info', travel: '待预订酒店', travelTone: 'warning' },
|
||||
{ id: 'BR240706009', reason: '客户拜访与市场调研', city: '南京、合肥', period: '06-28~06-30 (3天)', applyTime: '2024-06-26', amount: '¥1,750.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240705007', reason: '技术交流会', city: '武汉', period: '06-25~06-26 (2天)', applyTime: '2024-06-23', amount: '¥1,120.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240704004', reason: '渠道合作洽谈', city: '西安', period: '06-20~06-21 (2天)', applyTime: '2024-06-18', amount: '¥780.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240703011', reason: '新员工入职培训', city: '长沙', period: '06-18~06-19 (2天)', applyTime: '2024-06-16', amount: '¥920.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240702006', reason: '季度业绩复盘会', city: '杭州', period: '06-15~06-16 (2天)', applyTime: '2024-06-13', amount: '¥1,350.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240701002', reason: '智慧金融峰会参展', city: '上海', period: '06-12~06-14 (3天)', applyTime: '2024-06-10', amount: '¥5,680.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240630009', reason: '西南区域渠道拓展', city: '重庆、贵阳', period: '06-10~06-13 (4天)', applyTime: '2024-06-08', amount: '¥3,450.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240629003', reason: '信息安全合规审计', city: '深圳', period: '06-08~06-09 (2天)', applyTime: '2024-06-06', amount: '¥1,180.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240628007', reason: '产学研合作对接', city: '南京', period: '06-05~06-07 (3天)', applyTime: '2024-06-03', amount: '¥2,260.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240627001', reason: 'ERP系统上线支持', city: '青岛', period: '06-03~06-05 (3天)', applyTime: '2024-06-01', amount: '¥1,960.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240626004', reason: '大客户续约洽谈', city: '天津', period: '06-01~06-02 (2天)', applyTime: '2024-05-29', amount: '¥890.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240625010', reason: '区域销售团队建设', city: '厦门', period: '05-28~05-30 (3天)', applyTime: '2024-05-26', amount: '¥2,780.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240624002', reason: '供应链管理系统演示', city: '苏州', period: '05-25~05-26 (2天)', applyTime: '2024-05-23', amount: '¥650.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240623008', reason: '行业白皮书发布会', city: '北京', period: '05-22~05-23 (2天)', applyTime: '2024-05-20', amount: '¥1,560.00', node: '待提交', approval: '待提交', approvalTone: 'info', travel: '待订机票', travelTone: 'warning' },
|
||||
{ id: 'BR240622005', reason: '跨部门协同工作坊', city: '大连', period: '05-20~05-22 (3天)', applyTime: '2024-05-18', amount: '¥2,340.00', node: '待提交', approval: '待提交', approvalTone: 'info', travel: '无需预订', travelTone: 'neutral' },
|
||||
{ id: 'BR240621003', reason: '数字化转型的客户交流', city: '深圳、珠海', period: '05-16~05-18 (3天)', applyTime: '2024-05-14', amount: '¥3,120.00', node: '待提交', approval: '待提交', approvalTone: 'info', travel: '待预订酒店', travelTone: 'warning' },
|
||||
{ id: 'BR240620006', reason: '年中预算评审会', city: '上海', period: '05-13~05-14 (2天)', applyTime: '2024-05-11', amount: '¥1,480.00', node: '财务审核', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240619001', reason: '医疗行业解决方案展', city: '成都', period: '05-10~05-12 (3天)', applyTime: '2024-05-08', amount: '¥3,860.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240618009', reason: '东北区域客户回访', city: '沈阳、长春', period: '05-06~05-09 (4天)', applyTime: '2024-05-04', amount: '¥4,520.00', node: '部门负责人审批', approval: '审批中', approvalTone: 'info', travel: '待订机票', travelTone: 'warning' },
|
||||
{ id: 'BR240617007', reason: '大数据平台技术对接', city: '杭州', period: '05-03~05-05 (3天)', applyTime: '2024-05-01', amount: '¥2,180.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' },
|
||||
{ id: 'BR240616004', reason: '国际业务合规培训', city: '北京', period: '04-28~04-30 (3天)', applyTime: '2024-04-26', amount: '¥2,960.00', node: '已完成', approval: '已完成', approvalTone: 'success', travel: '已订酒店/机票', travelTone: 'success' }
|
||||
]
|
||||
|
||||
const visibleRows = computed(() => {
|
||||
const currentPage = ref(1)
|
||||
const pageSize = 10
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
if (activeTab.value === '全部') return rows
|
||||
return rows.filter((row) => row.approval === activeTab.value || row.travel.includes(activeTab.value.replace('待出行', '待订')))
|
||||
})
|
||||
|
||||
const totalCount = computed(() => filteredRows.value.length)
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(totalCount.value / pageSize)))
|
||||
|
||||
const visibleRows = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize
|
||||
return filteredRows.value.slice(start, start + pageSize)
|
||||
})
|
||||
|
||||
watch(activeTab, () => { currentPage.value = 1 })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -229,6 +326,42 @@ const visibleRows = computed(() => {
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.list-search {
|
||||
position: relative;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.list-search .mdi {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #64748b;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.list-search input {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
padding: 0 12px 0 36px;
|
||||
border: 1px solid #d7e0ea;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #0f172a;
|
||||
font-size: 13px;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.list-search input::placeholder {
|
||||
color: #8da0b4;
|
||||
}
|
||||
|
||||
.list-search input:focus {
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.14);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
@@ -302,10 +435,128 @@ const visibleRows = computed(() => {
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
min-width: 132px;
|
||||
min-width: 120px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.date-range-filter {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.date-range-trigger {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.date-range-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 110px;
|
||||
}
|
||||
|
||||
.date-range-popover {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 0;
|
||||
width: 320px;
|
||||
z-index: 40;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
border: 1px solid #d7e0ea;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 18px 42px rgba(15, 23, 42, .16);
|
||||
}
|
||||
|
||||
.date-range-popover header,
|
||||
.date-range-popover footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.date-range-popover header strong {
|
||||
color: #0f172a;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.date-range-popover header button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.date-range-popover header button:hover {
|
||||
background: #f1f5f9;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.date-range-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.date-range-fields label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.date-range-fields span {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.date-range-fields input {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
padding: 0 9px;
|
||||
border: 1px solid #d7e0ea;
|
||||
border-radius: 8px;
|
||||
color: #0f172a;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.date-range-fields input:focus {
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, .12);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ghost-btn,
|
||||
.apply-btn {
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.ghost-btn {
|
||||
border: 1px solid #d7e0ea;
|
||||
background: #fff;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
border: 0;
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.apply-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
background: #cbd5e1;
|
||||
}
|
||||
|
||||
.filter-btn:hover,
|
||||
.export-btn:hover,
|
||||
.page-size:hover {
|
||||
@@ -333,12 +584,12 @@ const visibleRows = computed(() => {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hint .pi {
|
||||
.hint .mdi {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
min-height: 0;
|
||||
min-height: 495px;
|
||||
margin-top: 10px;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
@@ -348,19 +599,27 @@ const visibleRows = computed(() => {
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
min-width: 1040px;
|
||||
min-width: 1140px;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
colgroup col {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 15px 12px;
|
||||
padding: 13px 12px;
|
||||
border-bottom: 1px solid #edf2f7;
|
||||
color: #24324a;
|
||||
font-size: 14px;
|
||||
line-height: 1.35;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
th {
|
||||
@@ -368,14 +627,16 @@ th {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tbody tr:hover,
|
||||
tbody tr:nth-child(2) {
|
||||
tbody tr:hover {
|
||||
background: linear-gradient(90deg, rgba(16, 185, 129, .08), rgba(16, 185, 129, .03));
|
||||
}
|
||||
|
||||
@@ -447,7 +708,7 @@ tbody tr:last-child td {
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.page-summary {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<article class="assistant-panel panel">
|
||||
<header class="assistant-head">
|
||||
<button class="back-btn" type="button" aria-label="返回差旅申请/报销" @click="emit('backToRequests')">
|
||||
<i class="pi pi-arrow-left"></i>
|
||||
<i class="mdi mdi-arrow-left"></i>
|
||||
<span>返回</span>
|
||||
</button>
|
||||
</header>
|
||||
@@ -12,25 +12,25 @@
|
||||
<div class="message-row user">
|
||||
<div class="message-bubble user-bubble">
|
||||
<p>我要报销昨天去上海出差的费用。</p>
|
||||
<time>09:41 <i class="pi pi-check"></i></time>
|
||||
<time>09:41 <i class="mdi mdi-check"></i></time>
|
||||
</div>
|
||||
<span class="avatar user-avatar"><i class="pi pi-user"></i></span>
|
||||
<span class="avatar user-avatar"><i class="mdi mdi-account-outline"></i></span>
|
||||
</div>
|
||||
|
||||
<div class="message-row assistant">
|
||||
<span class="avatar ai-avatar"><i class="pi pi-sparkles"></i></span>
|
||||
<span class="avatar ai-avatar"><i class="mdi mdi-sparkles"></i></span>
|
||||
<div class="message-bubble">
|
||||
<p>好的,我已识别到这是一次差旅报销。我建议你先补充行程日期、交通票据和酒店发票。</p>
|
||||
<div class="message-actions">
|
||||
<button type="button" aria-label="赞同"><i class="pi pi-thumbs-up"></i></button>
|
||||
<button type="button" aria-label="不赞同"><i class="pi pi-thumbs-down"></i></button>
|
||||
<button type="button" aria-label="赞同"><i class="mdi mdi-thumb-up-outline"></i></button>
|
||||
<button type="button" aria-label="不赞同"><i class="mdi mdi-thumb-down-outline"></i></button>
|
||||
</div>
|
||||
<time>09:42</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-row assistant">
|
||||
<span class="avatar ai-avatar"><i class="pi pi-sparkles"></i></span>
|
||||
<span class="avatar ai-avatar"><i class="mdi mdi-sparkles"></i></span>
|
||||
<div class="message-bubble wide">
|
||||
<p>已自动识别</p>
|
||||
<div class="detected-grid">
|
||||
@@ -47,24 +47,24 @@
|
||||
<div class="message-row user">
|
||||
<div class="message-bubble user-bubble compact">
|
||||
<p>现在到哪一步了?</p>
|
||||
<time>09:43 <i class="pi pi-check"></i></time>
|
||||
<time>09:43 <i class="mdi mdi-check"></i></time>
|
||||
</div>
|
||||
<span class="avatar user-avatar"><i class="pi pi-user"></i></span>
|
||||
<span class="avatar user-avatar"><i class="mdi mdi-account-outline"></i></span>
|
||||
</div>
|
||||
|
||||
<div class="message-row assistant">
|
||||
<span class="avatar ai-avatar"><i class="pi pi-sparkles"></i></span>
|
||||
<span class="avatar ai-avatar"><i class="mdi mdi-sparkles"></i></span>
|
||||
<div class="message-bubble">
|
||||
<p>当前已完成票据识别与费用归类,下一步请确认费用明细并提交审批。</p>
|
||||
<ul class="check-list">
|
||||
<li><i class="pi pi-check"></i>票据识别:已完成</li>
|
||||
<li><i class="pi pi-check"></i>费用归类:已完成</li>
|
||||
<li><i class="pi pi-circle"></i>明细确认:待你确认</li>
|
||||
<li><i class="pi pi-circle"></i>提交审批:待提交</li>
|
||||
<li><i class="mdi mdi-check"></i>票据识别:已完成</li>
|
||||
<li><i class="mdi mdi-check"></i>费用归类:已完成</li>
|
||||
<li><i class="mdi mdi-circle"></i>明细确认:待你确认</li>
|
||||
<li><i class="mdi mdi-circle"></i>提交审批:待提交</li>
|
||||
</ul>
|
||||
<div class="message-actions">
|
||||
<button type="button" aria-label="赞同"><i class="pi pi-thumbs-up"></i></button>
|
||||
<button type="button" aria-label="不赞同"><i class="pi pi-thumbs-down"></i></button>
|
||||
<button type="button" aria-label="赞同"><i class="mdi mdi-thumb-up-outline"></i></button>
|
||||
<button type="button" aria-label="不赞同"><i class="mdi mdi-thumb-down-outline"></i></button>
|
||||
</div>
|
||||
<time>09:43</time>
|
||||
</div>
|
||||
@@ -82,12 +82,12 @@
|
||||
<textarea rows="2" placeholder="请输入你的问题,或让 AI 帮你填写报销..."></textarea>
|
||||
<div class="composer-foot">
|
||||
<div class="tool-set">
|
||||
<button type="button" aria-label="上传附件"><i class="pi pi-paperclip"></i></button>
|
||||
<button type="button" aria-label="上传图片"><i class="pi pi-image"></i></button>
|
||||
<button type="button" aria-label="语音输入"><i class="pi pi-microphone"></i></button>
|
||||
<button type="button" aria-label="上传附件"><i class="mdi mdi-paperclip"></i></button>
|
||||
<button type="button" aria-label="上传图片"><i class="mdi mdi-image-outline"></i></button>
|
||||
<button type="button" aria-label="语音输入"><i class="mdi mdi-microphone"></i></button>
|
||||
</div>
|
||||
<button class="send-btn" type="submit" aria-label="发送">
|
||||
<i class="pi pi-send"></i>
|
||||
<i class="mdi mdi-send"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -112,7 +112,7 @@
|
||||
<article class="side-card panel">
|
||||
<header>
|
||||
<h3>当前摘要</h3>
|
||||
<button type="button">查看详情 <i class="pi pi-angle-right"></i></button>
|
||||
<button type="button">查看详情 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<dl class="summary-list">
|
||||
<template v-for="item in summaryItems" :key="item.label">
|
||||
@@ -141,7 +141,7 @@
|
||||
<span>{{ item.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="more-link" type="button">查看更多建议 <i class="pi pi-angle-right"></i></button>
|
||||
<button class="more-link" type="button">查看更多建议 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</article>
|
||||
|
||||
<article class="side-card panel">
|
||||
@@ -149,14 +149,14 @@
|
||||
<ul class="notice-list">
|
||||
<li v-for="item in notices" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
<button class="more-link" type="button">查看差旅管理制度 <i class="pi pi-angle-right"></i></button>
|
||||
<button class="more-link" type="button">查看差旅管理制度 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<article class="recent-card panel">
|
||||
<header>
|
||||
<h3>最近操作</h3>
|
||||
<button type="button">查看全部 <i class="pi pi-angle-right"></i></button>
|
||||
<button type="button">查看全部 <i class="mdi mdi-chevron-right"></i></button>
|
||||
</header>
|
||||
<ol>
|
||||
<li v-for="item in recentOps" :key="item.time">
|
||||
@@ -173,17 +173,17 @@
|
||||
const emit = defineEmits(['backToRequests'])
|
||||
|
||||
const detectedItems = [
|
||||
{ label: '机票', count: '2 张', icon: 'pi pi-send' },
|
||||
{ label: '酒店发票', count: '1 张', icon: 'pi pi-building' },
|
||||
{ label: '出租车发票', count: '3 张', icon: 'pi pi-car' },
|
||||
{ label: '其他票据', count: '0 张', icon: 'pi pi-file' }
|
||||
{ label: '机票', count: '2 张', icon: 'mdi mdi-send' },
|
||||
{ label: '酒店发票', count: '1 张', icon: 'mdi mdi-office-building' },
|
||||
{ label: '出租车发票', count: '3 张', icon: 'mdi mdi-car' },
|
||||
{ label: '其他票据', count: '0 张', icon: 'mdi mdi-file-document-outline' }
|
||||
]
|
||||
|
||||
const quickActions = [
|
||||
{ label: '一键生成报销单', icon: 'pi pi-file-edit' },
|
||||
{ label: '检查缺失材料', icon: 'pi pi-verified' },
|
||||
{ label: '查看审批进度', icon: 'pi pi-list-check' },
|
||||
{ label: '差旅标准查询', icon: 'pi pi-search' }
|
||||
{ label: '一键生成报销单', icon: 'mdi mdi-file-document-outline-edit' },
|
||||
{ label: '检查缺失材料', icon: 'mdi mdi-check-decagram' },
|
||||
{ label: '查看审批进度', icon: 'mdi mdi-clipboard-check-outline' },
|
||||
{ label: '差旅标准查询', icon: 'mdi mdi-magnify' }
|
||||
]
|
||||
|
||||
const progressSteps = [
|
||||
@@ -195,24 +195,24 @@ const progressSteps = [
|
||||
]
|
||||
|
||||
const summaryItems = [
|
||||
{ label: '报销类型', value: '差旅报销', icon: 'pi pi-tags' },
|
||||
{ label: '申请人', value: '张晓明', icon: 'pi pi-user' },
|
||||
{ label: '金额预估', value: '¥3,680', icon: 'pi pi-wallet', money: true },
|
||||
{ label: '出差时间', value: '07-11 ~ 07-12', icon: 'pi pi-calendar' },
|
||||
{ label: '单据数量', value: '6', icon: 'pi pi-copy' }
|
||||
{ label: '报销类型', value: '差旅报销', icon: 'mdi mdi-tag-multiple' },
|
||||
{ label: '申请人', value: '张晓明', icon: 'mdi mdi-account-outline' },
|
||||
{ label: '金额预估', value: '¥3,680', icon: 'mdi mdi-wallet', money: true },
|
||||
{ label: '出差时间', value: '07-11 ~ 07-12', icon: 'mdi mdi-calendar' },
|
||||
{ label: '单据数量', value: '6', icon: 'mdi mdi-content-copy' }
|
||||
]
|
||||
|
||||
const todos = [
|
||||
{ text: '缺少酒店入住清单', tag: '缺失', tone: 'danger', icon: 'pi pi-times-circle' },
|
||||
{ text: '发票抬头已识别为个人,建议核对', tag: '警告', tone: 'warning', icon: 'pi pi-exclamation-triangle' },
|
||||
{ text: '有 1 笔出租车费用超过标准', tag: '注意', tone: 'notice', icon: 'pi pi-info-circle' },
|
||||
{ text: '请在今日 18:00 前提交以保证本周审批', tag: '提醒', tone: 'success', icon: 'pi pi-clock' }
|
||||
{ text: '缺少酒店入住清单', tag: '缺失', tone: 'danger', icon: 'mdi mdi-close-circle' },
|
||||
{ text: '发票抬头已识别为个人,建议核对', tag: '警告', tone: 'warning', icon: 'mdi mdi-alert' },
|
||||
{ text: '有 1 笔出租车费用超过标准', tag: '注意', tone: 'notice', icon: 'mdi mdi-information-outline' },
|
||||
{ text: '请在今日 18:00 前提交以保证本周审批', tag: '提醒', tone: 'success', icon: 'mdi mdi-clock-outline' }
|
||||
]
|
||||
|
||||
const advice = [
|
||||
{ text: '建议补充酒店入住清单,提升审批通过率', icon: 'pi pi-lightbulb' },
|
||||
{ text: '出租车费用超标 ¥28,建议说明原因或调整', icon: 'pi pi-car' },
|
||||
{ text: '可合并同类票据,减少审批批次', icon: 'pi pi-check-circle' }
|
||||
{ text: '建议补充酒店入住清单,提升审批通过率', icon: 'mdi mdi-lightbulb-outline' },
|
||||
{ text: '出租车费用超标 ¥28,建议说明原因或调整', icon: 'mdi mdi-car' },
|
||||
{ text: '可合并同类票据,减少审批批次', icon: 'mdi mdi-check-circle' }
|
||||
]
|
||||
|
||||
const notices = [
|
||||
|
||||
Reference in New Issue
Block a user