feat: 新增员工行为画像算法与费用风险标签体系
后端新增员工行为画像算法模块,支持标签规则引擎和评分计算, 完善员工模型、银行信息、序列化和导入逻辑,优化报销审批流 和工作流常量,增强 Hermes 同步和知识同步能力,前端新增费 用画像详情弹窗、雷达图和风险卡片组件,完善登录页和工作台 样式,优化文档中心和归档中心交互,补充单元测试。
This commit is contained in:
154
web/src/components/charts/RadarChart.vue
Normal file
154
web/src/components/charts/RadarChart.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div class="radar-chart">
|
||||
<Radar :data="chartData" :options="chartOptions" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { Radar } from 'vue-chartjs'
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Filler,
|
||||
Legend,
|
||||
LineElement,
|
||||
PointElement,
|
||||
RadialLinearScale,
|
||||
Tooltip
|
||||
} from 'chart.js'
|
||||
|
||||
import { useThemeColors } from '../../composables/useThemeColors.js'
|
||||
|
||||
ChartJS.register(RadialLinearScale, PointElement, LineElement, Filler, Tooltip, Legend)
|
||||
|
||||
const props = defineProps({
|
||||
items: { type: Array, required: true },
|
||||
label: { type: String, default: '行为评分' },
|
||||
max: { type: Number, default: 100 }
|
||||
})
|
||||
|
||||
const themeColors = useThemeColors()
|
||||
|
||||
const normalizedItems = computed(() =>
|
||||
props.items.map((item) => ({
|
||||
code: String(item.code || item.label || '').trim(),
|
||||
label: String(item.label || item.code || '').trim(),
|
||||
score: clampScore(item.score)
|
||||
}))
|
||||
)
|
||||
|
||||
const chartData = computed(() => {
|
||||
const primary = themeColors.value.chartPrimary
|
||||
|
||||
return {
|
||||
labels: normalizedItems.value.map((item) => item.label),
|
||||
datasets: [
|
||||
{
|
||||
label: props.label,
|
||||
data: normalizedItems.value.map((item) => item.score),
|
||||
borderColor: primary,
|
||||
backgroundColor: toRgba(primary, 0.16),
|
||||
pointBackgroundColor: '#ffffff',
|
||||
pointBorderColor: primary,
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 3,
|
||||
pointHoverRadius: 4,
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.18
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const chartOptions = computed(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: 760,
|
||||
easing: 'easeOutQuart'
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.98)',
|
||||
titleColor: '#0f172a',
|
||||
bodyColor: '#475569',
|
||||
borderColor: 'rgba(148, 163, 184, 0.28)',
|
||||
borderWidth: 1,
|
||||
cornerRadius: 4,
|
||||
padding: 10,
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
label: (context) => `${context.dataset.label}: ${context.parsed.r}`
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
r: {
|
||||
min: 0,
|
||||
max: props.max,
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
display: false,
|
||||
stepSize: 25
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(148, 163, 184, 0.22)',
|
||||
circular: false
|
||||
},
|
||||
angleLines: {
|
||||
color: 'rgba(148, 163, 184, 0.18)'
|
||||
},
|
||||
pointLabels: {
|
||||
color: '#475569',
|
||||
padding: 8,
|
||||
font: {
|
||||
size: 11,
|
||||
weight: '700'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
function clampScore(value) {
|
||||
const score = Number(value || 0)
|
||||
if (!Number.isFinite(score)) {
|
||||
return 0
|
||||
}
|
||||
return Math.max(0, Math.min(props.max, score))
|
||||
}
|
||||
|
||||
function toRgba(color, alpha) {
|
||||
const normalized = String(color || '').trim()
|
||||
const hex = normalized.replace('#', '')
|
||||
|
||||
if (/^[\da-f]{3}$/i.test(hex)) {
|
||||
const [r, g, b] = hex.split('').map((part) => parseInt(part + part, 16))
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||
}
|
||||
|
||||
if (/^[\da-f]{6}$/i.test(hex)) {
|
||||
const r = parseInt(hex.slice(0, 2), 16)
|
||||
const g = parseInt(hex.slice(2, 4), 16)
|
||||
const b = parseInt(hex.slice(4, 6), 16)
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||
}
|
||||
|
||||
if (normalized.startsWith('rgb(')) {
|
||||
return normalized.replace('rgb(', 'rgba(').replace(')', `, ${alpha})`)
|
||||
}
|
||||
|
||||
return `rgba(58, 124, 165, ${alpha})`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.radar-chart {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 260px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user