style: 全局 UI 主题皮肤重构与样式模块化

引入 Element Plus 主题定制和主题皮肤 composable,将全局
样式拆分为组件级独立 CSS 文件(侧边栏、顶栏、工作台等),
统一色彩变量和间距规范,重构所有视图和组件样式以适配新
主题系统,优化图表和知识图谱组件视觉表现,提取审计和差
旅报销相关子组件。
This commit is contained in:
caoxiaozhu
2026-05-27 09:17:57 +08:00
parent df49103f23
commit 2dcc72102d
112 changed files with 10983 additions and 8996 deletions

View File

@@ -29,35 +29,35 @@
<input v-model="listKeyword" type="search" placeholder="搜索单号、申请人、部门、归档类型..." />
</div>
<div
v-for="dropdown in filterDropdowns"
:key="dropdown.key"
class="archive-dropdown-filter"
:class="{ open: openFilterKey === dropdown.key }"
<el-dropdown
v-for="menu in filterMenus"
:key="menu.key"
class="archive-filter-control"
trigger="click"
placement="bottom-start"
popper-class="archive-filter-menu"
@command="selectFilterValue(menu.key, $event)"
>
<button class="filter-btn" type="button" @click="toggleFilterDropdown(dropdown.key)">
<span>{{ dropdown.label }}</span>
<button class="filter-btn archive-filter-trigger" type="button">
<span>{{ menu.label }}</span>
<i class="mdi mdi-chevron-down"></i>
</button>
<div
v-if="openFilterKey === dropdown.key"
class="archive-dropdown-menu"
role="menu"
:aria-label="`${dropdown.label}筛选`"
>
<button
v-for="option in dropdown.options"
:key="`${dropdown.key}-${option.value}`"
type="button"
class="archive-dropdown-option"
:class="{ active: dropdown.activeValue === option.value }"
role="menuitem"
@click="selectFilterValue(dropdown.key, option.value)"
>
{{ option.label }}
</button>
</div>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="option in menu.options"
:key="`${menu.key}-${option.value}`"
:command="option.value"
:class="{ 'is-active': menu.activeValue === option.value }"
class="archive-filter-option"
:aria-current="menu.activeValue === option.value ? 'true' : undefined"
>
<i v-if="menu.activeValue === option.value" class="mdi mdi-check"></i>
<span>{{ option.label }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -35,27 +35,19 @@
<div class="budget-filter-set">
<label>
<span>预算年度</span>
<select v-model="filters.year">
<option v-for="year in years" :key="year" :value="year">{{ year }}年度</option>
</select>
<EnterpriseSelect v-model="filters.year" :options="yearOptions" />
</label>
<label>
<span>预算季度</span>
<select v-model="filters.quarter">
<option v-for="quarter in quarters" :key="quarter" :value="quarter">{{ quarter }}</option>
</select>
<EnterpriseSelect v-model="filters.quarter" :options="quarters" />
</label>
<label>
<span>费用类型</span>
<select v-model="filters.expenseType">
<option v-for="type in expenseTypes" :key="type">{{ type }}</option>
</select>
<EnterpriseSelect v-model="filters.expenseType" :options="expenseTypes" />
</label>
<label>
<span>状态</span>
<select v-model="filters.status">
<option v-for="status in statuses" :key="status">{{ status }}</option>
</select>
<EnterpriseSelect v-model="filters.status" :options="statuses" />
</label>
</div>
<div class="budget-action-set">
@@ -158,14 +150,13 @@
<i class="mdi mdi-chevron-right"></i>
</button>
</div>
<label class="budget-page-size">
<select v-model.number="budgetPageSize" aria-label="每页条数">
<option v-for="size in budgetPageSizeOptions" :key="size" :value="size">
{{ size }} /
</option>
</select>
<i class="mdi mdi-chevron-down"></i>
</label>
<EnterpriseSelect
v-model="budgetPageSize"
class="budget-page-size-select"
:options="budgetPageSizeOptions"
aria-label="每页条数"
size="small"
/>
<span class="budget-page-summary">
{{ totalBudgetRows }} 当前第 {{ budgetPage }} / {{ totalBudgetPages }}
</span>
@@ -232,23 +223,15 @@
<div class="budget-edit-form-grid">
<label class="required">
<span>预算年度</span>
<select v-model="budgetEditForm.budgetYear">
<option v-for="year in years" :key="year" :value="year">{{ year }}年度</option>
</select>
<EnterpriseSelect v-model="budgetEditForm.budgetYear" :options="yearOptions" />
</label>
<label class="required">
<span>预算季度</span>
<select v-model="budgetEditForm.budgetQuarter">
<option v-for="quarter in quarters" :key="quarter" :value="quarter">{{ quarter }}</option>
</select>
<EnterpriseSelect v-model="budgetEditForm.budgetQuarter" :options="quarters" />
</label>
<label v-if="canSwitchDepartments" class="required">
<span>所属部门</span>
<select v-model="budgetEditForm.departmentCode">
<option v-for="department in departments" :key="department.code" :value="department.code">
{{ department.name }}
</option>
</select>
<EnterpriseSelect v-model="budgetEditForm.departmentCode" :options="departmentOptions" />
</label>
<label v-else class="required">
<span>所属部门</span>
@@ -279,26 +262,19 @@
<tbody>
<tr v-for="row in budgetEditRows" :key="row.id">
<td>
<select v-model="row.budgetSubjectCode" @change="syncBudgetRowSubject(row)">
<option
v-for="option in expenseTypeOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
<EnterpriseSelect
v-model="row.budgetSubjectCode"
:options="expenseTypeOptions"
size="small"
@change="syncBudgetRowSubject(row)"
/>
</td>
<td><input v-model="row.budgetAmount" type="text" inputmode="decimal" /></td>
<td>
<select v-model="row.warningThreshold">
<option v-for="warning in warningOptions" :key="warning">{{ warning }}</option>
</select>
<EnterpriseSelect v-model="row.warningThreshold" :options="warningOptions" size="small" />
</td>
<td>
<select v-model="row.controlAction">
<option v-for="action in controlActionOptions" :key="action">{{ action }}</option>
</select>
<EnterpriseSelect v-model="row.controlAction" :options="controlActionOptions" size="small" />
</td>
<td><input v-model="row.budgetRemark" type="text" /></td>
<td>

View File

@@ -232,24 +232,7 @@
<i class="mdi mdi-chevron-right"></i>
</button>
</div>
<div class="page-size-wrap">
<button class="page-size" type="button" @click="pageSizeOpen = !pageSizeOpen">
{{ pageSize }} / <i class="mdi mdi-chevron-down"></i>
</button>
<div v-if="pageSizeOpen" class="page-size-dropdown" role="listbox">
<button
v-for="size in pageSizes"
:key="size"
type="button"
role="option"
:aria-selected="pageSize === size"
:class="{ active: pageSize === size }"
@click="changePageSize(size)"
>
{{ size }} /
</button>
</div>
</div>
<EnterpriseSelect v-model="pageSize" class="page-size-select" :options="pageSizeOptions" size="small" @change="changePageSize" />
</footer>
</article>
</section>
@@ -258,6 +241,7 @@
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import EnterpriseSelect from '../components/shared/EnterpriseSelect.vue'
import TableEmptyState from '../components/shared/TableEmptyState.vue'
import TableLoadingState from '../components/shared/TableLoadingState.vue'
import { mapExpenseClaimToRequest } from '../composables/useRequests.js'
@@ -321,7 +305,7 @@ const FILTER_CONFIG_BY_SCOPE = {
showDocumentType: false
}
}
const pageSizes = [10, 20, 50]
const pageSizeOptions = [10, 20, 50].map((size) => ({ label: `${size} 条/页`, value: size }))
const documentTypeOptions = [
{ value: DOCUMENT_TYPE_ALL, label: '单据类型' },
{ value: DOCUMENT_TYPE_APPLICATION, label: '申请单' },
@@ -356,7 +340,6 @@ const appliedStart = ref('')
const appliedEnd = ref('')
const currentPage = ref(1)
const pageSize = ref(20)
const pageSizeOpen = ref(false)
const archiveRows = ref([])
const approvalRows = ref([])
const supportingLoading = ref(false)
@@ -519,7 +502,7 @@ const emptyState = computed(() => {
icon: 'mdi mdi-file-sign-outline',
actionLabel: '',
actionIcon: '',
tone: 'emerald',
tone: 'theme',
artLabel: 'APPLY',
tips: ['申请、报销、审批与归档统一在此查看', '申请批准后可继续发起报销']
}
@@ -534,7 +517,7 @@ const emptyState = computed(() => {
icon: filtered ? 'mdi mdi-magnify-scan' : 'mdi mdi-file-document-multiple-outline',
actionLabel: '',
actionIcon: '',
tone: 'emerald',
tone: 'theme',
artLabel: filtered ? 'FILTER' : 'DOCS',
tips: ['单据中心已接入当前报销单据', '归档视角会同步已归档数据']
}
@@ -724,7 +707,6 @@ function handleEmptyAction() {
function changePageSize(size) {
pageSize.value = size
pageSizeOpen.value = false
currentPage.value = 1
}
@@ -782,14 +764,12 @@ watch(
[activeScopeTab, activeStatusTab, activeDocumentType, activeScene, listKeyword, appliedStart, appliedEnd],
() => {
currentPage.value = 1
pageSizeOpen.value = false
}
)
watch(activeFilterConfig, () => {
openFilterKey.value = ''
datePopover.value = false
pageSizeOpen.value = false
if (!showDocumentTypeFilter.value) {
activeDocumentType.value = DOCUMENT_TYPE_ALL

View File

@@ -665,24 +665,13 @@
<i class="mdi mdi-chevron-right"></i>
</button>
</div>
<div class="page-size-wrap">
<button class="page-size" type="button" @click="togglePageSizeOpen">
{{ pageSize }} / <i class="mdi mdi-chevron-down"></i>
</button>
<div v-if="pageSizeOpen" class="page-size-dropdown" role="listbox">
<button
v-for="size in pageSizes"
:key="size"
type="button"
role="option"
:aria-selected="pageSize === size"
:class="{ active: pageSize === size }"
@click="changePageSize(size)"
>
{{ size }} /
</button>
</div>
</div>
<EnterpriseSelect
v-model="pageSize"
class="page-size-select"
:options="pageSizeOptions"
size="small"
@change="changePageSize"
/>
</footer>
</article>
</Transition>

View File

@@ -28,9 +28,12 @@
<div class="form-grid">
<label class="field">
<span><em>*</em> 供应商</span>
<select v-model="llmForm.mainProvider" @change="applyProviderPreset('main')">
<option v-for="option in providerOptions" :key="option" :value="option">{{ option }}</option>
</select>
<EnterpriseSelect
v-model="llmForm.mainProvider"
:options="providerOptions"
placeholder="选择供应商"
@change="applyProviderPreset('main')"
/>
</label>
<label class="field">
@@ -101,9 +104,12 @@
<div class="form-grid">
<label class="field">
<span><em>*</em> 供应商</span>
<select v-model="llmForm.backupProvider" @change="applyProviderPreset('backup')">
<option v-for="option in providerOptions" :key="option" :value="option">{{ option }}</option>
</select>
<EnterpriseSelect
v-model="llmForm.backupProvider"
:options="providerOptions"
placeholder="选择供应商"
@change="applyProviderPreset('backup')"
/>
</label>
<label class="field">
@@ -174,9 +180,12 @@
<div class="form-grid">
<label class="field">
<span><em>*</em> 供应商</span>
<select v-model="llmForm.embeddingProvider" @change="applyProviderPreset('embedding')">
<option v-for="option in providerOptions" :key="option" :value="option">{{ option }}</option>
</select>
<EnterpriseSelect
v-model="llmForm.embeddingProvider"
:options="providerOptions"
placeholder="选择供应商"
@change="applyProviderPreset('embedding')"
/>
</label>
<label class="field">
@@ -251,9 +260,12 @@
<div class="form-grid">
<label class="field">
<span><em>*</em> 供应商</span>
<select v-model="llmForm.rerankerProvider" @change="applyProviderPreset('reranker')">
<option v-for="option in providerOptions" :key="option" :value="option">{{ option }}</option>
</select>
<EnterpriseSelect
v-model="llmForm.rerankerProvider"
:options="providerOptions"
placeholder="选择供应商"
@change="applyProviderPreset('reranker')"
/>
</label>
<label class="field">
@@ -306,4 +318,3 @@
<style scoped src="../assets/styles/views/settings-view-form.css"></style>
<style scoped src="../assets/styles/views/settings-view.css"></style>

View File

@@ -27,22 +27,22 @@
<label class="filter-field">
<span>日志级别</span>
<select v-model="systemLevelFilter">
<option value="">全部</option>
<option v-for="level in systemLevelOptions" :key="level" :value="level">
{{ level }}
</option>
</select>
<EnterpriseSelect
v-model="systemLevelFilter"
:options="systemLevelFilterOptions"
placeholder="全部"
size="small"
/>
</label>
<label class="filter-field">
<span>事件类型</span>
<select v-model="systemEventTypeFilter">
<option value="">全部</option>
<option v-for="eventType in systemEventTypeOptions" :key="eventType" :value="eventType">
{{ eventType }}
</option>
</select>
<EnterpriseSelect
v-model="systemEventTypeFilter"
:options="systemEventTypeFilterOptions"
placeholder="全部"
size="small"
/>
</label>
<button
@@ -221,24 +221,13 @@
<i class="mdi mdi-chevron-right"></i>
</button>
</div>
<div class="page-size-wrap">
<button class="page-size" type="button" @click="pageSizeOpen = !pageSizeOpen">
{{ pageSize }} / <i class="mdi mdi-chevron-down"></i>
</button>
<div v-if="pageSizeOpen" class="page-size-dropdown" role="listbox">
<button
v-for="size in pageSizes"
:key="size"
type="button"
role="option"
:aria-selected="pageSize === size"
:class="{ active: pageSize === size }"
@click="changePageSize(size)"
>
{{ size }} /
</button>
</div>
</div>
<EnterpriseSelect
v-model="pageSize"
class="page-size-select"
:options="pageSizeOptions"
size="small"
@change="changePageSize"
/>
</div>
</section>

View File

@@ -27,11 +27,11 @@
<label class="field">
<span>加密方式</span>
<select v-model="mailForm.encryption">
<option value="SSL/TLS">SSL/TLS</option>
<option value="STARTTLS">STARTTLS</option>
<option value="None"></option>
</select>
<EnterpriseSelect
v-model="mailForm.encryption"
:options="encryptionOptions"
placeholder="选择加密方式"
/>
</label>
<label class="field">

View File

@@ -26,9 +26,13 @@
<article class="panel dashboard-card trend-panel">
<div class="card-head">
<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>
<EnterpriseSelect
v-model="activeTrendRange"
class="card-select"
:options="trendRanges"
aria-label="趋势时间范围"
size="small"
/>
</div>
<TrendChart
@@ -52,17 +56,21 @@
<h3>风险异常分布 <i class="mdi mdi-information-outline"></i></h3>
</div>
<DonutChart :items="riskLegend" :center-value="`${riskTotal}`" center-label="异常预警单" />
<p class="panel-note">* 30天数据</p>
<p class="panel-note">* 30 天数据</p>
</article>
</div>
<div class="content-grid bottom-grid">
<article class="panel dashboard-card rank-panel">
<div class="card-head">
<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>
<h3>部门报销排行待处理金额<i class="mdi mdi-information-outline"></i></h3>
<EnterpriseSelect
v-model="activeDepartmentRange"
class="card-select"
:options="departmentRangeOptions"
aria-label="部门排行时间范围"
size="small"
/>
</div>
<BarChart :items="rankedDepartments" />
@@ -99,7 +107,7 @@
<article class="panel dashboard-card budget-panel">
<div class="card-head">
<h3>预算执行率本月 <i class="mdi mdi-information-outline"></i></h3>
<h3>预算执行率本月<i class="mdi mdi-information-outline"></i></h3>
</div>
<GaugeChart
@@ -112,7 +120,6 @@
<button type="button" class="text-link">查看详情 <i class="mdi mdi-chevron-right"></i></button>
</article>
</div>
</section>
</template>
@@ -121,6 +128,7 @@ import TrendChart from '../components/charts/TrendChart.vue'
import DonutChart from '../components/charts/DonutChart.vue'
import BarChart from '../components/charts/BarChart.vue'
import GaugeChart from '../components/charts/GaugeChart.vue'
import EnterpriseSelect from '../components/shared/EnterpriseSelect.vue'
import { useOverviewView } from '../composables/useOverviewView.js'
@@ -128,8 +136,6 @@ defineProps({
filteredRequests: { type: Array, required: true }
})
const emit = defineEmits(['ask'])
const {
activeDepartmentRange,
activeTrend,

View File

@@ -168,24 +168,13 @@
<i class="mdi mdi-chevron-right"></i>
</button>
</div>
<div class="page-size-wrap">
<button class="page-size" type="button" @click="pageSizeOpen = !pageSizeOpen">
{{ pageSize }} /<i class="mdi mdi-chevron-down"></i>
</button>
<div v-if="pageSizeOpen" class="page-size-dropdown" role="listbox">
<button
v-for="size in pageSizes"
:key="size"
type="button"
role="option"
:aria-selected="pageSize === size"
:class="{ active: pageSize === size }"
@click="changePageSize(size)"
>
{{ size }} /
</button>
</div>
</div>
<EnterpriseSelect
v-model="pageSize"
class="page-size-select"
:options="pageSizeOptions"
size="small"
@change="changePageSize"
/>
</footer>
</section>
</div>

View File

@@ -135,14 +135,13 @@
<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>
<div class="page-size-wrap">
<button class="page-size" type="button" @click="pageSizeOpen = !pageSizeOpen">
{{ pageSize }} / <i class="mdi mdi-chevron-down"></i>
</button>
<div v-if="pageSizeOpen" class="page-size-dropdown" role="listbox">
<button v-for="size in pageSizes" :key="size" type="button" role="option" :aria-selected="pageSize === size" :class="{ active: pageSize === size }" @click="changePageSize(size)">{{ size }} /</button>
</div>
</div>
<EnterpriseSelect
v-model="pageSize"
class="page-size-select"
:options="pageSizeOptions"
size="small"
@change="changePageSize"
/>
</footer>
</article>
</section>

View File

@@ -101,7 +101,54 @@
</section>
</template>
<template v-else-if="activeSection === 'admin'">
<template v-else-if="activeSection === 'appearance'">
<section class="settings-card">
<div class="card-head">
<div class="card-title-with-icon">
<div class="model-icon-box slate">
<i class="mdi mdi-palette-outline"></i>
</div>
<div>
<h4>界面皮肤与企业主色</h4>
<p>只调整整体主色焦点态按钮和 Element Plus 控件颜色不改变业务布局</p>
</div>
</div>
</div>
<div class="skin-option-grid">
<button
v-for="skin in themeSkinOptions"
:key="skin.id"
class="skin-option"
:class="{ active: activeThemeSkinId === skin.id }"
type="button"
@click="selectThemeSkin(skin.id)"
>
<span class="skin-swatch" aria-hidden="true">
<i :style="{ background: skin.primary }"></i>
<i :style="{ background: skin.primarySoftStrong }"></i>
<i :style="{ background: skin.secondary }"></i>
<i :style="{ background: skin.chartAmber }"></i>
</span>
<span class="skin-copy">
<strong>{{ skin.label }}</strong>
<small>{{ skin.desc }}</small>
</span>
<span v-if="activeThemeSkinId === skin.id" class="skin-current">当前</span>
</button>
</div>
<div class="skin-preview-panel">
<div>
<strong>{{ activeThemeSkin.label }}</strong>
<span>当前主色会同步到全局按钮焦点环下拉浮层和表单控件</span>
</div>
<button class="skin-preview-action" type="button">主按钮</button>
</div>
</section>
</template>
<template v-else-if="activeSection === 'admin'">
<section class="settings-card">
<div class="card-head">
<div class="card-title-with-icon">
@@ -365,14 +412,14 @@
<input v-model.number="pageState.logForm.retentionDays" type="number" min="7" />
</label>
<label class="field">
<span>归档周期</span>
<select v-model="pageState.logForm.archiveCycle">
<option value="daily">按天归档</option>
<option value="weekly">按周归档</option>
<option value="monthly">按月归档</option>
</select>
</label>
<label class="field">
<span>归档周期</span>
<EnterpriseSelect
v-model="pageState.logForm.archiveCycle"
:options="archiveCycleOptions"
placeholder="选择归档周期"
/>
</label>
<label class="field field-full">
<span><em>*</em> 日志路径</span>

File diff suppressed because it is too large Load Diff

View File

@@ -241,11 +241,7 @@
<td class="expense-type col-type">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor">
<select v-model="expenseEditor.itemType" class="editor-select">
<option v-for="option in expenseTypeOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
<EnterpriseSelect v-model="expenseEditor.itemType" class="editor-select" :options="expenseTypeOptions" size="small" />
<span>编辑费用项目</span>
</div>
</template>
@@ -763,58 +759,27 @@
</div>
</ConfirmDialog>
<ConfirmDialog
:open="deleteDialogOpen"
:badge="deleteActionLabel"
badge-tone="danger"
:title="deleteDialogTitle"
:description="deleteDialogDescription"
cancel-text="取消"
confirm-text="确认删除"
busy-text="删除中..."
confirm-tone="danger"
confirm-icon="mdi mdi-trash-can-outline"
:busy="deleteBusy"
@close="closeDeleteDialog"
@confirm="confirmDeleteRequest"
/>
<TravelRequestDeleteDialog :open="deleteDialogOpen" :badge="deleteActionLabel" :title="deleteDialogTitle" :description="deleteDialogDescription" :busy="deleteBusy" @close="closeDeleteDialog" @confirm="confirmDeleteRequest" />
<ConfirmDialog
<TravelRequestApprovalDialog
:open="approveConfirmDialogOpen"
:badge="approvalConfirmBadge"
badge-tone="info"
:title="approveConfirmTitle"
:description="approvalConfirmDescription"
cancel-text="返回核对"
:confirm-text="approveConfirmText"
:busy-text="approveBusyText"
confirm-tone="primary"
confirm-icon="mdi mdi-check-circle-outline"
:busy="approveBusy"
:document-no="request.documentNo || request.id"
:node="request.node"
:summary-label="approvalConfirmSummaryLabel"
:next-stage="approvalNextStage"
:opinion-title="approvalOpinionTitle"
:opinion="leaderOpinion"
@close="closeApproveConfirmDialog"
@confirm="confirmApproveRequest"
>
<div class="submit-confirm-summary" aria-label="领导审批通过摘要">
<div class="submit-confirm-row">
<span>单据编号</span>
<strong>{{ request.documentNo || request.id }}</strong>
</div>
<div class="submit-confirm-row">
<span>当前节点</span>
<strong>{{ request.node }}</strong>
</div>
<div class="submit-confirm-row">
<span>{{ approvalConfirmSummaryLabel }}</span>
<strong>{{ approvalNextStage }}</strong>
</div>
<div class="submit-confirm-row">
<span>{{ approvalOpinionTitle }}</span>
<strong>{{ leaderOpinion.trim() || '未填写' }}</strong>
</div>
</div>
</ConfirmDialog>
/>
<ReturnReasonDialog
<TravelRequestReturnDialog
:open="returnDialogOpen"
:title="`确认退回 ${request.id} 吗?`"
:description="returnDialogDescription"

View File

@@ -1,4 +1,4 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { computed, ref } from 'vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
@@ -102,7 +102,6 @@ export default {
const activeTypeFilter = ref(ARCHIVE_FILTER_ALL)
const activeDepartmentFilter = ref(ARCHIVE_FILTER_ALL)
const activeArchiveMonthFilter = ref(ARCHIVE_FILTER_ALL)
const openFilterKey = ref('')
const selectedClaimId = ref('')
const listKeyword = ref('')
const rows = ref([])
@@ -118,7 +117,7 @@ export default {
const departmentFilterLabel = computed(() => resolveFilterLabel(departmentFilterOptions.value, activeDepartmentFilter.value, '所属部门'))
const archiveMonthFilterLabel = computed(() => resolveFilterLabel(archiveMonthFilterOptions.value, activeArchiveMonthFilter.value, '归档月份'))
const filterDropdowns = computed(() => [
const filterMenus = computed(() => [
{
key: 'risk',
label: riskFilterLabel.value,
@@ -210,7 +209,6 @@ export default {
activeDepartmentFilter.value = ARCHIVE_FILTER_ALL
activeArchiveMonthFilter.value = ARCHIVE_FILTER_ALL
listKeyword.value = ''
openFilterKey.value = ''
}
function handleEmptyAction() {
@@ -222,10 +220,6 @@ export default {
resetListFilters()
}
function toggleFilterDropdown(key) {
openFilterKey.value = openFilterKey.value === key ? '' : key
}
function selectFilterValue(key, value) {
if (key === 'risk') {
activeRiskFilter.value = value
@@ -237,18 +231,6 @@ export default {
activeArchiveMonthFilter.value = value
}
openFilterKey.value = ''
}
function handleDocumentClick(event) {
const target = event.target
if (!(target instanceof Element)) {
return
}
if (!target.closest('.archive-dropdown-filter')) {
openFilterKey.value = ''
}
}
function closeSelectedDetail() {
@@ -278,14 +260,6 @@ export default {
}
}
onMounted(() => {
document.addEventListener('click', handleDocumentClick)
})
onBeforeUnmount(() => {
document.removeEventListener('click', handleDocumentClick)
})
void reload()
return {
@@ -293,11 +267,10 @@ export default {
archiveEmptyState,
closeSelectedDetail,
error,
filterDropdowns,
filterMenus,
handleEmptyAction,
listKeyword,
loading,
openFilterKey,
reload,
resetListFilters,
rows,
@@ -305,7 +278,6 @@ export default {
selectedRow,
showEmpty,
tabs,
toggleFilterDropdown,
visibleRows
}
}

View File

@@ -1,11 +1,13 @@
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
import AuditAssetList from '../../components/audit/AuditAssetList.vue'
import AuditJsonRiskRuleDetail from '../../components/audit/AuditJsonRiskRuleDetail.vue'
import AuditRuleDialogs from '../../components/audit/AuditRuleDialogs.vue'
import AuditSpreadsheetChangeDrawer from '../../components/audit/AuditSpreadsheetChangeDrawer.vue'
import AuditSpreadsheetRuleDetail from '../../components/audit/AuditSpreadsheetRuleDetail.vue'
import AuditVersionTimelineDrawer from '../../components/audit/AuditVersionTimelineDrawer.vue'
import { fetchEmployees } from '../../services/employees.js'
import RiskRuleFlowDiagram from '../../components/shared/RiskRuleFlowDiagram.vue'
import RiskRuleTestDialog from '../../components/shared/RiskRuleTestDialog.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
import {
@@ -77,11 +79,13 @@ import {
export default {
name: 'AuditView',
components: {
ConfirmDialog,
RiskRuleFlowDiagram,
RiskRuleTestDialog,
TableLoadingState,
TableEmptyState
AuditAssetList,
AuditJsonRiskRuleDetail,
AuditRuleDialogs,
AuditSpreadsheetChangeDrawer,
AuditSpreadsheetRuleDetail,
AuditVersionTimelineDrawer,
TableLoadingState
},
emits: ['detail-open-change'],
setup(_, { emit }) {
@@ -115,6 +119,10 @@ export default {
const reviewSubmitReviewer = ref('')
const reviewSubmitReviewerLoading = ref(false)
const reviewSubmitReviewerOptions = ref([])
const riskRuleAttachmentOptions = [
{ label: '是', value: true },
{ label: '否', value: false }
]
const riskRuleCreateOpen = ref(false)
const riskRuleCreateForm = ref(createDefaultRiskRuleForm())
const riskRuleTestOpen = ref(false)
@@ -466,7 +474,7 @@ export default {
icon: hasFilters ? 'mdi mdi-tune-variant' : 'mdi mdi-view-grid-outline',
actionLabel: hasFilters ? '清空筛选' : '',
actionIcon: hasFilters ? 'mdi mdi-filter-remove-outline' : '',
tone: hasFilters ? 'emerald' : 'slate',
tone: hasFilters ? 'primary' : 'slate',
artLabel: hasFilters ? 'FILTER' : 'QUEUE',
tips: hasFilters
? [
@@ -956,7 +964,7 @@ export default {
if (!canUploadSpreadsheet.value) {
return
}
spreadsheetUploadInput.value?.click()
spreadsheetUploadInput.value?.click?.()
}
async function downloadSpreadsheetFile() {
@@ -1003,9 +1011,7 @@ export default {
toast(error?.message || '规则表内容导入失败,请稍后重试。')
} finally {
actionState.value = ''
if (spreadsheetUploadInput.value) {
spreadsheetUploadInput.value.value = ''
}
spreadsheetUploadInput.value?.reset?.()
}
}
@@ -1916,6 +1922,7 @@ export default {
reviewSubmitReviewer,
reviewSubmitReviewerLoading,
reviewSubmitReviewerOptions,
riskRuleAttachmentOptions,
riskRuleCreateOpen,
riskRuleCreateForm,
riskRuleCreateBusy,

View File

@@ -2,6 +2,7 @@ import { computed, onMounted, ref, watch } from 'vue'
import BudgetTrendChart from '../../components/charts/BudgetTrendChart.vue'
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import { createBudgetAllocation, fetchBudgetSummary } from '../../services/budgets.js'
import { fetchEmployeeMeta } from '../../services/employees.js'
import {
@@ -177,7 +178,8 @@ export default {
},
components: {
BudgetTrendChart,
ConfirmDialog
ConfirmDialog,
EnterpriseSelect
},
setup(props) {
const departments = ref(FALLBACK_DEPARTMENTS)
@@ -215,6 +217,14 @@ export default {
const isDepartmentBudgetMonitor = computed(
() => isBudgetMonitorUser(props.currentUser) && !canSwitchDepartments.value && !isExecutiveUser(props.currentUser)
)
const yearOptions = BUDGET_YEAR_OPTIONS.map((year) => ({ label: `${year}年度`, value: year }))
const budgetPageSizeOptions = BUDGET_PAGE_SIZE_OPTIONS.map((size) => ({ label: `${size} 条/页`, value: size }))
const departmentOptions = computed(() =>
departments.value.map((department) => ({
label: department.name,
value: department.code
}))
)
const activeDepartment = computed(() =>
departments.value.find((item) => item.code === activeDepartmentCode.value) || departments.value[0]
@@ -273,7 +283,7 @@ export default {
value: `¥${currency(totals.value.total)}`,
yoy: comparison('+8.42%', 'up'),
mom: comparison('+2.16%', 'up'),
tone: 'green',
tone: 'primary',
icon: 'mdi mdi-wallet-outline'
},
{
@@ -281,7 +291,7 @@ export default {
value: `¥${currency(totals.value.used)}`,
yoy: comparison('+12.68%', 'up'),
mom: comparison('+4.35%', 'up'),
tone: 'blue',
tone: 'info',
icon: 'mdi mdi-chart-line'
},
{
@@ -289,7 +299,7 @@ export default {
value: `¥${currency(totals.value.occupied)}`,
yoy: comparison('+6.37%', 'up'),
mom: comparison('-1.84%', 'down'),
tone: 'orange',
tone: 'warning',
icon: 'mdi mdi-briefcase-check-outline'
},
{
@@ -297,7 +307,7 @@ export default {
value: `¥${currency(totals.value.left)}`,
yoy: comparison('-3.26%', 'down'),
mom: comparison('-2.08%', 'down'),
tone: 'green',
tone: 'primary',
icon: 'mdi mdi-cash'
}
])
@@ -612,7 +622,7 @@ export default {
budgetPage: currentBudgetPage,
budgetPageNumbers,
budgetPageSize,
budgetPageSizeOptions: BUDGET_PAGE_SIZE_OPTIONS,
budgetPageSizeOptions,
canEditBudget,
canSwitchDepartments,
closeBudgetEditDialog,
@@ -633,6 +643,7 @@ export default {
confirmDeleteRow,
cancelDeleteRow,
cancelSaveBudget,
departmentOptions,
requestSaveBudget,
statusOptions: BUDGET_STATUS_OPTIONS,
statuses: ['全部', '正常', '预警', '管控'],
@@ -645,6 +656,7 @@ export default {
visibleDepartments,
warningOptions: BUDGET_WARNING_OPTIONS,
warnings,
yearOptions,
years: BUDGET_YEAR_OPTIONS
}
}

View File

@@ -1,6 +1,7 @@
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
import { useToast } from '../../composables/useToast.js'
@@ -438,6 +439,7 @@ export default {
name: 'EmployeeManagementView',
components: {
ConfirmDialog,
EnterpriseSelect,
TableLoadingState,
TableEmptyState
},
@@ -457,7 +459,7 @@ export default {
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const pageSizeOpen = ref(false)
const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size }))
const actionState = ref('')
const loading = ref(false)
const errorMessage = ref('')
@@ -713,7 +715,7 @@ export default {
icon: hasEmployeeFilters.value ? 'mdi mdi-account-search-outline' : 'mdi mdi-badge-account-horizontal-outline',
actionLabel: hasEmployeeFilters.value ? '清空筛选' : '查看全部员工',
actionIcon: hasEmployeeFilters.value ? 'mdi mdi-filter-remove-outline' : 'mdi mdi-format-list-bulleted',
tone: hasEmployeeFilters.value ? 'emerald' : 'slate',
tone: hasEmployeeFilters.value ? 'primary' : 'slate',
artLabel: hasEmployeeFilters.value ? 'FILTER' : 'STATUS',
tips: hasEmployeeFilters.value
? ['关键词、部门、职级和角色条件会叠加生效', '也可以直接搜索姓名、工号或岗位']
@@ -791,7 +793,6 @@ export default {
watch(filteredEmployees, () => {
currentPage.value = 1
pageSizeOpen.value = false
})
function resetFilters() {
@@ -801,7 +802,6 @@ export default {
selectedRole.value = ''
activeTab.value = DEFAULT_STATUS_TABS[0]
activeFilterPopover.value = ''
pageSizeOpen.value = false
}
function handleEmployeeEmptyAction() {
@@ -815,14 +815,9 @@ export default {
function changePageSize(size) {
pageSize.value = size
pageSizeOpen.value = false
currentPage.value = 1
}
function togglePageSizeOpen() {
pageSizeOpen.value = !pageSizeOpen.value
}
function toggleFilterPopover(name) {
activeFilterPopover.value = activeFilterPopover.value === name ? '' : name
}
@@ -852,7 +847,6 @@ export default {
if (!(target instanceof Element)) {
closeFilterPopover()
pageSizeOpen.value = false
return
}
@@ -868,13 +862,8 @@ export default {
closeDepartmentPicker()
}
if (!target.closest('.page-size-wrap')) {
pageSizeOpen.value = false
}
if (
target.closest('.picker-filter') ||
target.closest('.page-size-wrap') ||
target.closest('.manager-picker') ||
target.closest('.department-picker')
) {
@@ -1437,7 +1426,7 @@ export default {
currentPage,
pageSize,
pageSizes,
pageSizeOpen,
pageSizeOptions,
departmentOptions,
gradeOptions,
roleFilterOptions,
@@ -1475,7 +1464,6 @@ export default {
disableDialogOpen,
disableEmployeeAccount,
changePageSize,
togglePageSizeOpen,
toggleFilterPopover,
closeFilterPopover,
selectFilter,

View File

@@ -1,6 +1,7 @@
import { ref } from 'vue'
import { testModelConnectivity } from '../../services/settings.js'
import { useToast } from '../../composables/useToast.js'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
const MODEL_SECRET_MASK = '********'
@@ -101,6 +102,9 @@ function isModelSecretMask(value) {
export default {
name: 'LlmSettingsPanel',
components: {
EnterpriseSelect
},
props: {
llmForm: {
type: Object,

View File

@@ -11,7 +11,7 @@ export default {
const showPassword = ref(false)
const features = [
{ title: '智能审单', desc: 'AI 自动识别票据与规则,提升准确率与效率', icon: 'mdi mdi-file-document-outline', tone: 'green' },
{ title: '智能审单', desc: 'AI 自动识别票据与规则,提升准确率与效率', icon: 'mdi mdi-file-document-outline', tone: 'primary' },
{ title: '异常预警', desc: '多维风险识别与预警,主动防控风险', icon: 'mdi mdi-bell-outline', tone: 'red' },
{ title: 'SLA 监控', desc: '实时监控服务水平协议,保障审批及时性', icon: 'mdi mdi-sync', tone: 'blue' }
]

View File

@@ -3,6 +3,7 @@ import { useRouter } from 'vue-router'
import LogTrendChart from '../../components/charts/LogTrendChart.vue'
import DonutChart from '../../components/charts/DonutChart.vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import { fetchAgentRuns } from '../../services/agentAssets.js'
import { fetchSystemLogEntries } from '../../services/systemLogs.js'
@@ -221,6 +222,7 @@ export default {
components: {
LogTrendChart,
DonutChart,
EnterpriseSelect,
TableLoadingState
},
emits: ['summary-change'],
@@ -237,20 +239,28 @@ export default {
const systemLevelFilter = ref('')
const systemEventTypeFilter = ref('')
const systemLogEntries = ref([])
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const pageSizeOpen = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size }))
let pollTimer = 0
const isAdmin = computed(() => isManagerUser(currentUser.value))
const filteredHermesRuns = computed(() => hermesRuns.value)
const systemLevelOptions = computed(() =>
Array.from(new Set(systemLogEntries.value.map((entry) => entry.level).filter(Boolean)))
)
const systemEventTypeOptions = computed(() =>
Array.from(new Set(systemLogEntries.value.map((entry) => entry.event_type).filter(Boolean)))
)
const systemLevelOptions = computed(() =>
Array.from(new Set(systemLogEntries.value.map((entry) => entry.level).filter(Boolean)))
)
const systemEventTypeOptions = computed(() =>
Array.from(new Set(systemLogEntries.value.map((entry) => entry.event_type).filter(Boolean)))
)
const systemLevelFilterOptions = computed(() => [
{ label: '全部', value: '' },
...systemLevelOptions.value
])
const systemEventTypeFilterOptions = computed(() => [
{ label: '全部', value: '' },
...systemEventTypeOptions.value
])
const filteredSystemLogEntries = computed(() => {
const keyword = systemSearchKeyword.value.trim().toLowerCase()
return systemLogEntries.value.filter((entry) => {
@@ -330,11 +340,10 @@ export default {
return filteredSystemLogEntries.value.slice(start, start + pageSize.value)
})
function changePageSize(size) {
pageSize.value = size
pageSizeOpen.value = false
currentPage.value = 1
}
function changePageSize(size) {
pageSize.value = size
currentPage.value = 1
}
async function loadHermesRuns() {
if (!isAdmin.value) {
@@ -456,8 +465,8 @@ export default {
loadSystemLogs,
currentPage,
pageSize,
pageSizeOpen,
pageSizes,
pageSizes,
pageSizeOptions,
resolveLevelTone,
resolveRunLevel,
resolveRunModuleLabel,
@@ -472,10 +481,12 @@ export default {
runningRunCount,
selectRun,
selectSystemLog,
systemEventTypeFilter,
systemEventTypeOptions,
systemLevelFilter,
systemLevelOptions,
systemEventTypeFilter,
systemEventTypeFilterOptions,
systemEventTypeOptions,
systemLevelFilter,
systemLevelFilterOptions,
systemLevelOptions,
systemLogEntries,
systemLogLoading,
systemSearchKeyword,

View File

@@ -1,5 +1,10 @@
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
export default {
name: 'MailSettingsPanel',
components: {
EnterpriseSelect
},
props: {
mailForm: {
type: Object,
@@ -7,6 +12,12 @@ export default {
}
},
setup(props) {
const encryptionOptions = [
{ label: 'SSL/TLS', value: 'SSL/TLS' },
{ label: 'STARTTLS', value: 'STARTTLS' },
{ label: '无', value: 'None' }
]
function toggleField(field) {
if (props.mailForm) {
props.mailForm[field] = !props.mailForm[field]
@@ -14,6 +25,7 @@ export default {
}
return {
encryptionOptions,
toggleField
}
}

View File

@@ -36,11 +36,11 @@ export default {
}
const demoDepartments = [
{ name: '销售部', amount: 182000, color: '#10b981' },
{ name: '研发中心', amount: 146000, color: '#3b82f6' },
{ name: '市场部', amount: 96000, color: '#f59e0b' },
{ name: '运营部', amount: 68600, color: '#8b5cf6' },
{ name: '行政部', amount: 48300, color: '#3b82f6' }
{ name: '销售部', amount: 182000, color: 'var(--theme-primary)' },
{ name: '研发中心', amount: 146000, color: 'var(--chart-blue)' },
{ name: '市场部', amount: 96000, color: 'var(--chart-amber)' },
{ name: '运营部', amount: 68600, color: 'var(--chart-purple)' },
{ name: '行政部', amount: 48300, color: 'var(--chart-blue)' }
]
const formatCompact = (value) => {

View File

@@ -1,6 +1,7 @@
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
@@ -86,6 +87,7 @@ export default {
name: 'PoliciesView',
components: {
ConfirmDialog,
EnterpriseSelect,
TableLoadingState
},
emits: ['summary-change'],
@@ -95,13 +97,13 @@ export default {
const documentSearch = ref('')
const activeFolder = ref('')
const folders = ref([])
const documents = ref([])
const selectedDocument = ref(null)
const pageSizeOpen = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const folders = ref([])
const documents = ref([])
const selectedDocument = ref(null)
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size }))
const loading = ref(false)
const uploadInput = ref(null)
const uploading = ref(false)
@@ -558,11 +560,10 @@ export default {
}
}
function changePageSize(size) {
pageSize.value = size
pageSizeOpen.value = false
currentPage.value = 1
}
function changePageSize(size) {
pageSize.value = size
currentPage.value = 1
}
function closePreview() {
selectedDocument.value = null
@@ -586,12 +587,11 @@ export default {
currentPreviewPageIndex.value = index
}
watch(filteredDocuments, () => {
currentPage.value = 1
pageSizeOpen.value = false
if (selectedDocument.value && !filteredDocuments.value.some((doc) => doc.id === selectedDocument.value.id)) {
closePreview()
watch(filteredDocuments, () => {
currentPage.value = 1
if (selectedDocument.value && !filteredDocuments.value.some((doc) => doc.id === selectedDocument.value.id)) {
closePreview()
}
})
@@ -650,9 +650,9 @@ export default {
knowledgeSyncButtonLabel,
knowledgeSyncHint,
loading,
pageSize,
pageSizeOpen,
pageSizes,
pageSize,
pageSizeOptions,
pageSizes,
onlyOfficeError,
onlyOfficeHostId,
onlyOfficeLoading,

View File

@@ -1,5 +1,6 @@
import { computed, ref, watch } from 'vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
import { normalizeRequestForUi } from '../../utils/requestViewModel.js'
@@ -12,6 +13,7 @@ function extractRowDate(value) {
export default {
name: 'RequestsView',
components: {
EnterpriseSelect,
TableLoadingState,
TableEmptyState
},
@@ -61,11 +63,10 @@ export default {
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const pageSizeOpen = ref(false)
const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size }))
function changePageSize(size) {
pageSize.value = size
pageSizeOpen.value = false
currentPage.value = 1
}
@@ -132,7 +133,7 @@ export default {
icon: 'mdi mdi-receipt-text-plus-outline',
actionLabel: '',
actionIcon: '',
tone: 'emerald',
tone: 'theme',
artLabel: 'CLAIM',
tips: ['保存草稿后会自动回到这里', '支持草稿、待提交、审批中和已完成全流程管理']
}
@@ -163,7 +164,6 @@ export default {
rangeEnd.value = ''
appliedStart.value = ''
appliedEnd.value = ''
pageSizeOpen.value = false
currentPage.value = 1
}
@@ -197,7 +197,7 @@ export default {
currentPage,
pageSize,
pageSizes,
pageSizeOpen,
pageSizeOptions,
changePageSize,
filteredRows,
totalCount,

View File

@@ -1,12 +1,14 @@
import HermesEmployeeSettingsPanel from '../HermesEmployeeSettingsPanel.vue'
import LlmSettingsPanel from '../LlmSettingsPanel.vue'
import MailSettingsPanel from '../MailSettingsPanel.vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import { useSettings } from '../../composables/useSettings.js'
export default {
name: 'SettingsView',
components: {
HermesEmployeeSettingsPanel,
EnterpriseSelect,
LlmSettingsPanel,
MailSettingsPanel
},
@@ -18,4 +20,3 @@ export default {
}
}
}

View File

@@ -2,6 +2,8 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
import TravelReimbursementInsightPanel from '../../components/travel/TravelReimbursementInsightPanel.vue'
import TravelReimbursementMessageItem from '../../components/travel/TravelReimbursementMessageItem.vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
import { useTravelReimbursementFlow } from './useTravelReimbursementFlow.js'
@@ -501,7 +503,9 @@ function buildReviewMainMessageText(message) {
export default {
name: 'TravelReimbursementCreateView',
components: {
ConfirmDialog
ConfirmDialog,
TravelReimbursementInsightPanel,
TravelReimbursementMessageItem
},
props: {
initialPrompt: {
@@ -2109,8 +2113,126 @@ export default {
await handleSaveDraftDirectly(message, 'save_draft')
}
const messageItemUi = computed(() => ({
ASSISTANT_DISPLAY_NAME,
aiAvatar,
userAvatar,
submitting: submitting.value,
reviewActionBusy: reviewActionBusy.value,
sessionSwitchBusy: sessionSwitchBusy.value,
applicationPreviewEditor: applicationPreviewEditor.value,
buildMessageBubbleClass,
buildReviewMainMessageText,
renderMarkdown,
handleAssistantMarkdownClick,
resolveApplicationPreviewRows,
resolveApplicationPreviewEditorControl,
resolveApplicationPreviewEditorOptions,
isApplicationPreviewEditing,
openApplicationPreviewEditor,
commitApplicationPreviewEditor,
handleApplicationPreviewEditorKeydown,
buildApplicationPreviewFooterText,
runWelcomeQuickAction: runShortcut,
handleSuggestedAction,
isSuggestedActionSelected,
buildExpenseQueryWindowLabel,
buildExpenseQueryHint,
getExpenseQueryActivePage,
getExpenseQueryTotalPages,
getExpenseQueryVisibleRecords,
handleExpenseQueryRecordClick,
appendExpenseQueryRiskToConversation,
shiftExpenseQueryPage,
setExpenseQueryPage,
buildReviewPlainFollowupForMessage,
canUseInlineSaveDraft,
handleInlineSaveDraft,
buildReviewNextStepRichCopyForMessage,
resolveReviewFooterActions,
handleReviewAction,
buildReviewPrimaryButtonLabel
}))
const insightPanelUi = computed(() => ({
showInsightPanel: showInsightPanel.value,
isKnowledgeSession: isKnowledgeSession.value,
activeReviewPayload: activeReviewPayload.value,
isReviewFlowDrawer: isReviewFlowDrawer.value,
currentInsight: currentInsight.value,
currentIntentLabel: currentIntentLabel.value,
reviewDrawerTitle: reviewDrawerTitle.value,
reviewOverviewDrawerAvailable: reviewOverviewDrawerAvailable.value,
isReviewOverviewDrawer: isReviewOverviewDrawer.value,
submitting: submitting.value,
reviewActionBusy: reviewActionBusy.value,
switchToReviewOverviewDrawer,
reviewDocumentDrawerAvailable: reviewDocumentDrawerAvailable.value,
isReviewDocumentDrawer: isReviewDocumentDrawer.value,
toggleReviewDocumentDrawer,
reviewDocumentDrawerIcon: reviewDocumentDrawerIcon.value,
reviewRiskDrawerAvailable: reviewRiskDrawerAvailable.value,
isReviewRiskDrawer: isReviewRiskDrawer.value,
toggleReviewRiskDrawer,
reviewRiskDrawerIcon: reviewRiskDrawerIcon.value,
reviewFlowDrawerAvailable: reviewFlowDrawerAvailable.value,
flowOverallStatusTone: flowOverallStatusTone.value,
toggleReviewFlowDrawer,
reviewFlowDrawerIcon: reviewFlowDrawerIcon.value,
activeSessionType: activeSessionType.value,
reviewDrawerMode: reviewDrawerMode.value,
hotKnowledgeQuestions,
deleteSessionBusy: deleteSessionBusy.value,
sessionSwitchBusy: sessionSwitchBusy.value,
askHotKnowledgeQuestion,
resolveKnowledgeRankTone,
resolveKnowledgeRankLabel,
flowOverallStatusText: flowOverallStatusText.value,
flowTotalDurationText: flowTotalDurationText.value,
flowRunId: flowRunId.value,
flowRefreshBusy: flowRefreshBusy.value,
refreshFlowRunDetail,
flowSteps: flowSteps.value,
resolveFlowStepStatusLabel,
formatFlowStepDuration,
resolveFlowStepDetail,
reviewIntentText: reviewIntentText.value,
reviewFactCards: reviewFactCards.value,
reviewInlineEditorKey: reviewInlineEditorKey.value,
reviewInlineErrors: reviewInlineErrors.value,
reviewInlineForm: reviewInlineForm.value,
DATE_INPUT_FORMAT,
REVIEW_SCENE_OPTIONS,
REVIEW_SCENE_OTHER_OPTION,
clearInlineReviewFieldError,
commitInlineReviewEditor,
selectInlineScene,
reviewInlinePendingFiles: reviewInlinePendingFiles.value,
openInlineReviewEditor,
reviewPanelConfidence: reviewPanelConfidence.value,
reviewCategoryOptions: reviewCategoryOptions.value,
selectReviewCategory,
reviewSelectedOtherCategory: reviewSelectedOtherCategory.value,
reviewOtherCategoryOpen: reviewOtherCategoryOpen.value,
reviewOtherCategoryOptions: reviewOtherCategoryOptions.value,
selectReviewOtherCategory,
activeReviewDocumentIndex: activeReviewDocumentIndex.value,
reviewDocumentCount: reviewDocumentCount.value,
goReviewDocument,
activeReviewDocument: activeReviewDocument.value,
activeReviewDocumentPreview: activeReviewDocumentPreview.value,
canPreviewActiveReviewDocument: canPreviewActiveReviewDocument.value,
openActiveReviewDocumentPreview,
reviewRiskSummary: reviewRiskSummary.value,
reviewRiskItems: reviewRiskItems.value,
appendReviewRiskBriefToConversation,
reviewRiskEmpty: reviewRiskEmpty.value,
reviewHasUnsavedChanges: reviewHasUnsavedChanges.value,
saveInlineReviewChanges
}))
return {
emit, ASSISTANT_DISPLAY_NAME, aiAvatar, userAvatar, fileInputRef, composerTextareaRef, messageListRef, composerDraft, composerDatePickerOpen, composerDateMode, composerSingleDate, composerRangeStartDate, composerRangeEndDate, composerBusinessTimeTags, composerCanApplyDateSelection,
emit, messageItemUi, insightPanelUi, ASSISTANT_DISPLAY_NAME, aiAvatar, userAvatar, fileInputRef, composerTextareaRef, messageListRef, composerDraft, composerDatePickerOpen, composerDateMode, composerSingleDate, composerRangeStartDate, composerRangeEndDate, composerBusinessTimeTags, composerCanApplyDateSelection,
toggleComposerDatePicker, closeComposerDatePicker, setComposerDateMode, handleComposerDateInputChange, removeComposerBusinessTimeTag, flowSteps, flowRunId, flowRefreshBusy, completedFlowStepCount, flowOverallStatusTone, flowOverallStatusText, flowTotalDurationText,
attachedFiles, composerFilesExpanded, visibleAttachedFiles, hiddenAttachedFileCount, submitting, sessionSwitchBusy, messages, currentInsight, linkedRequest, canSubmit, activeSessionType, isKnowledgeSession, hotKnowledgeQuestions,
hasInsightPanelContent, showInsightPanel, insightPanelToggleLabel, assistantHeaderTitle, assistantHeaderDescription, composerPlaceholder, currentIntentLabel, canDeleteCurrentSession, latestReviewMessage, activeReviewPayload, activeReviewPanelScope, activeReviewFilePreviews, reviewDrawerMode, isReviewOverviewDrawer, isReviewDocumentDrawer, isReviewRiskDrawer, isReviewFlowDrawer,

View File

@@ -2,8 +2,11 @@ import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
import ReturnReasonDialog from '../../components/shared/ReturnReasonDialog.vue'
import TravelRequestApprovalDialog from '../../components/travel/TravelRequestApprovalDialog.vue'
import TravelRequestDeleteDialog from '../../components/travel/TravelRequestDeleteDialog.vue'
import TravelRequestReturnDialog from '../../components/travel/TravelRequestReturnDialog.vue'
import {
approveExpenseClaim,
createExpenseClaimItem,
@@ -358,7 +361,10 @@ export default {
name: 'TravelRequestDetailView',
components: {
ConfirmDialog,
ReturnReasonDialog
EnterpriseSelect,
TravelRequestApprovalDialog,
TravelRequestDeleteDialog,
TravelRequestReturnDialog
},
props: {
request: {

View File

@@ -71,7 +71,7 @@ export const TAB_META = {
tableColumns: RULE_TABLE_COLUMNS,
showRuntimeColumn: false,
showStatusColumn: false,
badgeTone: 'emerald'
badgeTone: 'primary'
},
riskRules: {
assetType: 'rule',

View File

@@ -346,7 +346,7 @@ export function resolveTabMeta(tabId, typeKey) {
return {
...TYPE_META.rules,
typeKey: 'rules',
badgeTone: 'emerald'
badgeTone: 'primary'
}
}
return TAB_META[typeKey]