feat: 数字员工财务报告体系与定时提醒及看板快照调度

- 新增数字员工财务报告生成、邮件投递与渲染调度器
- 引入员工画像扫描调度与定时提醒任务
- 完善财务看板快照、排行口径与部门人员占比计算
- 优化数字员工工作看板仪表盘与技能目录
- 增强前端总览页图表、工作台摘要与顶部导航栏交互
- 新增差旅申请规划推动提醒与报销创建会话状态管理
- 补充财务报告、看板调度、数字员工工作记录测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-03 09:25:23 +08:00
parent 0c74b4ab4a
commit 15006a05a7
114 changed files with 7356 additions and 650 deletions

View File

@@ -16,7 +16,6 @@
:current-page="currentPage"
:page-size="pageSize"
:page-size-options="pageSizeOptions"
:pages="pageNumbers"
:show-page-size="true"
:summary="paginationSummary"
:total="visibleSkills.length"
@@ -326,14 +325,6 @@ const pagedSkills = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return props.visibleSkills.slice(start, start + pageSize.value)
})
const pageNumbers = computed(() => {
const total = totalPages.value
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1)
}
const start = Math.max(1, Math.min(currentPage.value - 3, total - 6))
return Array.from({ length: 7 }, (_, index) => start + index)
})
const paginationSummary = computed(() =>
`共 ${props.visibleSkills.length} 条,每页 ${pageSize.value} 条,当前第 ${currentPage.value} / ${totalPages.value} 页`
)

View File

@@ -10,7 +10,6 @@
:current-page="currentPage"
:page-size="pageSize"
:page-size-options="pageSizeOptions"
:pages="pageNumbers"
:show-page-size="true"
:summary="paginationSummary"
:total="visibleEmployees.length"
@@ -225,14 +224,6 @@ const pagedEmployees = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return props.visibleEmployees.slice(start, start + pageSize.value)
})
const pageNumbers = computed(() => {
const total = totalPages.value
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1)
}
const start = Math.max(1, Math.min(currentPage.value - 3, total - 6))
return Array.from({ length: 7 }, (_, index) => start + index)
})
const paginationSummary = computed(() =>
`共 ${props.visibleEmployees.length} 条,每页 ${pageSize.value} 条,目前第 ${currentPage.value} / ${totalPages.value} 页`
)

View File

