|
|
@@ -0,0 +1,241 @@
|
|
|
+<template>
|
|
|
+ <div class="indicator-mappings admin-page">
|
|
|
+ <div class="header">
|
|
|
+ <h2>指标映射管理</h2>
|
|
|
+ <div class="filters">
|
|
|
+ <el-select v-model="statusFilter" placeholder="状态" clearable style="width: 120px;" @change="listMappings">
|
|
|
+ <el-option label="生效" :value="1" />
|
|
|
+ <el-option label="待确认" :value="0" />
|
|
|
+ </el-select>
|
|
|
+ <el-input v-model="sourceTypeFilter" placeholder="来源" style="width: 160px;" clearable @keyup.enter.native="listMappings" @clear="listMappings" />
|
|
|
+ <el-button type="primary" icon="el-icon-search" @click="listMappings">查询</el-button>
|
|
|
+ <el-button type="success" icon="el-icon-upload2" @click="openImport">批量导入</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="adm-table-wrap">
|
|
|
+ <el-table height="100%" :data="list" v-loading="loading" border stripe>
|
|
|
+ <el-table-column prop="id" label="ID" width="60" />
|
|
|
+ <el-table-column prop="sourceType" label="来源" width="100" />
|
|
|
+ <el-table-column prop="rawName" label="原始名称" min-width="130" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="aliasText" label="别名" min-width="130" show-overflow-tooltip />
|
|
|
+ <el-table-column label="关联指标" min-width="150" show-overflow-tooltip>
|
|
|
+ <template slot-scope="{ row }">{{ defName(row.definitionId) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="匹配方式" width="110">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag v-if="row.matchType === 'ai_fallback'" type="warning" size="mini">AI 建议</el-tag>
|
|
|
+ <span v-else>{{ row.matchType || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="priority" label="优先级" width="80" />
|
|
|
+ <el-table-column label="状态" width="90">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="row.status === 1 ? 'success' : 'warning'" size="mini">{{ row.status === 1 ? '生效' : '待确认' }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="创建时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button v-if="row.status === 0" size="mini" type="primary" @click="openConfirm(row)">确认映射</el-button>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-pagination
|
|
|
+ @size-change="onSizeChange"
|
|
|
+ @current-change="onPageChange"
|
|
|
+ :current-page="page"
|
|
|
+ :page-size="size"
|
|
|
+ :total="total"
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ style="margin-top: 20px; text-align: right;"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-dialog title="批量导入映射种子" :visible.sync="importDialogVisible" width="600px">
|
|
|
+ <el-form :model="importForm" label-width="80px" size="small">
|
|
|
+ <el-form-item label="来源">
|
|
|
+ <el-input v-model="importForm.sourceType" placeholder="来源类型,如 gut" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="数据">
|
|
|
+ <el-input v-model="importForm.text" type="textarea" :rows="6" placeholder='支持 JSON 数组如 [{"rawName":"乳酸菌","definitionId":3}] 或 CSV 两列如 乳酸菌,3' />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <span slot="footer">
|
|
|
+ <el-button @click="importDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitImport" :loading="importing">导入</el-button>
|
|
|
+ </span>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog title="确认映射" :visible.sync="confirmDialogVisible" width="500px">
|
|
|
+ <el-form label-width="100px" size="small">
|
|
|
+ <el-form-item label="原始名称">
|
|
|
+ <el-input :value="confirmRow && confirmRow.rawName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="AI 建议">
|
|
|
+ <el-input :value="aiSuggestionText" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="目标定义">
|
|
|
+ <el-select v-model="confirmDefId" filterable clearable :filter-method="filterConfirmDefs" placeholder="搜索并选择目标定义" style="width:100%;">
|
|
|
+ <el-option v-for="d in defOptions" :key="d.id" :label="d.name + '(' + d.code + ')'" :value="d.id" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <span slot="footer">
|
|
|
+ <el-button @click="confirmDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitConfirm" :loading="confirming">确认</el-button>
|
|
|
+ </span>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import SortMixin from '@/mixins/SortMixin'
|
|
|
+import { listIndicatorDefinitions, listIndicatorMappings, importIndicatorMappings } from '@/api/indicator'
|
|
|
+import { parseSeedsText } from '@/utils/indicatorSeeds'
|
|
|
+
|
|
|
+export default {
|
|
|
+ mixins: [SortMixin],
|
|
|
+ name: 'IndicatorMappings',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ list: [],
|
|
|
+ total: 0,
|
|
|
+ page: 1,
|
|
|
+ size: 15,
|
|
|
+ loading: false,
|
|
|
+ statusFilter: null,
|
|
|
+ sourceTypeFilter: '',
|
|
|
+ importDialogVisible: false,
|
|
|
+ importing: false,
|
|
|
+ importForm: { sourceType: '', text: '' },
|
|
|
+ confirmDialogVisible: false,
|
|
|
+ confirming: false,
|
|
|
+ confirmRow: null,
|
|
|
+ confirmDefId: null,
|
|
|
+ definitions: [],
|
|
|
+ defMap: {},
|
|
|
+ defOptions: [],
|
|
|
+ sortableColumns: { id: 'ID', rawName: '原始名称', createdAt: '创建时间' }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.loadDefinitions()
|
|
|
+ this.listMappings()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async loadDefinitions() {
|
|
|
+ try {
|
|
|
+ const res = await listIndicatorDefinitions({ status: 1 })
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.definitions = res.data || []
|
|
|
+ const map = {}
|
|
|
+ this.definitions.forEach(d => { map[d.id] = d.name })
|
|
|
+ this.defMap = map
|
|
|
+ }
|
|
|
+ } catch (e) { console.error(e) }
|
|
|
+ },
|
|
|
+ async listMappings() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ const params = { page: this.page, size: this.size }
|
|
|
+ if (this.statusFilter !== null && this.statusFilter !== '') params.status = this.statusFilter
|
|
|
+ if (this.sourceTypeFilter) params.sourceType = this.sourceTypeFilter
|
|
|
+ const res = await listIndicatorMappings(params)
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.list = res.data.records || []
|
|
|
+ this.total = res.data.total || 0
|
|
|
+ }
|
|
|
+ } catch (e) { console.error(e) }
|
|
|
+ this.loading = false
|
|
|
+ },
|
|
|
+ onSizeChange(val) {
|
|
|
+ this.size = val
|
|
|
+ this.page = 1
|
|
|
+ this.listMappings()
|
|
|
+ },
|
|
|
+ onPageChange(p) {
|
|
|
+ this.page = typeof p === 'number' ? p : 1
|
|
|
+ this.listMappings()
|
|
|
+ },
|
|
|
+ openImport() {
|
|
|
+ this.importForm = { sourceType: '', text: '' }
|
|
|
+ this.importDialogVisible = true
|
|
|
+ },
|
|
|
+ async submitImport() {
|
|
|
+ if (!this.importForm.sourceType.trim()) {
|
|
|
+ this.$message.error('请填写来源')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const seeds = parseSeedsText(this.importForm.text)
|
|
|
+ if (!seeds.length) {
|
|
|
+ this.$message.error('未解析到有效条目')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.importing = true
|
|
|
+ try {
|
|
|
+ const res = await importIndicatorMappings({ sourceType: this.importForm.sourceType.trim(), seeds })
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.$message.success(res.message || '导入完成')
|
|
|
+ this.importDialogVisible = false
|
|
|
+ this.listMappings()
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.message || '导入失败')
|
|
|
+ }
|
|
|
+ } catch (e) { this.$message.error('导入失败') }
|
|
|
+ this.importing = false
|
|
|
+ },
|
|
|
+ openConfirm(row) {
|
|
|
+ this.confirmRow = row
|
|
|
+ this.confirmDefId = null
|
|
|
+ this.defOptions = this.definitions
|
|
|
+ this.confirmDialogVisible = true
|
|
|
+ },
|
|
|
+ filterConfirmDefs(keyword) {
|
|
|
+ const kw = (keyword || '').trim().toLowerCase()
|
|
|
+ if (!kw) {
|
|
|
+ this.defOptions = this.definitions
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.defOptions = this.definitions.filter(d => (d.name || '').toLowerCase().indexOf(kw) > -1 || (d.code || '').toLowerCase().indexOf(kw) > -1)
|
|
|
+ },
|
|
|
+ async submitConfirm() {
|
|
|
+ if (!this.confirmDefId) {
|
|
|
+ this.$message.error('请选择目标定义')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.confirming = true
|
|
|
+ try {
|
|
|
+ const res = await importIndicatorMappings({
|
|
|
+ sourceType: this.confirmRow.sourceType,
|
|
|
+ seeds: [{ rawName: this.confirmRow.rawName, definitionId: this.confirmDefId }]
|
|
|
+ })
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.$message.success(res.message || '确认成功')
|
|
|
+ this.confirmDialogVisible = false
|
|
|
+ this.listMappings()
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.message || '确认失败')
|
|
|
+ }
|
|
|
+ } catch (e) { this.$message.error('确认失败') }
|
|
|
+ this.confirming = false
|
|
|
+ },
|
|
|
+ defName(id) {
|
|
|
+ return (this.defMap[id] || '#' + id)
|
|
|
+ },
|
|
|
+ formatTime(t) {
|
|
|
+ return t ? t.replace('T', ' ').substring(0, 19) : '-'
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
|
|
+.header h2 { margin: 0; }
|
|
|
+.filters { display: flex; align-items: center; gap: 12px; }
|
|
|
+</style>
|