| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- <template>
- <div class="app-container score-dimension-page">
- <el-row :gutter="16" style="height:100%">
- <!-- 左侧:维度列表 -->
- <el-col :span="7">
- <el-card class="dim-card" shadow="never">
- <template #header>
- <div class="card-header">
- <span>评分维度(6个)</span>
- <el-button type="primary" size="small" icon="Plus" @click="handleAddDim" v-hasPermi="['score:dimension:add']">新增</el-button>
- </div>
- </template>
- <div v-loading="dimLoading">
- <div v-for="dim in dimList" :key="dim.id"
- :class="['dim-item', { active: selectedDim && selectedDim.id === dim.id }]"
- @click="selectDimension(dim)">
- <div class="dim-name">
- <el-tag :type="dim.status === '0' ? '' : 'info'" size="small" style="margin-right:6px">{{ dim.name }}</el-tag>
- <span class="dim-weight">{{ dim.weight }}%</span>
- </div>
- <div class="dim-actions">
- <el-button link size="small" icon="Edit" @click.stop="handleEditDim(dim)" v-hasPermi="['score:dimension:edit']" />
- <el-button link size="small" icon="Delete" type="danger" @click.stop="handleDeleteDim(dim)" v-hasPermi="['score:dimension:remove']" />
- </div>
- </div>
- </div>
- <div class="weight-total">
- 权重合计:<strong :style="{ color: totalWeight === 100 ? '#67c23a' : '#f56c6c' }">{{ totalWeight }}%</strong>
- </div>
- </el-card>
- </el-col>
- <!-- 右侧:指标树 -->
- <el-col :span="17">
- <el-card shadow="never">
- <template #header>
- <div class="card-header">
- <span>{{ selectedDim ? selectedDim.name + ' — 指标树' : '请选择左侧维度' }}</span>
- <div v-if="selectedDim">
- <el-button size="small" icon="Plus" type="primary" @click="handleAddIndicator(null)" v-hasPermi="['score:indicator:add']">新增二级指标</el-button>
- <el-button size="small" icon="Refresh" @click="loadTree" />
- </div>
- </div>
- </template>
- <el-table v-loading="treeLoading" :data="indicatorTree" row-key="id"
- :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
- border default-expand-all>
- <el-table-column label="指标名称" prop="name" min-width="220" />
- <el-table-column label="层级" align="center" width="70">
- <template #default="{ row }">
- <el-tag size="small" :type="row.level === 2 ? 'primary' : row.level === 3 ? 'success' : 'warning'">
- {{ row.level }}级
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="类型" align="center" width="80">
- <template #default="{ row }">
- <el-tag v-if="row.type" size="small" :type="row.type === '1' ? 'success' : row.type === '2' ? 'danger' : 'info'">
- {{ row.type === '1' ? '加分' : row.type === '2' ? '扣分' : '记录' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="分值" align="center" width="90">
- <template #default="{ row }">
- <span v-if="row.scoreValue != null"
- :style="{ color: row.scoreValue > 0 ? '#67c23a' : row.scoreValue < 0 ? '#f56c6c' : '' }">
- {{ row.scoreValue > 0 ? '+' : '' }}{{ row.scoreValue }}
- </span>
- </template>
- </el-table-column>
- <el-table-column label="叠加规则" prop="cascadeRule" min-width="180" show-overflow-tooltip />
- <el-table-column label="状态" align="center" width="80">
- <template #default="{ row }">
- <el-switch v-model="row.status" active-value="0" inactive-value="1"
- @change="handleStatusChange(row)" v-hasPermi="['score:indicator:edit']" />
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" width="160">
- <template #default="{ row }">
- <el-button v-if="row.level < 4" link type="primary" size="small" icon="Plus"
- @click="handleAddIndicator(row)" v-hasPermi="['score:indicator:add']">子级</el-button>
- <el-button link type="primary" size="small" icon="Edit"
- @click="handleEditIndicator(row)" v-hasPermi="['score:indicator:edit']">修改</el-button>
- <el-button link type="danger" size="small" icon="Delete"
- @click="handleDeleteIndicator(row)" v-hasPermi="['score:indicator:remove']">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- </el-card>
- </el-col>
- </el-row>
- <!-- 维度对话框 -->
- <el-dialog :title="dimDialogTitle" v-model="dimDialogVisible" width="480px" append-to-body>
- <el-form ref="dimFormRef" :model="dimForm" :rules="dimRules" label-width="90px">
- <el-form-item label="维度名称" prop="name">
- <el-input v-model="dimForm.name" placeholder="请输入维度名称" />
- </el-form-item>
- <el-form-item label="权重(%)" prop="weight">
- <el-input-number v-model="dimForm.weight" :precision="2" :min="0" :max="100" style="width:100%" />
- </el-form-item>
- <el-form-item label="基础分" prop="baseScore">
- <el-input-number v-model="dimForm.baseScore" :precision="2" :min="0" style="width:100%" />
- </el-form-item>
- <el-form-item label="排序" prop="sortOrder">
- <el-input-number v-model="dimForm.sortOrder" :min="0" style="width:100%" />
- </el-form-item>
- <el-form-item label="状态">
- <el-radio-group v-model="dimForm.status">
- <el-radio value="0">正常</el-radio>
- <el-radio value="1">停用</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="dimForm.remark" type="textarea" :rows="2" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dimDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitDimForm">确定</el-button>
- </template>
- </el-dialog>
- <!-- 指标对话框 -->
- <el-dialog :title="indDialogTitle" v-model="indDialogVisible" width="540px" append-to-body>
- <el-form ref="indFormRef" :model="indForm" :rules="indRules" label-width="100px">
- <el-form-item label="所属维度">
- <el-input :value="selectedDim ? selectedDim.name : ''" disabled />
- </el-form-item>
- <el-form-item label="父级指标" v-if="indForm.parentId && indForm.parentId !== 0">
- <el-input :value="parentIndicatorName" disabled />
- </el-form-item>
- <el-form-item label="层级">
- <el-tag>{{ indForm.level }}级</el-tag>
- </el-form-item>
- <el-form-item label="指标名称" prop="name">
- <el-input v-model="indForm.name" placeholder="请输入指标名称" />
- </el-form-item>
- <el-form-item label="类型" prop="type">
- <el-radio-group v-model="indForm.type">
- <el-radio value="1">加分</el-radio>
- <el-radio value="2">扣分</el-radio>
- <el-radio value="3">纯记录</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="分值" prop="scoreValue">
- <el-input-number v-model="indForm.scoreValue" :precision="2" style="width:100%"
- placeholder="正数=加分,负数=扣分" />
- </el-form-item>
- <el-form-item label="叠加规则">
- <el-input v-model="indForm.cascadeRule" type="textarea" :rows="2"
- placeholder="如:返航/二次清舱-10、航班延误-8..." />
- </el-form-item>
- <el-form-item label="排序">
- <el-input-number v-model="indForm.sortOrder" :min="0" style="width:100%" />
- </el-form-item>
- <el-form-item label="状态">
- <el-radio-group v-model="indForm.status">
- <el-radio value="0">正常</el-radio>
- <el-radio value="1">停用</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="indForm.remark" type="textarea" :rows="2" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="indDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitIndForm">确定</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { ref, reactive, computed, onMounted } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import {
- listDimension, addDimension, updateDimension, delDimension,
- treeIndicator, addIndicator, updateIndicator, delIndicator
- } from '@/api/score/index'
- defineOptions({ name: 'ScoreDimension' })
- // ===== 维度 =====
- const dimLoading = ref(false)
- const dimList = ref([])
- const selectedDim = ref(null)
- const dimDialogVisible = ref(false)
- const dimDialogTitle = ref('')
- const dimFormRef = ref(null)
- const dimForm = reactive({ id: null, name: '', weight: 0, baseScore: 80, sortOrder: 0, status: '0', remark: '' })
- const dimRules = {
- name: [{ required: true, message: '请输入维度名称', trigger: 'blur' }],
- weight: [{ required: true, message: '请输入权重', trigger: 'blur' }]
- }
- const totalWeight = computed(() => {
- return dimList.value.reduce((sum, d) => sum + (Number(d.weight) || 0), 0)
- })
- async function loadDimensions() {
- dimLoading.value = true
- const r = await listDimension({ pageNum: 1, pageSize: 100 }).finally(() => dimLoading.value = false)
- dimList.value = r.rows || []
- if (!selectedDim.value && dimList.value.length) selectDimension(dimList.value[0])
- }
- function selectDimension(dim) {
- selectedDim.value = dim
- loadTree()
- }
- function handleAddDim() {
- Object.assign(dimForm, { id: null, name: '', weight: 0, baseScore: 80, sortOrder: dimList.value.length + 1, status: '0', remark: '' })
- dimDialogTitle.value = '新增维度'
- dimDialogVisible.value = true
- }
- function handleEditDim(dim) {
- Object.assign(dimForm, dim)
- dimDialogTitle.value = '修改维度'
- dimDialogVisible.value = true
- }
- async function submitDimForm() {
- await dimFormRef.value.validate()
- if (dimForm.id) { await updateDimension(dimForm); ElMessage.success('修改成功') }
- else { await addDimension(dimForm); ElMessage.success('新增成功') }
- dimDialogVisible.value = false
- loadDimensions()
- }
- function handleDeleteDim(dim) {
- ElMessageBox.confirm(`确认删除维度「${dim.name}」?删除后其下所有指标也将不可用。`, '警告', { type: 'warning' }).then(() => {
- delDimension(dim.id).then(() => { ElMessage.success('删除成功'); loadDimensions() })
- })
- }
- // ===== 指标树 =====
- const treeLoading = ref(false)
- const indicatorTree = ref([])
- const indDialogVisible = ref(false)
- const indDialogTitle = ref('')
- const indFormRef = ref(null)
- const indForm = reactive({ id: null, dimensionId: null, parentId: 0, level: 2, name: '', type: '2', scoreValue: 0, cascadeRule: '', sortOrder: 0, status: '0', remark: '' })
- const indRules = { name: [{ required: true, message: '请输入指标名称', trigger: 'blur' }] }
- const parentIndicatorName = ref('')
- async function loadTree() {
- if (!selectedDim.value) return
- treeLoading.value = true
- const r = await treeIndicator(selectedDim.value.id).finally(() => treeLoading.value = false)
- indicatorTree.value = r.data || []
- }
- function handleAddIndicator(parentRow) {
- Object.assign(indForm, { id: null, dimensionId: selectedDim.value.id, parentId: parentRow ? parentRow.id : 0, level: parentRow ? parentRow.level + 1 : 2, name: '', type: '2', scoreValue: 0, cascadeRule: '', sortOrder: 0, status: '0', remark: '' })
- parentIndicatorName.value = parentRow ? parentRow.name : ''
- indDialogTitle.value = '新增' + (parentRow ? parentRow.level + 1 : 2) + '级指标'
- indDialogVisible.value = true
- }
- function handleEditIndicator(row) {
- Object.assign(indForm, row)
- parentIndicatorName.value = ''
- indDialogTitle.value = '修改指标'
- indDialogVisible.value = true
- }
- async function submitIndForm() {
- await indFormRef.value.validate()
- if (indForm.id) { await updateIndicator(indForm); ElMessage.success('修改成功') }
- else { await addIndicator(indForm); ElMessage.success('新增成功') }
- indDialogVisible.value = false
- loadTree()
- }
- async function handleStatusChange(row) {
- await updateIndicator({ id: row.id, status: row.status })
- ElMessage.success(row.status === '0' ? '已启用' : '已停用')
- }
- function handleDeleteIndicator(row) {
- ElMessageBox.confirm(`确认删除指标「${row.name}」?如有子指标也将一并删除。`, '警告', { type: 'warning' }).then(() => {
- delIndicator(row.id).then(() => { ElMessage.success('删除成功'); loadTree() })
- })
- }
- onMounted(loadDimensions)
- </script>
- <style scoped>
- .score-dimension-page { height: calc(100vh - 130px); }
- .dim-card { height: 100%; }
- .card-header { display: flex; justify-content: space-between; align-items: center; }
- .dim-item {
- display: flex; justify-content: space-between; align-items: center;
- padding: 10px 12px; border-radius: 6px; cursor: pointer;
- margin-bottom: 6px; border: 1px solid #ebeef5; transition: all .2s;
- }
- .dim-item:hover { border-color: #409eff; background: #f0f7ff; }
- .dim-item.active { border-color: #409eff; background: #ecf5ff; }
- .dim-name { display: flex; align-items: center; }
- .dim-weight { font-size: 12px; color: #909399; }
- .dim-actions { display: flex; gap: 4px; }
- .weight-total { text-align: right; margin-top: 12px; font-size: 13px; color: #606266; }
- </style>
|