feat: 指标

This commit is contained in:
zhanghao 2025-11-27 16:14:04 +08:00
parent 1347438e76
commit 0dbf3795d2
12 changed files with 1479 additions and 0 deletions

View File

@ -0,0 +1,101 @@
<!-- MetricCategory.vue - 一级指标组件 -->
<template>
<div class="metric-category">
<el-card class="category-card">
<div class="card-container">
<div class="category-info">
<div><el-tag type="primary">一级指标</el-tag></div>
<span class="category-name">{{ category.name }}</span>
</div>
<div class="category-actions">
<el-button size="small" @click="handleEdit" :icon="Edit" />
<el-button size="small" type="danger" @click="handleRemove" :icon="Delete" />
</div>
</div>
</el-card>
<!-- 编辑一级指标对话框 -->
<el-dialog v-model="showEditDialog" title="编辑一级指标" width="500px">
<MetricForm
ref="metricFormRef"
:metric="editingCategory"
@submit="handleUpdateCategory"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="showEditDialog = false">取消</el-button>
<el-button type="primary" @click="submitMetricForm">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import MetricForm from './MetricForm.vue'
import { Delete, Edit } from '@element-plus/icons-vue'
const props = defineProps({
category: {
type: Object,
required: true
}
})
const emit = defineEmits(['update-category', 'add-submetric', 'remove-category'])
const showEditDialog = ref(false)
const metricFormRef = ref(null)
const editingCategory = reactive({ ...props.category })
const handleEdit = () => {
Object.assign(editingCategory, props.category)
showEditDialog.value = true
}
const handleRemove = () => {
emit('remove-category', props.category.id)
}
const submitMetricForm = () => {
if (metricFormRef.value) {
metricFormRef.value.submitForm()
}
}
const handleUpdateCategory = (metricData) => {
emit('update-category', {
...props.category,
...metricData
})
showEditDialog.value = false
}
</script>
<style lang="scss" scoped>
.metric-category {
.category-card {
border-radius: 8px;
.card-container {
display: flex;
justify-content: space-between;
align-items: center;
.category-info {
display: flex;
align-items: center;
gap: 12px;
.category-name {
font-size: 14px;
color: #303133;
}
}
}
}
}
</style>

View File

@ -0,0 +1,49 @@
<!-- MetricForm.vue - 指标配置表单组件 -->
<template>
<el-form
ref="formRef"
:model="metric"
:rules="rules"
label-width="100px"
@submit.prevent
>
<el-form-item label="指标名称" prop="name">
<el-input v-model="metric.name" placeholder="请输入一级指标名称" />
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
metric: {
type: Object,
required: true
}
})
const emit = defineEmits(['submit'])
const formRef = ref(null)
const rules = reactive({
name: [
{ required: true, message: '请输入指标名称', trigger: 'blur' }
]
})
const submitForm = () => {
if (!formRef.value) return
formRef.value.validate((valid) => {
if (valid) {
emit('submit', { ...props.metric })
}
})
}
defineExpose({
submitForm
})
</script>

View File

