feat: 引入 ECharts 统一图表并完善员工画像标签分页
后端优化员工行为画像服务和辅助函数,完善系统设置模型和 配置持久化,前端引入 ECharts 替换所有图表组件实现统一 渲染,新增员工画像标签分页器和数字员工工作记录组件,优 化工作台响应式布局和登录页过渡动画,完善预算中心和数字 员工页面样式细节。
This commit is contained in:
@@ -221,7 +221,7 @@
|
||||
|
||||
<article class="panel workbench-card side-panel usage-profile-panel">
|
||||
<div class="section-head side-card-head">
|
||||
<h2>费用画像</h2>
|
||||
<h2>用户画像</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="detail-action"
|
||||
@@ -230,11 +230,11 @@
|
||||
@click="openExpenseProfileModal"
|
||||
>
|
||||
<span>查看详情</span>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
<i :class="employeeProfileLoading ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-chevron-right'"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="insight-profile-list" aria-label="费用画像">
|
||||
<div class="insight-profile-list" aria-label="用户画像">
|
||||
<div
|
||||
v-for="metric in visibleUsageProfileMetrics"
|
||||
:key="metric.key"
|
||||
@@ -264,8 +264,10 @@
|
||||
:tags="expenseProfileTags"
|
||||
:radar-dimensions="expenseProfileRadarDimensions"
|
||||
:operations="expenseProfileOperations"
|
||||
:loading="employeeProfileLoading"
|
||||
:error-message="employeeProfileError"
|
||||
:empty-reason="expenseProfileEmptyReason"
|
||||
@close="closeExpenseProfileModal"
|
||||
@explain="explainExpenseProfile"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
@@ -281,20 +283,26 @@ import { useToast } from '../../composables/useToast.js'
|
||||
import {
|
||||
assistantCapabilities,
|
||||
buildExpenseStatItems,
|
||||
expenseProfileOperations,
|
||||
expenseProfileRadarDimensions,
|
||||
expenseProfileTags,
|
||||
progressItems,
|
||||
progressSteps,
|
||||
quickPromptItems,
|
||||
todoItems,
|
||||
usageProfileMetrics
|
||||
} from '../../data/personalWorkbench.js'
|
||||
import { fetchAgentRuns } from '../../services/agentAssets.js'
|
||||
import { clearUserConversations, fetchLatestConversation } from '../../services/orchestrator.js'
|
||||
import { fetchCurrentEmployeeLatestProfile } from '../../services/reimbursements.js'
|
||||
import {
|
||||
ASSISTANT_SESSION_SNAPSHOT_EVENT,
|
||||
hasAssistantSessionSnapshot
|
||||
} from '../../utils/assistantSessionSnapshot.js'
|
||||
import {
|
||||
buildProfileOperationsFromAgentRuns,
|
||||
buildUserProfileMetricCards,
|
||||
buildUserProfileSummaryMetrics,
|
||||
normalizeUserProfileRadarDimensions,
|
||||
normalizeUserProfileTags,
|
||||
resolveCurrentUserProfileError
|
||||
} from '../../utils/employeeProfileViewModel.js'
|
||||
|
||||
const props = defineProps({
|
||||
showHeader: { type: Boolean, default: true },
|
||||
@@ -313,6 +321,11 @@ const pendingAction = ref('')
|
||||
const latestExpenseConversation = ref(null)
|
||||
const hasLocalExpenseSnapshot = ref(false)
|
||||
const expenseProfileModalOpen = ref(false)
|
||||
const employeeProfile = ref(null)
|
||||
const employeeProfileRuns = ref([])
|
||||
const employeeProfileLoading = ref(false)
|
||||
const employeeProfileError = ref('')
|
||||
let employeeProfileLoadSeq = 0
|
||||
const MAX_ATTACHMENTS = 10
|
||||
const SESSION_TYPE_EXPENSE = 'expense'
|
||||
const SESSION_TYPE_KNOWLEDGE = 'knowledge'
|
||||
@@ -359,16 +372,34 @@ const visibleExpenseStatItems = computed(() => {
|
||||
.filter(Boolean)
|
||||
})
|
||||
const visibleUsageProfileMetrics = computed(() => {
|
||||
const preferredKeys = ['ai-usage', 'submit-efficiency', 'auto-pass-rate', 'audit-duration']
|
||||
return preferredKeys
|
||||
.map((key) => usageProfileMetrics.find((item) => item.key === key))
|
||||
.filter(Boolean)
|
||||
return buildUserProfileMetricCards(
|
||||
employeeProfile.value,
|
||||
employeeProfileRuns.value,
|
||||
currentUser.value
|
||||
).slice(0, 4)
|
||||
})
|
||||
const expenseProfileModalMetrics = computed(() => {
|
||||
const preferredKeys = ['stay-duration', 'ai-usage', 'auto-pass-rate', 'audit-duration']
|
||||
return preferredKeys
|
||||
.map((key) => usageProfileMetrics.find((item) => item.key === key))
|
||||
.filter(Boolean)
|
||||
return buildUserProfileSummaryMetrics(
|
||||
employeeProfile.value,
|
||||
employeeProfileRuns.value,
|
||||
currentUser.value
|
||||
)
|
||||
})
|
||||
const expenseProfileTags = computed(() => normalizeUserProfileTags(employeeProfile.value))
|
||||
const expenseProfileRadarDimensions = computed(() => normalizeUserProfileRadarDimensions(employeeProfile.value))
|
||||
const expenseProfileOperations = computed(() =>
|
||||
buildProfileOperationsFromAgentRuns(employeeProfileRuns.value, currentUser.value)
|
||||
)
|
||||
const expenseProfileEmptyReason = computed(() => String(employeeProfile.value?.empty_reason || '').trim())
|
||||
const currentUserProfileKey = computed(() => {
|
||||
const user = currentUser.value || {}
|
||||
return [
|
||||
user.username,
|
||||
user.email,
|
||||
user.name,
|
||||
user.employeeNo,
|
||||
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))
|
||||
@@ -469,19 +500,46 @@ function openPromptAssistant(prompt) {
|
||||
})
|
||||
}
|
||||
|
||||
async function loadCurrentEmployeeProfile() {
|
||||
const sequence = ++employeeProfileLoadSeq
|
||||
employeeProfileLoading.value = true
|
||||
employeeProfileError.value = ''
|
||||
|
||||
const [profileResult, runsResult] = await Promise.allSettled([
|
||||
fetchCurrentEmployeeLatestProfile({
|
||||
scene: 'operations',
|
||||
window_days: 90,
|
||||
expense_type_scope: 'overall'
|
||||
}),
|
||||
fetchAgentRuns({ limit: 100 })
|
||||
])
|
||||
|
||||
if (sequence !== employeeProfileLoadSeq) {
|
||||
return
|
||||
}
|
||||
|
||||
if (profileResult.status === 'fulfilled') {
|
||||
employeeProfile.value = profileResult.value || null
|
||||
} else {
|
||||
employeeProfile.value = null
|
||||
employeeProfileError.value = resolveCurrentUserProfileError(profileResult.reason)
|
||||
}
|
||||
|
||||
employeeProfileRuns.value = runsResult.status === 'fulfilled' ? runsResult.value || [] : []
|
||||
employeeProfileLoading.value = false
|
||||
}
|
||||
|
||||
function openExpenseProfileModal() {
|
||||
expenseProfileModalOpen.value = true
|
||||
if (!employeeProfile.value && !employeeProfileLoading.value) {
|
||||
void loadCurrentEmployeeProfile()
|
||||
}
|
||||
}
|
||||
|
||||
function closeExpenseProfileModal() {
|
||||
expenseProfileModalOpen.value = false
|
||||
}
|
||||
|
||||
function explainExpenseProfile() {
|
||||
closeExpenseProfileModal()
|
||||
openPromptAssistant('请根据我的费用画像标签、行为雷达和最近 5 次操作,解释我的费用使用特点和可以优化的地方。')
|
||||
}
|
||||
|
||||
function handleWorkbenchEnter(event) {
|
||||
if (event.isComposing) {
|
||||
return
|
||||
@@ -570,6 +628,7 @@ async function handleExpenseConversationAction() {
|
||||
onMounted(() => {
|
||||
refreshLocalExpenseSnapshot()
|
||||
refreshLatestExpenseConversation()
|
||||
loadCurrentEmployeeProfile()
|
||||
window.addEventListener(ASSISTANT_SESSION_SNAPSHOT_EVENT, handleAssistantSessionSnapshotChange)
|
||||
})
|
||||
|
||||
@@ -585,6 +644,12 @@ watch(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(currentUserProfileKey, (nextKey, previousKey) => {
|
||||
if (nextKey && nextKey !== previousKey) {
|
||||
loadCurrentEmployeeProfile()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped src="../../assets/styles/components/personal-workbench.css"></style>
|
||||
|
||||
Reference in New Issue
Block a user