Browse Source

feat(admin): AI 问卷场景配置管理页

Xiaogang Liao 1 month ago
parent
commit
a8fe5f372a

+ 13 - 0
cfc-web/src/api/aiQuestionnaire.js

@@ -0,0 +1,13 @@
+import request from '@/utils/request'
+
+export function listScenes(params) {
+  return request({ url: '/api/ai-questionnaire/scene/list', method: 'post', data: params })
+}
+
+export function saveScene(scene) {
+  return request({ url: '/api/ai-questionnaire/scene/save', method: 'post', data: scene })
+}
+
+export function deleteScene(id) {
+  return request({ url: '/api/ai-questionnaire/scene/delete', method: 'post', data: { id: id } })
+}

+ 6 - 0
cfc-web/src/router/index.js

@@ -556,6 +556,12 @@ const routes = [
           component: () => import('@/views/admin/SurveyTemplates.vue'),
           meta: { title: '调研模板管理', perm: 'admin:all' }
         },
+        {
+          path: 'ai-questionnaire-scenes',
+          name: 'AiQuestionnaireScenes',
+          component: () => import('@/views/admin/AiQuestionnaireScenes.vue'),
+          meta: { title: 'AI 问卷场景配置', perm: 'admin:all' }
+        },
       {
         path: 'activity-review',
         name: 'ActivityReview',

+ 185 - 0
cfc-web/src/views/admin/AiQuestionnaireScenes.vue

@@ -0,0 +1,185 @@
+<template>
+  <div class="ai-q-scenes">
+    <div class="toolbar">
+      <el-button type="primary" size="small" @click="openEdit()">新增场景</el-button>
+    </div>
+    <el-table :data="scenes" border stripe size="small">
+      <el-table-column prop="sceneKey" label="场景标识" width="140" />
+      <el-table-column prop="sceneName" label="场景名称" width="160" />
+      <el-table-column prop="description" label="描述" show-overflow-tooltip />
+      <el-table-column prop="kbScope" label="知识库范围" width="160" />
+      <el-table-column prop="maxQuestions" label="题数上限" width="90" />
+      <el-table-column label="启用" width="80">
+        <template slot-scope="{ row }">
+          <el-tag :type="row.enabled === 1 ? 'success' : 'info'" size="mini">{{ row.enabled === 1 ? '是' : '否' }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="180">
+        <template slot-scope="{ row }">
+          <el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
+          <el-button size="mini" type="danger" @click="onDelete(row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-dialog :title="form.id ? '编辑场景' : '新增场景'" :visible.sync="dialogVisible" width="640px" append-to-body>
+      <el-form :model="form" label-width="110px" size="small">
+        <el-form-item label="场景标识" required>
+          <el-input v-model="form.sceneKey" placeholder="如 microbiome" :disabled="!!form.id" />
+        </el-form-item>
+        <el-form-item label="场景名称" required>
+          <el-input v-model="form.sceneName" placeholder="如 菌群健康评估" />
+        </el-form-item>
+        <el-form-item label="描述">
+          <el-input v-model="form.description" type="textarea" :rows="2" />
+        </el-form-item>
+        <el-form-item label="开场引导">
+          <el-input v-model="form.openingPrompt" type="textarea" :rows="3" placeholder="AI 出第一题前的引导语" />
+        </el-form-item>
+        <el-form-item label="画像维度定义">
+          <el-input v-model="form.dimensionsJson" type="textarea" :rows="5" placeholder='{"user":["肠道菌群状态"],"need":["营养需求"]}' />
+        </el-form-item>
+        <el-form-item label="知识库范围">
+          <el-select v-model="kbScopeArr" multiple placeholder="选择知识库源" style="width:100%">
+            <el-option label="菌群知识库" value="microbiome" />
+            <el-option label="统一知识库" value="dan_knowledge" />
+            <el-option label="文章" value="article" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="题数上限">
+          <el-input-number v-model="form.maxQuestions" :min="5" :max="30" style="width:120px" />
+        </el-form-item>
+        <el-form-item label="系统提示词">
+          <el-input v-model="form.systemPrompt" type="textarea" :rows="3" />
+        </el-form-item>
+        <el-form-item label="启用">
+          <el-switch v-model="form.enabled" :active-value="1" :inactive-value="0" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button size="small" @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" size="small" @click="onSave">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listScenes, saveScene, deleteScene } from '@/api/aiQuestionnaire'
+
+export default {
+  name: 'AiQuestionnaireScenes',
+  data() {
+    return {
+      scenes: [],
+      dialogVisible: false,
+      kbScopeArr: [],
+      form: {
+        id: null,
+        sceneKey: '',
+        sceneName: '',
+        description: '',
+        openingPrompt: '',
+        dimensionsJson: '',
+        kbScope: '',
+        maxQuestions: 12,
+        systemPrompt: '',
+        enabled: 1
+      }
+    }
+  },
+  created() {
+    this.load()
+  },
+  methods: {
+    async load() {
+      try {
+        var res = await listScenes({})
+        if (res.code === 200) {
+          this.scenes = res.data || []
+        } else {
+          this.$message.error(res.message || '加载失败')
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      }
+    },
+    openEdit(row) {
+      if (row) {
+        this.form = {
+          id: row.id,
+          sceneKey: row.sceneKey || '',
+          sceneName: row.sceneName || '',
+          description: row.description || '',
+          openingPrompt: row.openingPrompt || '',
+          dimensionsJson: row.dimensionsJson || '',
+          kbScope: row.kbScope || '',
+          maxQuestions: row.maxQuestions || 12,
+          systemPrompt: row.systemPrompt || '',
+          enabled: row.enabled === 1 ? 1 : 0
+        }
+      } else {
+        this.form = {
+          id: null,
+          sceneKey: '',
+          sceneName: '',
+          description: '',
+          openingPrompt: '',
+          dimensionsJson: '',
+          kbScope: '',
+          maxQuestions: 12,
+          systemPrompt: '',
+          enabled: 1
+        }
+      }
+      this.kbScopeArr = this.form.kbScope ? this.form.kbScope.split(',').filter(function(s) { return s }) : []
+      this.dialogVisible = true
+    },
+    async onSave() {
+      if (!this.form.sceneKey || !this.form.sceneName) {
+        this.$message.warning('请填写场景标识与名称')
+        return
+      }
+      this.form.kbScope = (this.kbScopeArr || []).join(',')
+      try {
+        var res = await saveScene(this.form)
+        if (res.code === 200) {
+          this.$message.success('保存成功')
+          this.dialogVisible = false
+          this.load()
+        } else {
+          this.$message.error(res.message || '保存失败')
+        }
+      } catch (e) {
+        this.$message.error('保存失败')
+      }
+    },
+    onDelete(row) {
+      this.$confirm('删除后不可恢复(已有问卷记录将被拒绝),确认删除?', '提示', { type: 'warning' })
+        .then(async () => {
+          try {
+            var res = await deleteScene(row.id)
+            if (res.code === 200) {
+              this.$message.success('已删除')
+              this.load()
+            } else {
+              this.$message.error(res.message || '删除失败')
+            }
+          } catch (e) {
+            this.$message.error('删除失败')
+          }
+        })
+        .catch(function() {})
+    }
+  }
+}
+</script>
+
+<style scoped>
+.ai-q-scenes {
+  padding: 16px;
+}
+.toolbar {
+  margin-bottom: 16px;
+}
+</style>