health-knowledge.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <template>
  2. <div class="knowledge-page">
  3. <el-card>
  4. <div slot="header" class="clearfix">
  5. <span>健康知识库管理</span>
  6. <div style="float:right; display:flex; gap:10px;">
  7. <el-button type="warning" size="small" @click="importData">导入参考数据</el-button>
  8. <el-button type="primary" size="small" @click="showAddDialog">新增条目</el-button>
  9. </div>
  10. </div>
  11. <!-- 搜索 -->
  12. <el-form :model="filters" inline size="small" style="margin-bottom:16px;">
  13. <el-form-item label="名称">
  14. <el-input v-model="filters.name" placeholder="搜索指标名称" clearable @input="fetchList" />
  15. </el-form-item>
  16. <el-form-item label="分类">
  17. <el-select v-model="filters.category" placeholder="全部分类" clearable @change="fetchList" style="width:160px;">
  18. <el-option v-for="c in categories" :key="c" :label="c" :value="c" />
  19. </el-select>
  20. </el-form-item>
  21. </el-form>
  22. <el-table :data="list" border stripe v-loading="loading" empty-text="暂无数据" style="width:100%" :max-height="tableHeight">
  23. <el-table-column prop="id" label="ID" width="60">
  24. <template slot="header">
  25. <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
  26. </template>
  27. </el-table-column>
  28. <el-table-column prop="itemName" label="指标名称" min-width="140">
  29. <template slot="header">
  30. <span class="sort-header" @click.stop="onSortClick('itemName', $event)">指标名称{{ sortIcon('itemName') }}</span>
  31. </template>
  32. </el-table-column>
  33. <el-table-column prop="category" label="分类" width="140" />
  34. <el-table-column prop="normalRange" label="正常范围" width="140" />
  35. <el-table-column prop="description" label="说明" min-width="260" show-overflow-tooltip />
  36. <el-table-column prop="source" label="来源" width="120" show-overflow-tooltip />
  37. <el-table-column label="操作" width="130" fixed="right">
  38. <template slot-scope="{ row }">
  39. <el-button type="text" @click="editRow(row)">编辑</el-button>
  40. <el-button type="text" style="color:#f56c6c" @click="deleteRow(row)">删除</el-button>
  41. </template>
  42. </el-table-column>
  43. </el-table>
  44. </el-card>
  45. <el-dialog :visible.sync="dialog.visible" :title="dialog.isEdit ? '编辑知识库条目' : '新增知识库条目'" width="560px" :close-on-click-modal="false">
  46. <el-form :model="dialog.form" label-width="100px" size="small">
  47. <el-form-item label="指标名称" required>
  48. <el-input v-model="dialog.form.itemName" placeholder="如:维生素D" />
  49. </el-form-item>
  50. <el-form-item label="分类" required>
  51. <el-select v-model="dialog.form.category" placeholder="请选择分类" style="width:100%">
  52. <el-option v-for="c in categories" :key="c" :label="c" :value="c" />
  53. </el-select>
  54. </el-form-item>
  55. <el-form-item label="正常范围">
  56. <el-input v-model="dialog.form.normalRange" placeholder="如:30-100 ng/mL" />
  57. </el-form-item>
  58. <el-form-item label="说明">
  59. <el-input type="textarea" :rows="4" v-model="dialog.form.description" placeholder="指标说明..." />
  60. </el-form-item>
  61. <el-form-item label="来源">
  62. <el-input v-model="dialog.form.source" placeholder="数据来源" />
  63. </el-form-item>
  64. </el-form>
  65. <div slot="footer">
  66. <el-button @click="dialog.visible = false" size="small">取消</el-button>
  67. <el-button type="primary" @click="saveDialog" size="small" :loading="dialog.saving">保存</el-button>
  68. </div>
  69. </el-dialog>
  70. </div>
  71. </template>
  72. <script>
  73. import SortMixin from '@/mixins/SortMixin'
  74. export default {
  75. mixins: [SortMixin],
  76. computed: {
  77. tableHeight() {
  78. return window.innerHeight - 300
  79. }
  80. },
  81. data() {
  82. return {
  83. loading: false,
  84. list: [],
  85. sortableColumns: { id: 'ID', itemName: '指标名称' },
  86. filters: { name: '', category: '' },
  87. categories: ['维生素', '氨基酸', '微量元素', '短链脂肪酸', '神经递质与激素', '肠道屏障与代谢物', '抗生素风险评估'],
  88. dialog: { visible: false, isEdit: false, saving: false, form: {} }
  89. }
  90. },
  91. mounted() { this.fetchList() },
  92. methods: {
  93. loadList() {
  94. this.fetchList()
  95. },
  96. fetchList() {
  97. this.loading = true
  98. var params = {}
  99. if (this.filters.name) params.name = this.filters.name
  100. if (this.filters.category) params.category = this.filters.category
  101. if (this.sortState.length > 0) params.sort = this.sortState
  102. this.$axios.post('/api/health/knowledge/list', params).then(res => {
  103. var data = res.data || {}
  104. if (data.code === 200) {
  105. var rows = data.data || []
  106. if (this.filters.name) {
  107. rows = rows.filter(function(r) { return r.itemName && r.itemName.indexOf(this.filters.name) >= 0 }.bind(this))
  108. }
  109. if (this.filters.category) {
  110. rows = rows.filter(function(r) { return r.category === this.filters.category }.bind(this))
  111. }
  112. this.list = rows
  113. }
  114. }).finally(() => { this.loading = false })
  115. },
  116. showAddDialog() {
  117. this.dialog = { visible: true, isEdit: false, saving: false, form: { itemName: '', category: '', normalRange: '', description: '', source: '' } }
  118. },
  119. editRow(row) {
  120. 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 } }
  121. },
  122. saveDialog() {
  123. if (!this.dialog.form.itemName || !this.dialog.form.category) {
  124. this.$message.warning('指标名称和分类不能为空'); return
  125. }
  126. this.dialog.saving = true
  127. var url = this.dialog.isEdit ? '/api/health/knowledge/update' : '/api/health/knowledge/add'
  128. this.$axios.post(url, this.dialog.form).then(res => {
  129. if (res.data.code === 200) {
  130. this.$message.success('已保存')
  131. this.dialog.visible = false
  132. this.fetchList()
  133. } else {
  134. this.$message.error(res.data.message || '保存失败')
  135. }
  136. }).finally(() => { this.dialog.saving = false })
  137. },
  138. deleteRow(row) {
  139. this.$confirm('确认删除"' + row.itemName + '"?', '提示', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' }).then(function() {
  140. this.$axios.post('/api/health/knowledge/delete', { id: row.id }).then(function(res) {
  141. if (res.data.code === 200) {
  142. this.$message.success('已删除')
  143. this.fetchList()
  144. }
  145. }.bind(this))
  146. }.bind(this)).catch(function() {})
  147. },
  148. importData() {
  149. this.$confirm('将从参考JSON导入知识库数据,是否继续?', '提示', { confirmButtonText: '导入', cancelButtonText: '取消' }).then(function() {
  150. this.$axios.post('/api/health/knowledge/import', {}).then(function(res) {
  151. if (res.data.code === 200) {
  152. this.$message.success(res.data.data || '导入完成')
  153. this.fetchList()
  154. }
  155. }.bind(this))
  156. }.bind(this)).catch(function() {})
  157. }
  158. }
  159. }
  160. </script>
  161. <style scoped>
  162. .knowledge-page { padding: 20px; }
  163. .sort-header {
  164. cursor: pointer;
  165. user-select: none;
  166. font-weight: 500;
  167. font-size: 12px;
  168. }
  169. .sort-header:hover {
  170. color: #409eff;
  171. }
  172. </style>