Browse Source

feat(web): 改版v1 问题域管理页 (W1)

ProblemDomainManage.vue: 问题域表格+新增/编辑弹窗(编码/名称/图标/描述/调研模板ID选择/内容标签/推荐产品/任务模板包/奖励CF值/7步流程可视化编辑)。api/problemDomain.js 封装。路由注册 problem-domain-manage。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 2 months ago
parent
commit
cdc94cd070

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

@@ -0,0 +1,13 @@
+import request from '@/utils/request'
+
+export function getProblemDomainListAdmin(params) {
+  return request({ url: '/api/admin/problem-domain/list', method: 'post', data: params || {} })
+}
+
+export function saveProblemDomain(data) {
+  return request({ url: '/api/admin/problem-domain/save', method: 'post', data })
+}
+
+export function deleteProblemDomain(data) {
+  return request({ url: '/api/admin/problem-domain/delete', method: 'post', data })
+}

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

@@ -568,6 +568,12 @@ const routes = [
           component: () => import('@/views/admin/SurveyTemplates.vue'),
           meta: { title: '调研模板管理', perm: 'admin:all' }
         },
+        {
+          path: 'problem-domain-manage',
+          name: 'ProblemDomainManage',
+          component: () => import('@/views/admin/ProblemDomainManage.vue'),
+          meta: { title: '问题域管理', perm: 'admin:all' }
+        },
         {
           path: 'ai-questionnaire-scenes',
           name: 'AiQuestionnaireScenes',

+ 243 - 0
cfc-web/src/views/admin/ProblemDomainManage.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="problem-domain-manage admin-page">
+    <el-card>
+      <div slot="header" class="admin-page-header">
+        <span class="admin-page-title">问题域管理(改版v1)</span>
+        <el-button type="primary" size="mini" icon="el-icon-plus" @click="openCreate">新增问题域</el-button>
+      </div>
+
+      <el-table :data="list" v-loading="loading" border stripe>
+        <el-table-column prop="id" label="ID" width="60" />
+        <el-table-column prop="code" label="编码" min-width="110" />
+        <el-table-column prop="name" label="名称" min-width="100" />
+        <el-table-column prop="icon" label="图标" width="70" align="center">
+          <template slot-scope="scope">
+            <span style="font-size:20px">{{ scope.row.icon || '🧭' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="description" label="描述" min-width="160" show-overflow-tooltip />
+        <el-table-column prop="rewardCfValue" label="奖励CF" width="80" align="center" />
+        <el-table-column label="启用" width="80" align="center">
+          <template slot-scope="scope">
+            <el-switch
+              v-model="scope.row.status"
+              :active-value="1"
+              :inactive-value="0"
+              @change="toggleStatus(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="150" align="center">
+          <template slot-scope="scope">
+            <el-button type="text" size="mini" @click="openEdit(scope.row)">编辑</el-button>
+            <el-button type="text" size="mini" style="color:#f56c6c" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog :title="form.id ? '编辑问题域' : '新增问题域'" :visible.sync="dialogVisible" width="700px" top="5vh">
+      <el-form :model="form" label-width="120px">
+        <el-form-item label="编码" required>
+          <el-input v-model="form.code" placeholder="如 weight_loss / sleep / constipation" :disabled="!!form.id" />
+        </el-form-item>
+        <el-form-item label="名称" required>
+          <el-input v-model="form.name" placeholder="如:减肥 / 睡眠 / 便秘" />
+        </el-form-item>
+        <el-form-item label="图标">
+          <el-input v-model="form.icon" placeholder="emoji,如 🌙" style="width:120px" />
+        </el-form-item>
+        <el-form-item label="描述">
+          <el-input v-model="form.description" type="textarea" :rows="2" placeholder="一句话描述" />
+        </el-form-item>
+        <el-form-item label="调研模板ID">
+          <el-input v-model.number="form.surveyTemplateId" placeholder="SurveyTemplate.id" style="width:200px" />
+          <el-button size="mini" style="margin-left:8px" @click="loadSurveys">从调研模板列表选择</el-button>
+          <el-select v-if="surveyOptions.length" v-model="form.surveyTemplateId" placeholder="选择模板" style="width:200px;margin-left:8px" clearable>
+            <el-option
+              v-for="s in surveyOptions"
+              :key="s.id"
+              :label="s.title + ' (#' + s.id + ')'"
+              :value="s.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="内容标签">
+          <el-input v-model="form.contentTags" placeholder="逗号分隔,如 睡眠,失眠,作息" />
+        </el-form-item>
+        <el-form-item label="推荐产品ID">
+          <el-input v-model="form.recommendedProductIds" placeholder="逗号分隔 Product.id" />
+        </el-form-item>
+        <el-form-item label="任务模板包ID">
+          <el-input v-model="form.taskTemplatePackageIds" placeholder="逗号分隔 TaskTemplatePackage.id" />
+        </el-form-item>
+        <el-form-item label="奖励CF值">
+          <el-input-number v-model="form.rewardCfValue" :min="0" :step="100" />
+        </el-form-item>
+        <el-form-item label="排序">
+          <el-input-number v-model="form.sortOrder" :min="0" :step="1" />
+        </el-form-item>
+        <el-form-item label="启用">
+          <el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
+        </el-form-item>
+
+        <el-divider content-position="left">服务流程(7步,JSON数组)</el-divider>
+        <el-form-item label="流程JSON">
+          <el-input
+            v-model="form.flowIntroJson"
+            type="textarea"
+            :rows="7"
+            placeholder='[{"title":"选择问题域","desc":"了解你的需求"},{"title":"填写调研","desc":"..."}]'
+          />
+        </el-form-item>
+        <el-form-item label="可视化编辑">
+          <div style="width:100%">
+            <div
+              v-for="(step, idx) in flowSteps"
+              :key="idx"
+              style="margin-bottom:8px;padding:8px;border:1px solid #eee;border-radius:4px;"
+            >
+              <el-input v-model="step.title" placeholder="步骤标题" style="width:40%;margin-right:8px" size="small" />
+              <el-input v-model="step.desc" placeholder="步骤描述" style="width:40%;margin-right:8px" size="small" />
+              <el-button type="text" size="small" style="color:#f56c6c" @click="removeStep(idx)">删除</el-button>
+            </div>
+            <el-button type="primary" size="mini" plain @click="addStep">+ 添加步骤</el-button>
+            <el-button type="success" size="mini" plain @click="syncFlowJson" style="margin-left:8px">同步到JSON</el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+      <span slot="footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSave">保存</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getProblemDomainListAdmin, saveProblemDomain, deleteProblemDomain } from '@/api/problemDomain'
+import { getSurveyTemplates } from '@/api/survey'
+
+export default {
+  name: 'ProblemDomainManage',
+  data() {
+    return {
+      list: [],
+      loading: false,
+      dialogVisible: false,
+      surveyOptions: [],
+      form: {
+        id: null,
+        code: '',
+        name: '',
+        icon: '🧭',
+        description: '',
+        surveyTemplateId: null,
+        contentTags: '',
+        recommendedProductIds: '',
+        taskTemplatePackageIds: '',
+        rewardCfValue: 0,
+        sortOrder: 0,
+        status: 1,
+        flowIntroJson: ''
+      },
+      flowSteps: []
+    }
+  },
+  created() {
+    this.loadList()
+    this.loadSurveys()
+  },
+  methods: {
+    loadList() {
+      this.loading = true
+      getProblemDomainListAdmin({}).then(res => {
+        this.loading = false
+        this.list = res.data || []
+      }).catch(() => {
+        this.loading = false
+        this.$message.error('加载失败')
+      })
+    },
+    loadSurveys() {
+      getSurveyTemplates({}).then(res => {
+        this.surveyOptions = res.data || []
+      }).catch(() => {})
+    },
+    openCreate() {
+      this.form = {
+        id: null, code: '', name: '', icon: '🧭', description: '',
+        surveyTemplateId: null, contentTags: '', recommendedProductIds: '',
+        taskTemplatePackageIds: '', rewardCfValue: 0, sortOrder: 0, status: 1,
+        flowIntroJson: ''
+      }
+      this.flowSteps = []
+      this.dialogVisible = true
+    },
+    openEdit(row) {
+      this.form = Object.assign({}, row)
+      this.flowSteps = this.parseFlowSteps(row.flowIntroJson)
+      this.dialogVisible = true
+    },
+    parseFlowSteps(json) {
+      if (!json) return []
+      try {
+        const parsed = typeof json === 'string' ? JSON.parse(json) : json
+        return Array.isArray(parsed) ? parsed : []
+      } catch (e) {
+        return []
+      }
+    },
+    buildFlowJson() {
+      return JSON.stringify(this.flowSteps)
+    },
+    addStep() {
+      this.flowSteps.push({ title: '', desc: '' })
+    },
+    removeStep(idx) {
+      this.flowSteps.splice(idx, 1)
+    },
+    syncFlowJson() {
+      this.form.flowIntroJson = this.buildFlowJson()
+      this.$message.success('已同步到JSON')
+    },
+    toggleStatus(row) {
+      saveProblemDomain(row).then(() => {
+        this.$message.success('状态已更新')
+      }).catch(() => {
+        this.$message.error('更新失败')
+        this.loadList()
+      })
+    },
+    handleSave() {
+      // 若可视化步骤有内容且JSON为空,自动同步
+      if (this.flowSteps.length > 0 && !this.form.flowIntroJson) {
+        this.form.flowIntroJson = this.buildFlowJson()
+      }
+      if (!this.form.code || !this.form.name) {
+        this.$message.warning('编码和名称必填')
+        return
+      }
+      saveProblemDomain(this.form).then(res => {
+        this.$message.success('保存成功')
+        this.dialogVisible = false
+        this.loadList()
+      }).catch(err => {
+        this.$message.error((err && err.message) || '保存失败')
+      })
+    },
+    handleDelete(row) {
+      this.$confirm(`确认删除问题域「${row.name}」?`, '提示', { type: 'warning' })
+        .then(() => {
+          deleteProblemDomain({ id: row.id }).then(() => {
+            this.$message.success('删除成功')
+            this.loadList()
+          }).catch(() => {
+            this.$message.error('删除失败')
+          })
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>