feat: 指标
This commit is contained in:
parent
1347438e76
commit
0dbf3795d2
101
src/views/evaluate/il/speech/components/MetricCategory.vue
Normal file
101
src/views/evaluate/il/speech/components/MetricCategory.vue
Normal 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>
|
||||
49
src/views/evaluate/il/speech/components/MetricForm.vue
Normal file
49
src/views/evaluate/il/speech/components/MetricForm.vue
Normal 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>
|
||||
287
src/views/evaluate/il/speech/components/ScoreTable.vue
Normal file
287
src/views/evaluate/il/speech/components/ScoreTable.vue
Normal 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>
|
||||
65
src/views/evaluate/il/speech/index.vue
Normal file
65
src/views/evaluate/il/speech/index.vue
Normal 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>
|
||||
162
src/views/evaluate/il/speech/indicator.vue
Normal file
162
src/views/evaluate/il/speech/indicator.vue
Normal 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>
|
||||
77
src/views/evaluate/il/speech/secondaryIndicator.vue
Normal file
77
src/views/evaluate/il/speech/secondaryIndicator.vue
Normal 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>
|
||||
101
src/views/evaluate/il/touch/components/MetricCategory.vue
Normal file
101
src/views/evaluate/il/touch/components/MetricCategory.vue
Normal 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>
|
||||
49
src/views/evaluate/il/touch/components/MetricForm.vue
Normal file
49
src/views/evaluate/il/touch/components/MetricForm.vue
Normal 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>
|
||||
284
src/views/evaluate/il/touch/components/ScoreTable.vue
Normal file
284
src/views/evaluate/il/touch/components/ScoreTable.vue
Normal 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>
|
||||
65
src/views/evaluate/il/touch/index.vue
Normal file
65
src/views/evaluate/il/touch/index.vue
Normal 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>
|
||||
162
src/views/evaluate/il/touch/indicator.vue
Normal file
162
src/views/evaluate/il/touch/indicator.vue
Normal 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>
|
||||
77
src/views/evaluate/il/touch/secondaryIndicator.vue
Normal file
77
src/views/evaluate/il/touch/secondaryIndicator.vue
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user