@@ -110,6 +110,26 @@
<p v-else class="run-product-inline-empty">本次运行没有生成新的风险观察</p>
</section>
<section v-else-if="productKind === 'finance_snapshot'" class="run-product-section">
<div class="run-product-section-head">
<h4>财务经营快照</h4>
<span>{{ summary.period || summary.month || '本期' }}</span>
</div>
<p class="run-product-copy">
本次产物已刷新财务看板缓存沉淀报销金额预算使用费用结构和高额单据等经营指标
</p>
</section>
<section v-else-if="productKind === 'reminder_scan'" class="run-product-section">
<div class="run-product-section-head">
<h4>提醒与待办沉淀</h4>
<span>{{ summary.reminder_count || summary.reminders || 0 }} </span>
</div>
<p class="run-product-copy">
本次产物已生成审批提醒预算编制提醒报销逾期提醒和差旅申请闭环提醒
</p>
</section>
<section v-else-if="productKind === 'risk_clue'" class="run-product-section">
<div class="run-product-section-head">
<h4>待复核线索</h4>
@@ -230,6 +250,12 @@ const productSubtitle = computed(() => {
if (productKind.value === 'risk_graph') {
return '展示本次巡检生成的风险观察、证据数量和图谱关系计数。'
}
if (productKind.value === 'finance_snapshot') {
return '展示本次财务经营快照沉淀的预算、费用和报销统计。'
}
if (productKind.value === 'reminder_scan') {
return '展示本次定时提醒扫描生成的待办和触达结果。'
}
if (productKind.value === 'employee_profile') {
return '展示本次画像巡检写入的员工画像快照摘要。'
}
@@ -245,6 +271,12 @@ const productBadge = computed(() => {
if (productKind.value === 'risk_graph') {
return '风险观察'
}
if (productKind.value === 'finance_snapshot') {
return '财务快照'
}
if (productKind.value === 'reminder_scan') {
return '提醒事项'
}
if (productKind.value === 'employee_profile') {
return '画像快照'
}
@@ -281,6 +313,25 @@ const metrics = computed(() => {
buildMetric('图谱关系', payload.graph_edge_count)
]
}
if (productKind.value === 'finance_snapshot') {
return [
buildMetric('报销单数', payload.claim_count ?? payload.claims ?? payload.total_claims),
buildMetric(
'报销金额',
formatMoney(payload.claim_amount ?? payload.reimbursement_amount ?? payload.total_amount)
),
buildMetric('预算使用率', formatPercent(payload.budget_usage_rate ?? payload.budget_rate)),
buildMetric('高额单据', payload.high_value_claim_count ?? payload.high_amount_claims)
]
}
if (productKind.value === 'reminder_scan') {
return [
buildMetric('提醒人数', payload.recipient_count),
buildMetric('提醒事项', payload.reminder_count),
buildMetric('待审批', payload.approval_pending_count),
buildMetric('逾期报销', payload.reimbursement_overdue_count)
]
}
if (productKind.value === 'employee_profile') {
return [
buildMetric('目标员工', payload.target_employee_count),
@@ -376,6 +427,23 @@ function formatWindowDays(value) {
return days.length ? days.map((item) => `${item}`).join(' / ') : '-'
}
function formatMoney(value) {
const amount = Number(value)
if (!Number.isFinite(amount)) {
return '-'
}
return `¥${amount.toLocaleString('zh-CN', { maximumFractionDigits: 0 })}`
}
function formatPercent(value) {
const numericValue = Number(value)
if (!Number.isFinite(numericValue)) {
return '-'
}
const percent = numericValue > 1 ? numericValue : numericValue * 100
return `${Math.round(percent)}%`
}
function observationGraphCount(item) {
return (item.graphNodeKeys || []).length + (item.graphEdgeKeys || []).length
}

View File

@@ -14,7 +14,6 @@
:show-pagination="!loading && !errorMessage && visibleRuns.length > 0"
:current-page="currentPage"
:page-size="pageSize"
:pages="pageNumbers"
:show-page-size="false"
:summary="paginationSummary"
:total="filteredRuns.length"
@@ -303,6 +302,7 @@ import {
import {
formatWorkRecordDateTime,
formatWorkRecordSummary,
compactDigitalEmployeeWorkRecords,
resolveWorkRecordModuleLabel,
resolveWorkRecordSourceLabel,
resolveWorkRecordStatusLabel,
@@ -456,14 +456,6 @@ const visibleRuns = computed(() => {
return filteredRuns.value.slice(start, start + pageSize)
})
const pageNumbers = computed(() => {
const total = totalPages.value
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1)
}
const start = Math.max(1, Math.min(currentPage.value - 3, total - 6))
return Array.from({ length: 7 }, (_, index) => start + index)
})
const paginationSummary = computed(() =>
`共 ${filteredRuns.value.length} 条,目前第 ${currentPage.value} / ${totalPages.value} 页`
)
@@ -523,7 +515,7 @@ async function loadWorkRecords(showToast = false) {
try {
const payload = await fetchAgentRuns({ agent: 'hermes', limit: 100 })
runs.value = Array.isArray(payload) ? payload : []
runs.value = Array.isArray(payload) ? compactDigitalEmployeeWorkRecords(payload) : []
emit('summary-change', {
total: workRecordSummary.value.total,
succeeded: workRecordSummary.value.succeeded,

View File

@@ -191,41 +191,6 @@
</div>
<div class="workbench-content-grid">
<article class="panel workbench-card todo-panel">
<div class="section-head">
<div class="title-with-badge">
<h2>我的待办</h2>
<span class="soft-badge">{{ todoAlertCount }}</span>
</div>
<button type="button" class="link-action">全部待办 <i class="mdi mdi-chevron-right"></i></button>
</div>
<div class="todo-list">
<button
v-for="item in visibleTodoItems"
:key="item.title"
type="button"
class="todo-row"
@click="openPromptAssistant(`帮我处理:${item.title}${item.description}`)"
>
<WorkbenchListIcon
:icon-key="item.iconKey"
:color="item.color"
:accent="item.accent"
/>
<span class="todo-copy">
<strong>{{ item.title }}</strong>
<small>{{ item.description }}</small>
</span>
<span class="todo-meta">
<span class="todo-status" :class="`todo-status--${item.statusTone}`">{{ item.status }}</span>
<small>{{ item.due }}</small>
</span>
</button>
</div>
</article>
<article class="panel workbench-card progress-panel">
<div class="section-head">
<h2>费用进度</h2>
@@ -238,7 +203,7 @@
:key="item.id"
type="button"
class="progress-row"
@click="openPromptAssistant(`查询 ${item.id} 的费用进度`)"
@click="openWorkbenchTarget(item)"
>
<span class="progress-identity">
<strong>{{ item.id }}</strong>
@@ -247,17 +212,17 @@
<span class="progress-steps" aria-hidden="true">
<span
v-for="(step, index) in progressSteps"
:key="step"
v-for="step in item.steps"
:key="step.label"
class="progress-step"
:class="{
'is-done': index < item.activeStep,
'is-current': index === item.activeStep,
'is-future': index > item.activeStep
'is-done': step.done,
'is-current': step.current,
'is-future': !step.done && !step.current
}"
>
<i :class="index <= item.activeStep ? 'mdi mdi-check' : 'mdi mdi-minus'"></i>
<small>{{ step }}</small>
<i :class="step.done || step.current ? 'mdi mdi-check' : 'mdi mdi-minus'"></i>
<small>{{ step.label }}</small>
</span>
</span>
@@ -357,7 +322,6 @@
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import PanelHead from '../shared/PanelHead.vue'
import ExpenseProfileDetailModal from './ExpenseProfileDetailModal.vue'
import WorkbenchListIcon from '../shared/WorkbenchListIcon.vue'
import workbenchHeroBackground from '../../assets/personal-workbench-hero-bg-theme-base.webp'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
@@ -365,11 +329,8 @@ import { useWorkbenchComposerDate } from '../../composables/useWorkbenchComposer
import {
buildExpenseStatItems,
filterAssistantCapabilitiesForUser,
progressItems,
progressSteps,
quickPromptItems,
resolveWorkbenchCapabilityGridClass,
todoItems,
} from '../../data/personalWorkbench.js'
import { fetchAgentRuns } from '../../services/agentAssets.js'
import { clearUserConversations, fetchLatestConversation } from '../../services/orchestrator.js'
@@ -394,7 +355,7 @@ const props = defineProps({
workbenchSummary: { type: Object, default: () => ({}) }
})
const emit = defineEmits(['open-assistant'])
const emit = defineEmits(['open-assistant', 'open-document'])
const { currentUser } = useSystemState()
const { toast } = useToast()
const assistantDraft = ref('')
@@ -494,9 +455,12 @@ const currentUserProfileKey = computed(() => {
user.employee_no
].map((item) => String(item || '').trim()).filter(Boolean).join('|')
})
const visibleTodoItems = computed(() => todoItems.slice(0, 5))
const visibleProgressItems = computed(() => progressItems.slice(0, 5))
const todoAlertCount = computed(() => visibleTodoItems.value.length)
const visibleProgressItems = computed(() => {
const rows = Array.isArray(props.workbenchSummary.progressItems)
? props.workbenchSummary.progressItems
: []
return rows.slice(0, 5)
})
function buildSelectedFileKey(file) {
return [file?.name, file?.size, file?.lastModified, file?.type].join('__')
@@ -625,6 +589,20 @@ function openPromptAssistant(prompt) {
emitAssistant(payload)
}
function openWorkbenchTarget(item) {
const target = item?.target || {}
if (target.type === 'document' && (target.id || target.claimNo)) {
emit('open-document', {
claimId: target.id,
id: target.id || target.claimNo,
claimNo: target.claimNo
})
return
}
openPromptAssistant(item?.prompt || `查询 ${item?.id || ''} 的费用进度`)
}
function openCapabilityAssistant(item) {
if (pendingAction.value) {
return

View File

@@ -9,7 +9,10 @@
</svg>
<template v-else>{{ idx + 1 }}</template>
</span>
<span class="rank-name">{{ item.name || item.shortName }}</span>
<span class="rank-copy">
<span class="rank-name">{{ item.name || item.shortName }}</span>
<small v-if="item.meta" class="rank-meta">{{ item.meta }}</small>
</span>
</div>
</div>
<div ref="chartElement" class="chart-area" role="img" :aria-label="ariaLabel"></div>
@@ -90,7 +93,11 @@ const chartOptions = computed(() => ({
fontWeight: 700
},
extraCssText: 'border-radius:4px;box-shadow:0 12px 28px rgba(15,23,42,.12);',
formatter: (params) => `${params.marker}${params.name}: ${formatValue(params.value)}`
formatter: (params) => {
const item = resolvedItems.value.find((row) => (row.name || row.shortName) === params.name)
const meta = item?.meta ? `<br/>${item.meta}` : ''
return `${params.marker}${params.name}: ${formatValue(params.value)}${meta}`
}
},
xAxis: {
type: 'value',
@@ -180,7 +187,8 @@ const formatValue = (value) => {
}
.rank-labels {
flex: 0 0 auto;
flex: 0 0 min(34%, 150px);
min-width: 112px;
display: flex;
flex-direction: column;
justify-content: space-around;
@@ -214,10 +222,24 @@ const formatValue = (value) => {
display: block;
}
.rank-copy {
min-width: 0;
display: grid;
gap: 2px;
}
.rank-name {
color: #475569;
font-size: 13px;
font-weight: 500;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
}
.rank-meta {
color: #94a3b8;
font-size: 11px;
font-weight: 650;
}
.chart-area {

View File

@@ -1,9 +1,14 @@
<template>
<section class="risk-observation-dashboard">
<section class="risk-observation-dashboard" :class="{ 'is-loading': loading }">
<div v-if="loading" class="risk-dashboard-loading-overlay" role="status" aria-live="polite">
<i class="mdi mdi-loading mdi-spin"></i>
<span>{{ loadingLabel }}</span>
</div>
<article class="panel dashboard-card risk-trend-panel">
<div class="card-head">
<h3>风险观察趋势 <i class="mdi mdi-information-outline"></i></h3>
<div class="risk-window-controls">
<span v-if="lastUpdatedLabel" class="risk-refresh-label">{{ lastUpdatedLabel }}</span>
<span class="risk-window-label"> {{ dashboard.windowDays }} </span>
<EnterpriseSelect
class="risk-window-select"
@@ -174,11 +179,29 @@ const props = defineProps({
signalRanking: { type: Array, default: () => [] },
dailyRows: { type: Array, default: () => [] },
windowOptions: { type: Array, default: () => [] },
activeWindowDays: { type: Number, default: 30 }
activeWindowDays: { type: Number, default: 30 },
lastUpdatedAt: { type: String, default: '' }
})
const emit = defineEmits(['update:windowDays'])
const router = useRouter()
const loadingLabel = computed(() => (
props.lastUpdatedAt ? '正在同步最新风险数据' : '正在加载风险看板数据'
))
const lastUpdatedLabel = computed(() => {
if (!props.lastUpdatedAt) {
return ''
}
const date = new Date(props.lastUpdatedAt)
if (Number.isNaN(date.getTime())) {
return ''
}
return `上次同步 ${date.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit'
})}`
})
const errorMessage = computed(() => props.error?.message || '风险看板数据加载失败')
const recentHighObservations = computed(() => props.dashboard.recentHighObservations || [])
const dimensionGroups = computed(() => [
@@ -315,12 +338,39 @@ function openClaim(item) {
<style scoped>
.risk-observation-dashboard {
position: relative;
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 18px;
min-width: 0;
}
.risk-dashboard-loading-overlay {
position: absolute;
inset: 0;
z-index: 5;
display: grid;
place-content: center;
justify-items: center;
gap: 10px;
border: 1px solid #e2e8f0;
border-radius: 4px;
background: rgba(248, 250, 252, .82);
color: #334155;
font-size: 13px;
font-weight: 800;
backdrop-filter: blur(2px);
}
.risk-dashboard-loading-overlay i {
color: var(--theme-primary);
font-size: 26px;
}
.risk-observation-dashboard.is-loading .dashboard-card {
pointer-events: none;
}
.dashboard-card {
min-width: 0;
padding: 18px;
@@ -359,6 +409,13 @@ function openClaim(item) {
font-weight: 700;
}
.risk-refresh-label {
flex: 0 0 auto;
color: #64748b;
font-size: 12px;
font-weight: 800;
}
.risk-window-controls {
flex: 0 0 auto;
display: flex;

View File

@@ -1,5 +1,5 @@
<template>
<header class="topbar" :class="{ 'chat-mode': isChat }">
<header class="topbar" :class="{ 'chat-mode': isChat, 'detail-mode': isRequestDetail }">
<div class="title-group">
<div class="eyebrow">{{ eyebrowLabel }}</div>
<h1>{{ currentView.title }}</h1>
@@ -121,12 +121,73 @@
</div>
</template>
<template v-else-if="isWorkbench">
<template v-else-if="isWorkbench">
<div class="topbar-toolset" aria-label="工作台快捷工具">
<button class="topbar-icon-btn notification-btn" type="button" aria-label="通知">
<i class="mdi mdi-bell-outline"></i>
<span v-if="topbarNotificationCount" class="notification-badge">{{ topbarNotificationCount }}</span>
</button>
<div class="notification-wrap">
<button
class="topbar-icon-btn notification-btn"
type="button"
aria-label="通知"
:aria-expanded="notificationOpen"
aria-haspopup="dialog"
@click="notificationOpen = !notificationOpen"
>
<i class="mdi mdi-bell-outline"></i>
<span v-if="topbarNotificationCount" class="notification-badge">{{ topbarNotificationCount }}</span>
</button>
<div v-if="notificationOpen" class="notification-popover" role="dialog" aria-label="通知中心">
<header class="notification-head">
<strong>通知</strong>
<button type="button" aria-label="关闭通知" @click="notificationOpen = false">
<i class="mdi mdi-close"></i>
</button>
</header>
<div class="notification-tabs" role="tablist" aria-label="通知状态">
<button
type="button"
role="tab"
:aria-selected="notificationTab === 'unread'"
:class="{ active: notificationTab === 'unread' }"
@click="notificationTab = 'unread'"
>
未读 {{ unreadNotifications.length }}
</button>
<button
type="button"
role="tab"
:aria-selected="notificationTab === 'read'"
:class="{ active: notificationTab === 'read' }"
@click="notificationTab = 'read'"
>
已读 {{ readNotifications.length }}
</button>
</div>
<div v-if="activeNotifications.length" class="notification-list">
<button
v-for="item in activeNotifications"
:key="item.id"
type="button"
class="notification-row"
@click="openNotification(item)"
>
<span class="notification-dot" :class="item.tone"></span>
<span class="notification-copy">
<strong>{{ item.title }}</strong>
<small>{{ item.description }}</small>
<em>{{ item.time }}</em>
</span>
<i class="mdi mdi-chevron-right"></i>
</button>
</div>
<div v-else class="notification-empty">
<i class="mdi mdi-bell-check-outline"></i>
<span>{{ notificationTab === 'unread' ? '暂无未读通知' : '暂无已读通知' }}</span>
</div>
</div>
</div>
<button class="topbar-icon-btn" type="button" aria-label="帮助">
<i class="mdi mdi-help-circle-outline"></i>
@@ -243,6 +304,10 @@ const props = defineProps({
type: Object,
default: () => null
},
workbenchSummary: {
type: Object,
default: () => null
},
companyName: {
type: String,
default: ''
@@ -276,7 +341,8 @@ const emit = defineEmits([
'update:overviewDashboard',
'batchApprove',
'openChat',
'newApplication'
'newApplication',
'openDocument'
])
const isChat = computed(() => props.activeView === 'chat')
const isOverview = computed(() => props.activeView === 'overview')
@@ -294,10 +360,34 @@ const eyebrowLabel = computed(() => (
))
const displayCompanyName = computed(() => String(props.companyName || '远光软件股份有限公司').trim() || '远光软件股份有限公司')
const topbarNotificationCount = computed(() => {
const summary = props.documentSummary ?? {}
const count = Number(summary.toProcess ?? summary.toSubmit ?? 8)
const summary = props.workbenchSummary ?? {}
const count = Number(summary.unreadNotificationCount ?? 0)
return Number.isFinite(count) && count > 0 ? Math.min(count, 99) : 0
})
})
const notificationOpen = ref(false)
const notificationTab = ref('unread')
const notificationItems = computed(() => (
Array.isArray(props.workbenchSummary?.notifications)
? props.workbenchSummary.notifications
: []
))
const unreadNotifications = computed(() => notificationItems.value.filter((item) => item.unread))
const readNotifications = computed(() => notificationItems.value.filter((item) => !item.unread))
const activeNotifications = computed(() => (
notificationTab.value === 'unread' ? unreadNotifications.value : readNotifications.value
))
function openNotification(item) {
notificationOpen.value = false
const target = item?.target || {}
if (target.type === 'document' && (target.id || target.claimNo)) {
emit('openDocument', {
claimId: target.id,
id: target.id || target.claimNo,
claimNo: target.claimNo
})
}
}
const requestKpis = computed(() => {
const summary = props.requestSummary ?? {}

View File

@@ -99,7 +99,6 @@
:current-page="currentPage"
:page-size="pageSize"
:page-size-options="pageSizeOptions"
:pages="pages"
:show-page-size="showPageSize"
:summary="summary"
:total="total"
@@ -139,10 +138,6 @@ const props = defineProps({
default: () => []
},
panel: { type: Boolean, default: true },
pages: {
type: Array,
default: () => []
},
retryLabel: { type: String, default: '重新加载' },
searchable: { type: Boolean, default: false },
searchPlaceholder: { type: String, default: '搜索' },

View File

@@ -78,10 +78,6 @@ const props = defineProps({
type: Array,
default: () => []
},
pages: {
type: Array,
default: () => []
},
showPageSize: { type: Boolean, default: true },
summary: { type: String, default: '' },
total: { type: Number, default: 0 },
@@ -106,7 +102,7 @@ const summaryText = computed(() => {
return props.summary
}
return `${props.total} 条,当前第 ${props.currentPage}`
return `${props.total} 条,当前第 ${props.currentPage} / ${props.totalPages}`
})
function setPage(page) {
@@ -142,3 +138,140 @@ watch(
}
)
</script>
<style scoped>
.list-foot.enterprise-pagination {
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
align-items: center;
gap: 16px;
margin-top: 12px;
}
.enterprise-pagination .page-summary {
min-width: 0;
color: #64748b;
font-size: 14px;
font-weight: 650;
}
.enterprise-pagination .pager {
display: inline-flex;
justify-content: center;
gap: 6px;
padding: 4px;
border: 1px solid #e2e8f0;
border-radius: 4px;
background: #f8fafc;
}
.enterprise-pagination .pager button {
width: 32px;
height: 32px;
padding: 0;
border: 0;
border-radius: 3px;
background: transparent;
color: #334155;
font-size: 14px;
font-weight: 800;
transition: background 160ms ease, color 160ms ease, box-shadow 160ms ease;
}
.enterprise-pagination .pager button:hover:not(.active) {
background: #fff;
color: var(--theme-primary-active);
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
}
.enterprise-pagination .pager button.active {
background: var(--theme-primary);
color: #fff;
box-shadow: 0 8px 16px var(--theme-primary-shadow);
}
.enterprise-pagination .pager button:disabled {
color: #cbd5e1;
cursor: not-allowed;
box-shadow: none;
}
.enterprise-pagination .page-ellipsis {
width: 28px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #64748b;
font-size: 13px;
font-weight: 850;
}
.enterprise-pagination .page-tools {
justify-self: end;
display: inline-flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.enterprise-pagination .page-size-select {
width: 112px;
}
.enterprise-pagination .page-jump {
display: inline-flex;
align-items: center;
gap: 6px;
color: #64748b;
font-size: 13px;
font-weight: 700;
white-space: nowrap;
}
.enterprise-pagination .page-jump input {
width: 54px;
height: 32px;
padding: 0 8px;
border: 1px solid #d7e0ea;
border-radius: 4px;
background: #fff;
color: #0f172a;
font-size: 13px;
font-weight: 800;
text-align: center;
transition: border-color 160ms ease, box-shadow 160ms ease;
}
.enterprise-pagination .page-jump input:focus {
border-color: var(--theme-primary);
box-shadow: 0 0 0 3px var(--theme-focus-ring);
outline: none;
}
@media (max-width: 760px) {
.list-foot.enterprise-pagination {
grid-template-columns: 1fr;
justify-items: stretch;
}
.enterprise-pagination .pager {
width: 100%;
max-width: 100%;
justify-content: flex-start;
overflow-x: auto;
scrollbar-width: thin;
}
.enterprise-pagination .pager button,
.enterprise-pagination .page-ellipsis {
flex: 0 0 auto;
}
.enterprise-pagination .page-tools {
justify-self: stretch;
justify-content: space-between;
flex-wrap: wrap;
}
}
</style>