|
@@ -1,232 +0,0 @@
|
|
|
-<template>
|
|
|
|
|
- <div class="knowledge-page">
|
|
|
|
|
- <el-card>
|
|
|
|
|
- <div slot="header" class="clearfix">
|
|
|
|
|
- <span>健康知识库管理</span>
|
|
|
|
|
- <div style="float:right; display:flex; gap:10px;">
|
|
|
|
|
- <el-button type="warning" size="small" @click="importData">导入参考数据</el-button>
|
|
|
|
|
- <el-button type="primary" size="small" @click="showAddDialog">新增条目</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 搜索 -->
|
|
|
|
|
- <el-form :model="filters" inline size="small" style="margin-bottom:16px;">
|
|
|
|
|
- <el-form-item label="名称">
|
|
|
|
|
- <el-input v-model="filters.name" placeholder="搜索指标名称" clearable @input="fetchList" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="分类">
|
|
|
|
|
- <el-select v-model="filters.category" placeholder="全部分类" clearable @change="fetchList" style="width:160px;">
|
|
|
|
|
- <el-option v-for="c in categories" :key="c" :label="c" :value="c" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
-
|
|
|
|
|
- <div class="adm-table-wrap">
|
|
|
|
|
-<el-table :data="list" border stripe v-loading="loading" empty-text="暂无数据" style="width:100%" height="100%">
|
|
|
|
|
- <el-table-column prop="id" label="ID" width="60">
|
|
|
|
|
- <template slot="header">
|
|
|
|
|
- <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="itemName" label="指标名称" min-width="140">
|
|
|
|
|
- <template slot="header">
|
|
|
|
|
- <span class="sort-header" @click.stop="onSortClick('itemName', $event)">指标名称{{ sortIcon('itemName') }}</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="category" label="分类" width="140">
|
|
|
|
|
- <template slot="header">
|
|
|
|
|
- <span class="sort-header" @click.stop="onSortClick('category', $event)">分类<span v-if="sortIndex('category') !== null" class="sort-index" :class="sortClass('category') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('category') }}</span>{{ sortIcon('category') }}</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="normalRange" label="正常范围" width="140">
|
|
|
|
|
- <template slot="header">
|
|
|
|
|
- <span class="sort-header" @click.stop="onSortClick('normalRange', $event)">正常范围<span v-if="sortIndex('normalRange') !== null" class="sort-index" :class="sortClass('normalRange') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('normalRange') }}</span>{{ sortIcon('normalRange') }}</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="description" label="说明" min-width="260">
|
|
|
|
|
- <template slot="header">
|
|
|
|
|
- <span class="sort-header" @click.stop="onSortClick('description', $event)">说明<span v-if="sortIndex('description') !== null" class="sort-index" :class="sortClass('description') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('description') }}</span>{{ sortIcon('description') }}</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- <template slot-scope="{ row }">
|
|
|
|
|
- <span class="desc-ellipsis">{{ (row.description || '').slice(0, 120) }}{{ (row.description || '').length > 120 ? '…' : '' }}</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="source" label="来源" width="120" show-overflow-tooltip>
|
|
|
|
|
- <template slot="header">
|
|
|
|
|
- <span class="sort-header" @click.stop="onSortClick('source', $event)">来源<span v-if="sortIndex('source') !== null" class="sort-index" :class="sortClass('source') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('source') }}</span>{{ sortIcon('source') }}</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="操作" width="200" fixed="right">
|
|
|
|
|
- <template slot-scope="{ row }">
|
|
|
|
|
- <el-button type="text" @click="showDetail(row)">详情</el-button>
|
|
|
|
|
- <el-button type="text" @click="editRow(row)">编辑</el-button>
|
|
|
|
|
- <el-button type="text" style="color:#f56c6c" @click="deleteRow(row)">删除</el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- </el-table>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-card>
|
|
|
|
|
-
|
|
|
|
|
- <el-dialog :visible.sync="dialog.visible" :title="dialog.isEdit ? '编辑知识库条目' : '新增知识库条目'" width="560px" :close-on-click-modal="false">
|
|
|
|
|
- <el-form :model="dialog.form" label-width="100px" size="small">
|
|
|
|
|
- <el-form-item label="指标名称" required>
|
|
|
|
|
- <el-input v-model="dialog.form.itemName" placeholder="如:维生素D" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="分类" required>
|
|
|
|
|
- <el-select v-model="dialog.form.category" placeholder="请选择分类" style="width:100%">
|
|
|
|
|
- <el-option v-for="c in categories" :key="c" :label="c" :value="c" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="正常范围">
|
|
|
|
|
- <el-input v-model="dialog.form.normalRange" placeholder="如:30-100 ng/mL" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="说明">
|
|
|
|
|
- <el-input type="textarea" :rows="4" v-model="dialog.form.description" placeholder="指标说明..." />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="来源">
|
|
|
|
|
- <el-input v-model="dialog.form.source" placeholder="数据来源" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <div slot="footer">
|
|
|
|
|
- <el-button @click="dialog.visible = false" size="small">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="saveDialog" size="small" :loading="dialog.saving">保存</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 详情弹窗:完整展示文章/条目全文(菌群文章最长 71K 字符) -->
|
|
|
|
|
- <el-dialog :visible.sync="detail.visible" :title="detail.row ? detail.row.itemName : '详情'" width="820px" :close-on-click-modal="true">
|
|
|
|
|
- <div v-if="detail.row">
|
|
|
|
|
- <div class="detail-meta">
|
|
|
|
|
- <el-tag v-if="detail.row.category" size="small">{{ detail.row.category }}</el-tag>
|
|
|
|
|
- <span class="detail-meta-item">来源:{{ detail.row.source || '-' }}</span>
|
|
|
|
|
- <span class="detail-meta-item">ID:{{ detail.row.id }}</span>
|
|
|
|
|
- <span class="detail-meta-item">正文 {{ (detail.row.description || '').length }} 字符</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-content">{{ detail.row.description || '(无正文)' }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div slot="footer">
|
|
|
|
|
- <el-button @click="detail.visible = false" size="small">关闭</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
- </div>
|
|
|
|
|
-</template>
|
|
|
|
|
-
|
|
|
|
|
-<script>
|
|
|
|
|
-import SortMixin from '@/mixins/SortMixin'
|
|
|
|
|
-import request from '@/utils/request'
|
|
|
|
|
-
|
|
|
|
|
-export default {
|
|
|
|
|
- mixins: [SortMixin],
|
|
|
|
|
- computed: {
|
|
|
|
|
- tableHeight() {
|
|
|
|
|
- return window.innerHeight - 300
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- data() {
|
|
|
|
|
- return {
|
|
|
|
|
- loading: false,
|
|
|
|
|
- list: [],
|
|
|
|
|
- sortableColumns: { id: 'ID', itemName: '指标名称', category: '分类', normalRange: '正常范围', description: '说明', source: '来源' },
|
|
|
|
|
- filters: { name: '', category: '' },
|
|
|
|
|
- categories: ['维生素', '氨基酸', '微量元素', '短链脂肪酸', '神经递质与激素', '肠道屏障与代谢物', '抗生素风险评估'],
|
|
|
|
|
- detail: { visible: false, row: null },
|
|
|
|
|
- dialog: { visible: false, isEdit: false, saving: false, form: {} }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- mounted() { this.fetchList() },
|
|
|
|
|
- methods: {
|
|
|
|
|
- loadList() {
|
|
|
|
|
- this.fetchList()
|
|
|
|
|
- },
|
|
|
|
|
- fetchList() {
|
|
|
|
|
- this.loading = true
|
|
|
|
|
- var params = {}
|
|
|
|
|
- if (this.filters.name) params.name = this.filters.name
|
|
|
|
|
- if (this.filters.category) params.category = this.filters.category
|
|
|
|
|
- if (this.sortState.length > 0) params.sort = this.sortState
|
|
|
|
|
- request.post('/api/health/knowledge/list', params).then(res => {
|
|
|
|
|
- if (res.code === 200) {
|
|
|
|
|
- var rows = res.data || []
|
|
|
|
|
- if (this.filters.name) {
|
|
|
|
|
- rows = rows.filter(function(r) { return r.itemName && r.itemName.indexOf(this.filters.name) >= 0 }.bind(this))
|
|
|
|
|
- }
|
|
|
|
|
- if (this.filters.category) {
|
|
|
|
|
- rows = rows.filter(function(r) { return r.category === this.filters.category }.bind(this))
|
|
|
|
|
- }
|
|
|
|
|
- // 动态合并数据中的分类(菌群文章等外部来源的分类可能不在硬编码列表内)
|
|
|
|
|
- var cats = this.categories.slice()
|
|
|
|
|
- rows.forEach(function(r) {
|
|
|
|
|
- if (r.category && cats.indexOf(r.category) < 0) cats.push(r.category)
|
|
|
|
|
- })
|
|
|
|
|
- this.categories = cats
|
|
|
|
|
- this.list = rows
|
|
|
|
|
- } else {
|
|
|
|
|
- this.$message.error(res.message || '加载失败')
|
|
|
|
|
- }
|
|
|
|
|
- }).finally(() => { this.loading = false })
|
|
|
|
|
- },
|
|
|
|
|
- showAddDialog() {
|
|
|
|
|
- this.dialog = { visible: true, isEdit: false, saving: false, form: { itemName: '', category: '', normalRange: '', description: '', source: '' } }
|
|
|
|
|
- },
|
|
|
|
|
- editRow(row) {
|
|
|
|
|
- this.dialog = { visible: true, isEdit: true, saving: false, form: { id: row.id, itemName: row.itemName, category: row.category, normalRange: row.normalRange, description: row.description, source: row.source } }
|
|
|
|
|
- },
|
|
|
|
|
- showDetail(row) {
|
|
|
|
|
- this.detail = { visible: true, row: row }
|
|
|
|
|
- },
|
|
|
|
|
- saveDialog() {
|
|
|
|
|
- if (!this.dialog.form.itemName || !this.dialog.form.category) {
|
|
|
|
|
- this.$message.warning('指标名称和分类不能为空'); return
|
|
|
|
|
- }
|
|
|
|
|
- this.dialog.saving = true
|
|
|
|
|
- var url = this.dialog.isEdit ? '/api/health/knowledge/update' : '/api/health/knowledge/add'
|
|
|
|
|
- request.post(url, this.dialog.form).then(res => {
|
|
|
|
|
- if (res.code === 200) {
|
|
|
|
|
- this.$message.success('已保存')
|
|
|
|
|
- this.dialog.visible = false
|
|
|
|
|
- this.fetchList()
|
|
|
|
|
- } else {
|
|
|
|
|
- this.$message.error(res.message || '保存失败')
|
|
|
|
|
- }
|
|
|
|
|
- }).finally(() => { this.dialog.saving = false })
|
|
|
|
|
- },
|
|
|
|
|
- deleteRow(row) {
|
|
|
|
|
- this.$confirm('确认删除"' + row.itemName + '"?', '提示', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(function() {
|
|
|
|
|
- request.post('/api/health/knowledge/delete', { id: row.id }).then(function(res) {
|
|
|
|
|
- if (res.code === 200) {
|
|
|
|
|
- this.$message.success('已删除')
|
|
|
|
|
- this.fetchList()
|
|
|
|
|
- }
|
|
|
|
|
- }.bind(this))
|
|
|
|
|
- }.bind(this)).catch(function() {})
|
|
|
|
|
- },
|
|
|
|
|
- importData() {
|
|
|
|
|
- this.$confirm('将从参考JSON导入知识库数据,是否继续?', '提示', { confirmButtonText: '导入', cancelButtonText: '取消' }).then(function() {
|
|
|
|
|
- request.post('/api/health/knowledge/import', {}).then(function(res) {
|
|
|
|
|
- if (res.code === 200) {
|
|
|
|
|
- this.$message.success(res.data || '导入完成')
|
|
|
|
|
- this.fetchList()
|
|
|
|
|
- }
|
|
|
|
|
- }.bind(this))
|
|
|
|
|
- }.bind(this)).catch(function() {})
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-</script>
|
|
|
|
|
-
|
|
|
|
|
-<style scoped>
|
|
|
|
|
-.knowledge-page { padding: 20px; }
|
|
|
|
|
-.desc-ellipsis { display: inline; }
|
|
|
|
|
-.detail-meta { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #ebeef5; font-size: 12px; color: #606266; }
|
|
|
|
|
-.detail-meta-item { color: #909399; }
|
|
|
|
|
-.detail-content { max-height: 62vh; overflow-y: auto; padding: 10px 12px; background: #fafafa; border-radius: 4px; white-space: pre-wrap; word-break: break-word; font-size: 13px; line-height: 1.75; color: #303133; }
|
|
|
|
|
-.sort-header {
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- user-select: none;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-.sort-header:hover {
|
|
|
|
|
- color: #409eff;
|
|
|
|
|
-}
|
|
|
|
|
-</style>
|
|
|