@ -0,0 +1,287 @@
<template>
<div class="score-table-container">
<!-- 表头 -->
<div class="table-header">
<div class="table-header-item">数值1</div>
<div class="table-header-item">符号1</div>
<div class="table-header-item">得分指标</div>
<div class="table-header-item">符号2</div>
<div class="table-header-item">数值2</div>
<div class="table-header-item">分数</div>
<div class="table-header-item">权重</div>
<div class="table-header-item">操作</div>
</div>
<!-- 表格内容 -->
<div class="table-body">
<div v-for="(row, index) in tableData" :key="index" class="table-row">
<!-- 数值1 -->
<el-input v-model="row.value1" placeholder="请输入..." class="table-cell-input">
<template #append>ms</template>
</el-input>
<!-- 符号1 -->
<el-select v-model="row.symbol1" class="table-cell-select">
<el-option label="≤" value="le"></el-option>
<el-option label="<" value="lt"></el-option>
<el-option label="≥" value="ge"></el-option>
<el-option label=">" value="gt"></el-option>
</el-select>
<!-- 得分指标 -->
<el-input v-model="row.indicator" class="table-cell-input" :readonly="true" />
<!-- 符号2 -->
<el-select v-model="row.symbol2" class="table-cell-select">
<el-option label="≤" value="le"></el-option>
<el-option label="<" value="lt"></el-option>
<el-option label="≥" value="ge"></el-option>
<el-option label=">" value="gt"></el-option>
</el-select>
<!-- 数值2 -->
<el-input v-model="row.value2" class="table-cell-input">
<template #append>ms</template>
</el-input>
<!-- 分数 -->
<el-input v-model="row.score" class="table-cell-input" />
<!-- 权重 -->
<el-input v-model="row.weight" class="table-cell-input">
<template #append>%</template>
</el-input>
<!-- 操作按钮 -->
<div class="table-cell-action">
<el-button
icon="Edit"
size="small"
circle
@click="handleEdit(index)"
class="action-btn edit-btn"
/>
<!-- 最后一行显示添加按钮 -->
<el-button
v-if="index === tableData.length - 1"
icon="Plus"
size="small"
circle
@click="handleAdd"
class="action-btn add-btn"
/>
<el-button
icon="Delete"
size="small"
circle
@click="handleDelete(index)"
class="action-btn del-btn"
/>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
const props = defineProps({
title: {
type: String,
default: "",
},
});
//
const tableData = ref([
{
value1: "",
symbol1: "",
indicator: props.title,
symbol2: "le",
value2: "700",
score: "100",
weight: "50",
},
{
value1: "700",
symbol1: "lt",
indicator: props.title,
symbol2: "le",
value2: "1000",
score: "80",
weight: "50",
},
{
value1: "1000",
symbol1: "lt",
indicator: props.title,
symbol2: "le",
value2: "1300",
score: "60",
weight: "50",
},
{
value1: "",
symbol1: "",
indicator: props.title,
symbol2: "gt",
value2: "1300",
score: "0",
weight: "50",
},
]);
//
const handleEdit = (index) => {
console.log("编辑行:", index, tableData.value[index]);
};
//
const handleAdd = () => {
tableData.value.push({
value1: "",
symbol1: "",
indicator: props.title,
symbol2: "le",
value2: "",
score: "",
weight: "50",
});
};
//
const handleDelete = (index) => {
if (tableData.value.length <= 1) {
ElMessage.warning("至少保留一行数据");
return;
}
tableData.value.splice(index, 1);
};
</script>
<style lang="scss" scoped>
// scss
$table-border-color: #e5e7eb;
$table-header-bg: #f9fafb;
$table-row-hover-bg: #f5f7fa;
$gap-base: 0.5rem;
$padding-base: 1rem;
$text-sm: 0.875rem;
$font-medium: 500;
//
.score-table-container {
border: 1px solid $table-border-color;
border-radius: 4px;
overflow: hidden; //
//
.table-header {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: $gap-base;
padding: $gap-base $padding-base;
background-color: $table-header-bg;
border-bottom: 1px solid $table-border-color;
.table-header-item {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: $text-sm;
font-weight: $font-medium;
color: #333;
}
}
//
.table-body {
.table-row {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: $gap-base;
padding: $gap-base * 1.5 $padding-base;
border-bottom: 1px solid $table-border-color;
align-items: center;
// hover
&:hover {
background-color: $table-row-hover-bg;
transition: background-color 0.2s ease;
}
//
&:last-child {
border-bottom: none;
}
//
.table-cell-input {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
//
.table-cell-select {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
//
.table-cell-action {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
display: flex;
align-items: center;
gap: $gap-base;
.action-btn {
transition: all 0.2s ease;
&:hover {
transform: scale(1.05);
}
}
.edit-btn {
color: #409eff;
&:hover {
background-color: #ecf5ff;
}
}
.add-btn {
color: #67c23a;
&:hover {
background-color: #f0f9eb;
}
}
.del-btn {
color: #f56c6c;
&:hover {
background-color: #fef0f0;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<div class="evaluation-metrics">
<div class="left">
<Indicator />
</div>
<div class="right">
<div class="title">
<h2>指标配置</h2>
<el-button type="primary">
<el-icon><Plus /></el-icon>
添加二级指标
</el-button>
</div>
<div class="secondaryIndicator-container">
<SecondaryIndicator v-for="value in 3" />
</div>
</div>
</div>
</template>
<script setup>
import Indicator from './indicator.vue';
import SecondaryIndicator from './secondaryIndicator.vue';
</script>
<style lang="scss" scoped>
.evaluation-metrics {
width: 100%;
display: flex;
gap: 12px;
.left {
background-color: #fff;
padding: 20px;
width: 320px;
border: 1px solid #dbdbdb;
border-radius: 5px;
}
.right {
flex: 1;
height: 100%;
background-color: #fff;
border: 1px solid #dbdbdb;
border-radius: 5px;
overflow: hidden;
.title {
display: flex;
padding: 0 20px;
align-items: center;
justify-content: space-between;
}
.secondaryIndicator-container {
height: calc(100% - 72px);
overflow: auto;
padding: 0 20px;
}
}
}
</style>

View File

@ -0,0 +1,162 @@
<template>
<div class="container">
<div class="page-header">
<h2>评价指标</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddMetric">
<el-icon><Plus /></el-icon>
添加
</el-button>
</div>
</div>
<div class="metrics-container">
<MetricCategory
v-for="category in metricCategories"
:key="category.id"
:category="category"
@update-category="updateCategory"
@add-submetric="addSubmetric"
@remove-category="removeCategory"
/>
</div>
<!-- 添加一级指标对话框 -->
<el-dialog v-model="showAddDialog" title="添加一级指标" width="500px">
<MetricForm ref="metricFormRef" :metric="newMetric" @submit="handleAddCategory" />
<template #footer>
<span class="dialog-footer">
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="submitMetricForm">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { Plus } from "@element-plus/icons-vue";
import MetricCategory from "./components/MetricCategory.vue";
import MetricForm from "./components/MetricForm.vue";
//
const metricCategories = ref([
{
id: 1,
name: "语音唤醒",
weight: 30,
subMetrics: [
{
id: 11,
name: "指挥名称:无噪声唤醒",
weight: 30,
},
],
},
{
id: 2,
name: "语音识别",
weight: 20,
subMetrics: [],
},
{
id: 3,
name: "智能交互",
weight: 20,
subMetrics: [],
},
{
id: 4,
name: "数据分析",
weight: 10,
subMetrics: [],
},
{
id: 5,
name: "能智能力",
weight: 50,
subMetrics: [
{
id: 51,
name: "情绪分析:免唤醒",
weight: 50,
},
],
},
]);
const handleAddMetric = () => {
newMetric.name = "";
newMetric.weight = 0;
newMetric.subMetrics = [];
showAddDialog.value = true;
};
const updateCategory = (updatedCategory) => {
const index = metricCategories.value.findIndex((cat) => cat.id === updatedCategory.id);
if (index !== -1) {
metricCategories.value[index] = updatedCategory;
}
};
const addSubmetric = (categoryId, submetric) => {
const category = metricCategories.value.find((cat) => cat.id === categoryId);
if (category) {
category.subMetrics.push({
id: Date.now(),
...submetric,
});
}
};
const removeCategory = (categoryId) => {
const index = metricCategories.value.findIndex((cat) => cat.id === categoryId);
if (index !== -1) {
metricCategories.value.splice(index, 1);
}
};
const showAddDialog = ref(false);
const metricFormRef = ref(null);
const newMetric = reactive({
name: "",
weight: 0,
subMetrics: [],
});
const submitMetricForm = () => {
if (metricFormRef.value) {
metricFormRef.value.submitForm();
}
};
const handleAddCategory = (metricData) => {
const newCategory = {
id: Date.now(),
...metricData,
};
metricCategories.value.push(newCategory);
showAddDialog.value = false;
};
</script>
<style lang="scss" scoped>
.container {
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e4e7ed;
h2 {
margin: 0;
color: #303133;
}
}
.metrics-container {
display: flex;
flex-direction: column;
gap: 16px;
}
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<div class="container">
<div class="indicator-container">
<div class="title-container">
<div class="title-left">
<el-tag>二级指标</el-tag>
<div class="title">指标名称:<span class="text">无噪声唤醒</span></div>
</div>
<div>指标权重:30.00%</div>
<div>
<el-button size="small" :icon="Edit" />
<el-button size="small" type="danger" :icon="Delete" />
</div>
</div>
<div>
<el-tabs v-model="activeTab">
<el-tab-pane
v-for="item in tabList"
:key="item.name"
:label="item.title"
:name="item.name"
>
<ScoreTable :title="item.title" />
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script setup>
import { Delete, Edit } from "@element-plus/icons-vue";
import ScoreTable from "./components/ScoreTable.vue";
const activeTab = ref('1');
const tabList = ref([
{
title: "唤醒时间",
name: "1",
},
{
title: "唤醒成功率",
name: "2",
},
]);
</script>
<style lang="scss" scoped>
.container {
margin: 20px 0;
.indicator-container {
border: 1px solid #dbdbdb;
border-radius: 5px;
padding: 20px;
.title-container {
display: flex;
align-items: center;
justify-content: space-between;
.title-left {
display: flex;
gap: 20px;
.title {
margin-left: 8px;
.text {
font-weight: bold;
margin-left: 8px;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,101 @@
<!-- MetricCategory.vue - 一级指标组件 -->
<template>
<div class="metric-category">
<el-card class="category-card">
<div class="card-container">
<div class="category-info">
<div><el-tag type="primary">一级指标</el-tag></div>
<span class="category-name">{{ category.name }}</span>
</div>
<div class="category-actions">
<el-button size="small" @click="handleEdit" :icon="Edit" />
<el-button size="small" type="danger" @click="handleRemove" :icon="Delete" />
</div>
</div>
</el-card>
<!-- 编辑一级指标对话框 -->
<el-dialog v-model="showEditDialog" title="编辑一级指标" width="500px">
<MetricForm
ref="metricFormRef"
:metric="editingCategory"
@submit="handleUpdateCategory"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="showEditDialog = false">取消</el-button>
<el-button type="primary" @click="submitMetricForm">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import MetricForm from './MetricForm.vue'
import { Delete, Edit } from '@element-plus/icons-vue'
const props = defineProps({
category: {
type: Object,
required: true
}
})
const emit = defineEmits(['update-category', 'add-submetric', 'remove-category'])
const showEditDialog = ref(false)
const metricFormRef = ref(null)
const editingCategory = reactive({ ...props.category })
const handleEdit = () => {
Object.assign(editingCategory, props.category)
showEditDialog.value = true
}
const handleRemove = () => {
emit('remove-category', props.category.id)
}
const submitMetricForm = () => {
if (metricFormRef.value) {
metricFormRef.value.submitForm()
}
}
const handleUpdateCategory = (metricData) => {
emit('update-category', {
...props.category,
...metricData
})
showEditDialog.value = false
}
</script>
<style lang="scss" scoped>
.metric-category {
.category-card {
border-radius: 8px;
.card-container {
display: flex;
justify-content: space-between;
align-items: center;
.category-info {
display: flex;
align-items: center;
gap: 12px;
.category-name {
font-size: 14px;
color: #303133;
}
}
}
}
}
</style>

View File

@ -0,0 +1,49 @@
<!-- MetricForm.vue - 指标配置表单组件 -->
<template>
<el-form
ref="formRef"
:model="metric"
:rules="rules"
label-width="100px"
@submit.prevent
>
<el-form-item label="指标名称" prop="name">
<el-input v-model="metric.name" placeholder="请输入一级指标名称" />
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
metric: {
type: Object,
required: true
}
})
const emit = defineEmits(['submit'])
const formRef = ref(null)
const rules = reactive({
name: [
{ required: true, message: '请输入指标名称', trigger: 'blur' }
]
})
const submitForm = () => {
if (!formRef.value) return
formRef.value.validate((valid) => {
if (valid) {
emit('submit', { ...props.metric })
}
})
}
defineExpose({
submitForm
})
</script>

View File

@ -0,0 +1,284 @@
<template>
<div class="score-table-container">
<!-- 表头 -->
<div class="table-header">
<div class="table-header-item">数值1</div>
<div class="table-header-item">符号1</div>
<div class="table-header-item">得分指标</div>
<div class="table-header-item">符号2</div>
<div class="table-header-item">数值2</div>
<div class="table-header-item">分数</div>
<div class="table-header-item">权重</div>
<div class="table-header-item">操作</div>
</div>
<!-- 表格内容 -->
<div class="table-body">
<div v-for="(row, index) in tableData" :key="index" class="table-row">
<!-- 数值1 -->
<el-input v-model="row.value1" placeholder="请输入..." class="table-cell-input">
<template #append>ms</template>
</el-input>
<!-- 符号1 -->
<el-select v-model="row.symbol1" class="table-cell-select">
<el-option label="≤" value="le"></el-option>
<el-option label="<" value="lt"></el-option>
<el-option label="≥" value="ge"></el-option>
<el-option label=">" value="gt"></el-option>
</el-select>
<!-- 得分指标 -->
<el-input v-model="row.indicator" class="table-cell-input" :readonly="true" />
<!-- 符号2 -->
<el-select v-model="row.symbol2" class="table-cell-select">
<el-option label="≤" value="le"></el-option>
<el-option label="<" value="lt"></el-option>
<el-option label="≥" value="ge"></el-option>
<el-option label=">" value="gt"></el-option>
</el-select>
<!-- 数值2 -->
<el-input v-model="row.value2" class="table-cell-input">
<template #append>ms</template>
</el-input>
<!-- 分数 -->
<el-input v-model="row.score" class="table-cell-input" />
<!-- 权重 -->
<el-input v-model="row.weight" class="table-cell-input">
<template #append>%</template>
</el-input>
<!-- 操作按钮 -->
<div class="table-cell-action">
<el-button
icon="Edit"
size="small"
circle
@click="handleEdit(index)"
class="action-btn edit-btn"
/>
<!-- 最后一行显示添加按钮 -->
<el-button
v-if="index === tableData.length - 1"
icon="Plus"
size="small"
circle
@click="handleAdd"
class="action-btn add-btn"
/>
<el-button
icon="Delete"
size="small"
circle
@click="handleDelete(index)"
class="action-btn del-btn"
/>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
const props = defineProps({
title: {
type: String,
default: "",
},
});
//
const tableData = ref([
{
value1: "",
symbol1: "",
indicator: props.title,
symbol2: "le",
value2: "700",
score: "100",
weight: "50",
},
{
value1: "700",
symbol1: "lt",
indicator: props.title,
symbol2: "le",
value2: "1000",
score: "80",
weight: "50",
},
{
value1: "1000",
symbol1: "lt",
indicator: props.title,
symbol2: "le",
value2: "1300",
score: "60",
weight: "50",
},
{
value1: "",
symbol1: "",
indicator: props.title,
symbol2: "gt",
value2: "1300",
score: "0",
weight: "50",
},
]);
//
const handleEdit = (index) => {
console.log("编辑行:", index, tableData.value[index]);
};
//
const handleAdd = () => {
tableData.value.push({
value1: "",
symbol1: "",
indicator: props.title,
symbol2: "le",
value2: "",
score: "",
weight: "50",
});
};
//
const handleDelete = (index) => {
if (tableData.value.length <= 1) {
ElMessage.warning("至少保留一行数据");
return;
}
tableData.value.splice(index, 1);
};
</script>
<style lang="scss" scoped>
// scss
$table-border-color: #e5e7eb;
$table-header-bg: #f9fafb;
$table-row-hover-bg: #f5f7fa;
$gap-base: 0.5rem;
$padding-base: 1rem;
$text-sm: 0.875rem;
$font-medium: 500;
//
.score-table-container {
border: 1px solid $table-border-color;
border-radius: 4px;
overflow: hidden; //
//
.table-header {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: $gap-base;
padding: $gap-base $padding-base;
background-color: $table-header-bg;
border-bottom: 1px solid $table-border-color;
.table-header-item {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: $text-sm;
font-weight: $font-medium;
color: #333;
}
}
//
.table-body {
.table-row {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: $gap-base;
padding: $gap-base * 1.5 $padding-base;
border-bottom: 1px solid $table-border-color;
align-items: center;
// hover
&:hover {
background-color: $table-row-hover-bg;
transition: background-color 0.2s ease;
}
//
&:last-child {
border-bottom: none;
}
//
.table-cell-input {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
//
.table-cell-select {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
//
.table-cell-action {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
display: flex;
align-items: center;
gap: $gap-base;
.action-btn {
transition: all 0.2s ease;
&:hover {
transform: scale(1.05);
}
}
.edit-btn {
color: #409eff;
&:hover {
background-color: #ecf5ff;
}
}
.add-btn {
color: #67c23a;
&:hover {
background-color: #f0f9eb;
}
}
.del-btn {
color: #f56c6c;
&:hover {
background-color: #fef0f0;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<div class="evaluation-metrics">
<div class="left">
<Indicator />
</div>
<div class="right">
<div class="title">
<h2>指标配置</h2>
<el-button type="primary">
<el-icon><Plus /></el-icon>
添加二级指标
</el-button>
</div>
<div class="secondaryIndicator-container">
<SecondaryIndicator v-for="value in 3" />
</div>
</div>
</div>
</template>
<script setup>
import Indicator from './indicator.vue';
import SecondaryIndicator from './secondaryIndicator.vue';
</script>
<style lang="scss" scoped>
.evaluation-metrics {
width: 100%;
display: flex;
gap: 12px;
.left {
background-color: #fff;
padding: 20px;
width: 320px;
border: 1px solid #dbdbdb;
border-radius: 5px;
}
.right {
flex: 1;
height: 100%;
background-color: #fff;
border: 1px solid #dbdbdb;
border-radius: 5px;
overflow: hidden;
.title {
display: flex;
padding: 0 20px;
align-items: center;
justify-content: space-between;
}
.secondaryIndicator-container {
height: calc(100% - 72px);
overflow: auto;
padding: 0 20px;
}
}
}
</style>

View File

@ -0,0 +1,162 @@
<template>
<div class="container">
<div class="page-header">
<h2>评价指标</h2>
<div class="header-actions">
<el-button type="primary" @click="handleAddMetric">
<el-icon><Plus /></el-icon>
添加
</el-button>
</div>
</div>
<div class="metrics-container">
<MetricCategory
v-for="category in metricCategories"
:key="category.id"
:category="category"
@update-category="updateCategory"
@add-submetric="addSubmetric"
@remove-category="removeCategory"
/>
</div>
<!-- 添加一级指标对话框 -->
<el-dialog v-model="showAddDialog" title="添加一级指标" width="500px">
<MetricForm ref="metricFormRef" :metric="newMetric" @submit="handleAddCategory" />
<template #footer>
<span class="dialog-footer">
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="submitMetricForm">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { Plus } from "@element-plus/icons-vue";
import MetricCategory from "./components/MetricCategory.vue";
import MetricForm from "./components/MetricForm.vue";
//
const metricCategories = ref([
{
id: 1,
name: "语音唤醒",
weight: 30,
subMetrics: [
{
id: 11,
name: "指挥名称:无噪声唤醒",
weight: 30,
},
],
},
{
id: 2,
name: "语音识别",
weight: 20,
subMetrics: [],
},
{
id: 3,
name: "智能交互",
weight: 20,
subMetrics: [],
},
{
id: 4,
name: "数据分析",
weight: 10,
subMetrics: [],
},
{
id: 5,
name: "能智能力",
weight: 50,
subMetrics: [
{
id: 51,
name: "情绪分析:免唤醒",
weight: 50,
},
],
},
]);
const handleAddMetric = () => {
newMetric.name = "";
newMetric.weight = 0;
newMetric.subMetrics = [];
showAddDialog.value = true;
};
const updateCategory = (updatedCategory) => {
const index = metricCategories.value.findIndex((cat) => cat.id === updatedCategory.id);
if (index !== -1) {
metricCategories.value[index] = updatedCategory;
}
};
const addSubmetric = (categoryId, submetric) => {
const category = metricCategories.value.find((cat) => cat.id === categoryId);
if (category) {
category.subMetrics.push({
id: Date.now(),
...submetric,
});
}
};
const removeCategory = (categoryId) => {
const index = metricCategories.value.findIndex((cat) => cat.id === categoryId);
if (index !== -1) {
metricCategories.value.splice(index, 1);
}
};
const showAddDialog = ref(false);
const metricFormRef = ref(null);
const newMetric = reactive({
name: "",
weight: 0,
subMetrics: [],
});
const submitMetricForm = () => {
if (metricFormRef.value) {
metricFormRef.value.submitForm();
}
};
const handleAddCategory = (metricData) => {
const newCategory = {
id: Date.now(),
...metricData,
};
metricCategories.value.push(newCategory);
showAddDialog.value = false;
};
</script>
<style lang="scss" scoped>
.container {
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e4e7ed;
h2 {
margin: 0;
color: #303133;
}
}
.metrics-container {
display: flex;
flex-direction: column;
gap: 16px;
}
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<div class="container">
<div class="indicator-container">
<div class="title-container">
<div class="title-left">
<el-tag>二级指标</el-tag>
<div class="title">指标名称:<span class="text">无噪声唤醒</span></div>
</div>
<div>指标权重:30.00%</div>
<div>
<el-button size="small" :icon="Edit" />
<el-button size="small" type="danger" :icon="Delete" />
</div>
</div>
<div>
<el-tabs v-model="activeTab">
<el-tab-pane
v-for="item in tabList"
:key="item.name"
:label="item.title"
:name="item.name"
>
<ScoreTable :title="item.title" />
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script setup>
import { Delete, Edit } from "@element-plus/icons-vue";
import ScoreTable from "./components/ScoreTable.vue";
const activeTab = ref('1');
const tabList = ref([
{
title: "唤醒时间",
name: "1",
},
{
title: "唤醒成功率",
name: "2",
},
]);
</script>
<style lang="scss" scoped>
.container {
margin: 20px 0;
.indicator-container {
border: 1px solid #dbdbdb;
border-radius: 5px;
padding: 20px;
.title-container {
display: flex;
align-items: center;
justify-content: space-between;
.title-left {
display: flex;
gap: 20px;
.title {
margin-left: 8px;
.text {
font-weight: bold;
margin-left: 8px;
}
}
}
}
}
}
</style>