Эх сурвалжийг харах

新增欢迎语配置管理页面,同步更新开发文档

yawuga 2 долоо хоног өмнө
parent
commit
14fc14c7d8

+ 27 - 0
src/api/base/welcomeConfig.js

@@ -0,0 +1,27 @@
+import request from '@/utils/request'
+
+// 获取欢迎语配置
+export function getWelcomeConfig() {
+  return request({
+    url: '/robot-ops/content/welcome-config',
+    method: 'get'
+  })
+}
+
+// 保存欢迎语配置
+export function saveWelcomeConfig(data) {
+  return request({
+    url: '/robot-ops/content/welcome-config',
+    method: 'put',
+    data: data
+  })
+}
+
+// 测试欢迎语播报
+export function testWelcomeBroadcast(data) {
+  return request({
+    url: '/robot-ops/content/welcome-config/test',
+    method: 'post',
+    data: data
+  })
+}

+ 298 - 0
src/views/base/welcomeConfig/index.vue

@@ -0,0 +1,298 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部说明 -->
+    <el-alert
+      title="欢迎语配置"
+      type="info"
+      :closable="false"
+      show-icon
+      class="form-tip"
+    >
+      <template #default>
+        用于配置机器人检测到访客时的默认欢迎语、语音播报及重复播报冷却策略。
+      </template>
+    </el-alert>
+
+    <!-- 配置表单卡片 -->
+    <el-card v-loading="loading" class="welcome-config-card">
+      <el-form
+        ref="formRef"
+        :model="form"
+        :rules="rules"
+        label-width="150px"
+        class="welcome-config-form"
+      >
+        <el-form-item label="欢迎语文本" prop="welcomeText">
+          <el-input
+            v-model="form.welcomeText"
+            type="textarea"
+            :rows="4"
+            maxlength="200"
+            show-word-limit
+            placeholder="请输入欢迎语文本"
+          />
+          <div class="form-tip">
+            用于机器人检测到访客时展示或播报的欢迎语内容。
+          </div>
+        </el-form-item>
+
+        <el-form-item label="启用欢迎语" prop="status">
+          <el-switch
+            v-model="form.status"
+            active-value="1"
+            inactive-value="0"
+          />
+          <div v-if="form.status === '0'" class="form-warning">
+            当前欢迎语功能已停用,机器人检测到访客时不会触发欢迎语。
+          </div>
+        </el-form-item>
+
+        <el-form-item label="语音播报" prop="voiceEnabled">
+          <el-switch
+            v-model="form.voiceEnabled"
+            active-value="1"
+            inactive-value="0"
+          />
+          <div class="form-tip">
+            启用后,欢迎语触发时进行语音播报;关闭后仅用于屏幕展示。
+          </div>
+          <div v-if="form.voiceEnabled === '0'" class="form-warning">
+            当前仅用于屏幕展示,不进行语音播报
+          </div>
+        </el-form-item>
+
+        <el-form-item label="语音播报冷却时间" prop="cooldownSeconds">
+          <el-input-number
+            v-model="form.cooldownSeconds"
+            :min="5"
+            :max="3600"
+            :step="1"
+            controls-position="right"
+            style="width: 200px"
+          />
+          <span class="form-unit">秒</span>
+          <div class="form-tip">
+            机器人语音播报欢迎语后,在该时间内不重复语音播报。冷却期内再次检测到访客时,可仅做屏幕展示。
+          </div>
+        </el-form-item>
+
+        <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            :rows="3"
+            maxlength="500"
+            show-word-limit
+            placeholder="请输入备注"
+          />
+        </el-form-item>
+      </el-form>
+
+      <!-- 按钮区 -->
+      <div class="action-bar">
+        <el-button
+          type="primary"
+          :loading="saveLoading"
+          @click="handleSave"
+          v-hasPermi="['base:welcomeConfig:edit']"
+        >
+          保存配置
+        </el-button>
+        <el-button
+          type="warning"
+          :loading="testLoading"
+          @click="handleTest"
+          v-hasPermi="['base:welcomeConfig:test']"
+        >
+          测试播报
+        </el-button>
+        <el-button @click="handleReset">
+          恢复默认
+        </el-button>
+        <el-button icon="Refresh" :loading="loading" @click="handleRefresh">
+          刷新
+        </el-button>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { getWelcomeConfig, saveWelcomeConfig, testWelcomeBroadcast } from '@/api/base/welcomeConfig'
+
+const { proxy } = getCurrentInstance()
+
+const loading = ref(false)
+const saveLoading = ref(false)
+const testLoading = ref(false)
+const formRef = ref(null)
+
+const defaultConfig = {
+  configKey: 'default',
+  welcomeText: '您好,欢迎光临!我是迎宾巡逻安防机器人,很高兴为您服务。',
+  status: '1',
+  voiceEnabled: '1',
+  cooldownSeconds: 30,
+  remark: ''
+}
+
+const form = ref({ ...defaultConfig })
+
+const rules = {
+  welcomeText: [
+    { required: true, message: '请输入欢迎语文本', trigger: 'blur' },
+    { max: 200, message: '欢迎语文本不能超过 200 字', trigger: 'blur' }
+  ],
+  status: [
+    { required: true, message: '请选择启用状态', trigger: 'change' }
+  ],
+  voiceEnabled: [
+    { required: true, message: '请选择是否语音播报', trigger: 'change' }
+  ],
+  cooldownSeconds: [
+    { required: true, message: '请输入语音播报冷却时间', trigger: 'blur' }
+  ],
+  remark: [
+    { max: 500, message: '备注不能超过 500 字', trigger: 'blur' }
+  ]
+}
+
+/** 加载配置 */
+function loadConfig() {
+  loading.value = true
+  getWelcomeConfig()
+    .then(response => {
+      const data = response.data || response
+      if (data && Object.keys(data).length > 0) {
+        form.value = {
+          configKey: data.configKey || 'default',
+          welcomeText: data.welcomeText || defaultConfig.welcomeText,
+          status: String(data.status ?? '1'),
+          voiceEnabled: String(data.voiceEnabled ?? '1'),
+          cooldownSeconds: Number(data.cooldownSeconds || 30),
+          remark: data.remark || ''
+        }
+      } else {
+        form.value = { ...defaultConfig }
+      }
+    })
+    .catch(() => {
+      proxy.$modal.msgWarning('获取欢迎语配置失败,已加载默认配置')
+      form.value = { ...defaultConfig }
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+
+/** 保存配置 */
+function handleSave() {
+  if (!formRef.value) return
+  formRef.value.validate(valid => {
+    if (!valid) return
+    saveLoading.value = true
+    const payload = {
+      configKey: form.value.configKey || 'default',
+      welcomeText: form.value.welcomeText,
+      status: String(form.value.status),
+      voiceEnabled: String(form.value.voiceEnabled),
+      cooldownSeconds: Number(form.value.cooldownSeconds || 30),
+      remark: form.value.remark || ''
+    }
+    saveWelcomeConfig(payload)
+      .then(() => {
+        proxy.$modal.msgSuccess('保存成功')
+      })
+      .catch(() => {
+        proxy.$modal.msgError('保存失败,请稍后重试')
+      })
+      .finally(() => {
+        saveLoading.value = false
+      })
+  })
+}
+
+/** 测试播报 */
+function handleTest() {
+  if (!formRef.value) return
+  formRef.value.validateField('welcomeText').then(() => {
+    if (form.value.voiceEnabled === '0') {
+      proxy.$modal.confirm('当前语音播报已关闭,本次测试将仅测试欢迎语内容下发。是否继续?')
+        .then(() => {
+          doTest()
+        })
+        .catch(() => {})
+    } else {
+      doTest()
+    }
+  }).catch(() => {})
+}
+
+function doTest() {
+  testLoading.value = true
+  testWelcomeBroadcast({
+    configKey: form.value.configKey || 'default',
+    welcomeText: form.value.welcomeText,
+    status: String(form.value.status),
+    voiceEnabled: String(form.value.voiceEnabled)
+  })
+    .then(() => {
+      proxy.$modal.msgSuccess('测试播报指令已下发')
+    })
+    .catch(() => {
+      proxy.$modal.msgError('测试播报失败,请稍后重试')
+    })
+    .finally(() => {
+      testLoading.value = false
+    })
+}
+
+/** 恢复默认 */
+function handleReset() {
+  proxy.$modal.confirm('确认恢复为系统默认欢迎语配置吗?恢复后需点击“保存配置”才会生效。')
+    .then(() => {
+      form.value = { ...defaultConfig }
+      if (formRef.value) {
+        formRef.value.clearValidate()
+      }
+    })
+    .catch(() => {})
+}
+
+/** 刷新 */
+function handleRefresh() {
+  loadConfig()
+}
+
+onMounted(() => {
+  loadConfig()
+})
+</script>
+
+<style scoped>
+.welcome-config-card {
+  max-width: 860px;
+}
+.welcome-config-form {
+  max-width: 700px;
+}
+.form-tip {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #909399;
+}
+.form-warning {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #e6a23c;
+}
+.form-unit {
+  margin-left: 8px;
+  color: #606266;
+}
+.action-bar {
+  margin-top: 30px;
+  display: flex;
+  gap: 12px;
+}
+</style>

+ 23 - 10
迎宾巡逻安防机器人运维端Web管理系统详细设计开发文档_V2.1.html

@@ -99,7 +99,7 @@
       </thead>
       <tbody>
         <tr><td>首页</td><td>首页总览</td><td>定制开发</td><td>否</td><td>首页涉及机器人实时状态、统计卡片、告警摘要、快捷操作入口,属于聚合看板页面,不能直接按单表 CRUD 生成。</td></tr>
-        <tr><td rowspan="7">内容管理</td><td>欢迎语配置</td><td>半定制开发</td><td>部分适合</td><td>可基于 robot_ops_welcome_config 生成基础表单,但页面更接近单配置页,需定制保存、恢复默认、测试播报等操作。</td></tr>
+        <tr><td rowspan="7">内容管理</td><td>欢迎语配置</td><td>定制开发</td><td>不建议生成前端列表页</td><td>欢迎语配置为单配置页,不是多条数据 CRUD 页面。数据库通过 config_key=default 定位默认配置;前端建议自定义表单页,直接加载和保存默认配置,不提供列表、新增、删除。</td></tr>
         <tr><td>问答库管理</td><td>RuoYi 主子表生成后定制</td><td>部分适合</td><td>可基于 robot_ops_faq、robot_ops_faq_similar 生成基础 CRUD;问题分类使用 RuoYi 字典 robot_faq_category,不单独生成问答分类管理页面;前端需将主子表明细表格调整为"相似问多行输入,一行一个"的交互方式;sortNo 作为保留字段,不在页面展示和编辑;启用/停用、导出、分类字典回显需按业务微调。一期暂不支持问答库导入,后续如运营需要批量维护再扩展。</td></tr>
         <tr><td>素材管理</td><td>RuoYi 生成后定制</td><td>部分适合</td><td>基础列表、查询、编辑可生成;上传、缩略图展示、图片/视频预览、引用保护需要定制。</td></tr>
         <tr><td>播放方案管理</td><td>定制开发</td><td>否</td><td>涉及主子表、素材选择、拖拽排序、播放时长、复制方案、预览方案等复杂交互,建议 Cursor 或开发人员手工实现。</td></tr>
@@ -136,7 +136,7 @@
     <h4>6.2.3 告警与快捷操作</h4><table><thead><tr><th>区块</th><th>内容</th></tr></thead><tbody><tr><td>最近系统异常</td><td>最近 5 条系统异常摘要,点击跳日志中心。</td></tr><tr><td>最近安防告警</td><td>最近 5 条安防告警摘要,点击跳安防告警日志。</td></tr><tr><td>最近升级失败</td><td>最近 5 条升级失败摘要,点击跳 OTA 升级页。</td></tr><tr><td>快捷按钮</td><td>查看摄像头、远程喊话、一键充电、停止充电、重启机器人、进入 OTA。</td></tr></tbody></table>
 
     <h3>6.3 内容管理</h3>
-    <h4>6.3.1 欢迎语配置页面</h4><table><thead><tr><th>字段/功能</th><th>类型</th><th>详细设计</th></tr></thead><tbody><tr><td>欢迎语文本(welcomeText)</td><td>textarea</td><td>欢迎语文本,最大 200 字。</td></tr><tr><td>是否启用语音播报(voiceEnabled)</td><td>switch</td><td>是否启用语音播报。</td></tr><tr><td>触发冷却时间(cooldownSeconds)</td><td>number</td><td>触发冷却时间,单位秒,默认 30。</td></tr><tr><td>启用状态(status)</td><td>switch</td><td>启用/停用。</td></tr><tr><td>保存</td><td>button</td><td>保存当前配置。</td></tr><tr><td>恢复默认</td><td>button</td><td>恢复系统默认欢迎语配置。</td></tr><tr><td>测试播报</td><td>button</td><td>下发测试播报指令。</td></tr></tbody></table>
+    <h4>6.3.1 欢迎语配置页面</h4><table><thead><tr><th>字段/功能</th><th>类型</th><th>详细设计</th></tr></thead><tbody><tr><td>页面目标</td><td>-</td><td>维护机器人默认欢迎语配置。该页面为单配置页,不提供列表、新增、删除;页面打开后直接加载 config_key=default 的默认配置,保存时更新该配置。</td></tr><tr><td>欢迎语文本(welcomeText)</td><td>textarea</td><td>欢迎语文本,最大 200 字。</td></tr><tr><td>语音播报(voiceEnabled)</td><td>switch</td><td>控制欢迎语触发时是否进行语音播报。启用后,机器人检测到访客时可语音播报欢迎语;关闭后,欢迎语可仅用于屏幕展示。</td></tr><tr><td>语音播报冷却时间(cooldownSeconds)</td><td>number</td><td>控制语音欢迎语的重复播报间隔,单位秒,默认 30。机器人语音播报欢迎语后,在冷却时间内再次检测到访客时,不重复语音播报,可仅进行屏幕展示。</td></tr><tr><td>启用欢迎语(status)</td><td>switch</td><td>控制欢迎语功能整体是否启用。停用后,机器人检测到访客时不触发欢迎语。</td></tr><tr><td>备注(remark)</td><td>input</td><td>备注说明。</td></tr><tr><td>保存</td><td>button</td><td>保存当前配置(更新 config_key=default 的配置)。</td></tr><tr><td>恢复默认</td><td>button</td><td>前端本地恢复系统默认欢迎语配置,不直接写入数据库;用户需再次点击“保存配置”后才更新 config_key=default 的配置。</td></tr><tr><td>测试播报</td><td>button</td><td>下发测试播报指令,用于验证当前欢迎语文本和语音播报配置。测试播报不新增或修改配置数据。</td></tr></tbody></table>
     <h4>6.3.2 问答库管理页面</h4>
 <table><thead><tr><th>模块</th><th>详细设计</th></tr></thead><tbody>
 <tr><td>页面目标</td><td>维护机器人 FAQ 问答内容,支持按问题分类管理标准问题、相似问和答案内容。页面基于 robot_ops_faq 主表和 robot_ops_faq_similar 相似问表实现,前端以一个"问答库管理"页面统一维护,不单独建设相似问管理菜单。</td></tr>
@@ -331,10 +331,14 @@
     <h3>7.4 内容管理接口</h3>
     <h4>7.4.1 欢迎语配置接口</h4>
     <table><thead><tr><th>接口</th><th>方法</th><th>说明</th><th>请求/返回字段</th><th>数据库表</th></tr></thead><tbody>
-      <tr><td>/robot-ops/content/welcome-config</td><td>GET</td><td>获取欢迎语配置</td><td>欢迎语文本(welcomeText)、是否启用语音播报(voiceEnabled)、触发冷却时间(cooldownSeconds)、启用状态(status)</td><td>robot_ops_welcome_config</td></tr>
-      <tr><td>/robot-ops/content/welcome-config</td><td>PUT</td><td>保存欢迎语配置</td><td>欢迎语文本(welcomeText)、是否启用语音播报(voiceEnabled)、触发冷却时间(cooldownSeconds)、启用状态(status)</td><td>robot_ops_welcome_config</td></tr>
-      <tr><td>/robot-ops/content/welcome-config/test</td><td>POST</td><td>测试欢迎语播报</td><td>欢迎语文本(welcomeText)</td><td>不新增业务表,可写入 robot_ops_operate_log</td></tr>
+      <tr><td>/robot-ops/content/welcome-config</td><td>GET</td><td>获取欢迎语配置</td><td>返回:配置标识(configKey,固定 default)、欢迎语文本(welcomeText)、语音播报开关(voiceEnabled)、语音播报冷却时间(cooldownSeconds)、启用欢迎语状态(status)、备注(remark)。前端无需传参,后端以 config_key='default' 查询;如数据库暂无默认配置,可返回空数据,由前端加载默认配置。</td><td>robot_ops_welcome_config</td></tr>
+      <tr><td>/robot-ops/content/welcome-config</td><td>PUT</td><td>保存欢迎语配置</td><td>请求:配置标识(configKey,固定 default,可由前端传入,也可由后端默认处理)、欢迎语文本(welcomeText)、语音播报开关(voiceEnabled)、语音播报冷却时间(cooldownSeconds)、启用欢迎语状态(status)、备注(remark)。后端保存时应以 config_key='default' 作为定位条件,存在则更新,不存在则新增。</td><td>robot_ops_welcome_config</td></tr>
+      <tr><td>/robot-ops/content/welcome-config/test</td><td>POST</td><td>测试欢迎语播报</td><td>请求:配置标识(configKey,固定 default)、欢迎语文本(welcomeText)、启用欢迎语状态(status)、语音播报开关(voiceEnabled)。仅下发测试播报,不新增或修改配置数据。</td><td>不新增业务表,可写入 robot_ops_operate_log</td></tr>
     </tbody></table>
+    <div class="note">欢迎语配置为单配置页,前端当前会在保存和测试播报时携带 configKey='default';后端也应支持不传 configKey 时默认按 default 处理。</div>
+    <div class="note">保存欢迎语配置时,后端建议按 config_key='default' 执行"有则更新,无则新增"的逻辑,避免初始化数据缺失时保存失败。</div>
+    <div class="note">恢复默认由前端本地重置表单完成,不单独调用后端恢复默认接口;用户点击“保存配置”后才真正写入数据库。</div>
+    <div class="note">status 控制欢迎语功能整体是否启用;voiceEnabled 控制欢迎语触发时是否进行语音播报;cooldownSeconds 表示语音播报冷却时间,用于避免短时间内重复语音播报。</div>
 
     <h4>7.4.2 问答库接口</h4>
     <table><thead><tr><th>接口</th><th>方法</th><th>说明</th><th>请求参数</th><th>返回/处理字段</th><th>数据库表</th></tr></thead><tbody>
@@ -555,17 +559,26 @@
     <h4>8.2.1 欢迎语配置表 robot_ops_welcome_config</h4>
     <div class="code">CREATE TABLE `robot_ops_welcome_config` (
   `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-  `welcome_text` VARCHAR(500) NOT NULL COMMENT '欢迎语文本',
+  `config_key` VARCHAR(50) NOT NULL DEFAULT 'default' COMMENT '配置标识,默认配置固定为default',
+  `welcome_text` VARCHAR(500) NOT NULL COMMENT '欢迎语文本,前端限制最大200字,数据库预留500字',
   `voice_enabled` CHAR(1) NOT NULL DEFAULT '1' COMMENT '是否启用语音播报:0否,1是',
-  `cooldown_seconds` INT DEFAULT 30 COMMENT '触发冷却时间,单位秒',
-  `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '启用状态:0停用,1启用',
+  `cooldown_seconds` INT NOT NULL DEFAULT 30 COMMENT '语音播报冷却时间,单位秒',
+  `status` CHAR(1) NOT NULL DEFAULT '1' COMMENT '启用欢迎语状态:0停用,1启用',
   `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
   `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人',
   `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人',
   `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  PRIMARY KEY (`id`)
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_robot_ops_welcome_config_key` (`config_key`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='欢迎语配置表';</div>
+    <div class="note">说明:欢迎语配置一期为单配置页,不作为多条数据列表管理。config_key 用于标识固定配置项,默认配置固定为 default;后端查询和保存时应以 config_key='default' 作为业务定位条件,避免因误插入多条数据导致无法判断当前配置。建议数据库初始化时预置一条 config_key='default' 的默认配置,同时保存接口应支持"有则更新,无则新增"。</div>
+    <div class="note">说明:welcome_text 数据库预留 500 字长度,前端页面按产品规则限制最大 200 字,便于后续扩展欢迎语内容。</div>
+    <div class="note">说明:status 控制欢迎语功能整体是否启用;voice_enabled 控制欢迎语触发后是否语音播报;cooldown_seconds 控制语音欢迎语的重复播报间隔。冷却期内再次检测到访客时,可仅做屏幕展示,不重复语音播报。</div>
+    <div class="code">INSERT INTO `robot_ops_welcome_config`
+(`config_key`, `welcome_text`, `voice_enabled`, `cooldown_seconds`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`)
+VALUES
+('default', '您好,欢迎光临!我是迎宾巡逻安防机器人,很高兴为您服务。', '1', 30, '1', '系统默认欢迎语配置', 'system', NOW(), 'system', NOW());</div>
 
     <h4>8.2.2 问答库表 robot_ops_faq</h4>
     <div class="code">CREATE TABLE `robot_ops_faq` (
@@ -583,7 +596,7 @@
   PRIMARY KEY (`id`),
   KEY `idx_robot_ops_faq_category_type` (`category_type`),
   KEY `idx_robot_ops_faq_status` (`status`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问答库主表';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问答库主表';</div>
     <div class="note">说明:sort_no 为保留字段,前端页面一期不展示、不编辑。新增问答默认写入 0,编辑问答时保持原值。后续如果需要推荐问答、置顶问答或机器人屏幕端展示排序,再重新设计排序和推荐能力。</div>
 
     <h4>8.2.3 相似问表 robot_ops_faq_similar</h4>