add:语音控制功能-创建前端组件
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
package org.nl.wms.system_manage.controller.speech;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Zhao Ya Fei
|
||||||
|
* @date 2026-04-14
|
||||||
|
* 根据语音输入执行任务
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/speech")
|
||||||
|
public class SysSpeechController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 健康检查
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/health")
|
||||||
|
public ResponseEntity<Object> health(){
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音转文字
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/asr")
|
||||||
|
public ResponseEntity<Object> asr(){
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字转语音
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/tts")
|
||||||
|
public ResponseEntity<Object> tts(){
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/confirm")
|
||||||
|
public ResponseEntity<Object> confirm(){
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.nl.wms.system_manage.service.speech;
|
||||||
|
|
||||||
|
public interface SysSpeechService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音识别服务健康检查
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
Boolean health();
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.nl.wms.system_manage.service.speech.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Zhao Ya Fei
|
||||||
|
* @date 2026-04-14
|
||||||
|
* @desc 语音识别URL
|
||||||
|
*/
|
||||||
|
public class SpeechUrlConstant {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.nl.wms.system_manage.service.speech.impl;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.nl.wms.system_manage.service.speech.SysSpeechService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Zhao Ya Fei
|
||||||
|
* @date 2026-04-14
|
||||||
|
* 语音识别服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class SysSpeechServiceImpl implements SysSpeechService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean health() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
531
nladmin-ui/src/components/VoiceInputDialog/index.vue
Normal file
531
nladmin-ui/src/components/VoiceInputDialog/index.vue
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:visible.sync="visible"
|
||||||
|
:show-close="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="400px"
|
||||||
|
custom-class="voice-input-dialog"
|
||||||
|
:modal="false"
|
||||||
|
:before-close="handleClose"
|
||||||
|
>
|
||||||
|
<div class="voice-input-container">
|
||||||
|
<!-- 关闭按钮 -->
|
||||||
|
<div class="close-btn" @click="handleClose">
|
||||||
|
<i class="el-icon-close" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<div class="dialog-header">
|
||||||
|
<span class="title">语音输入</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 录音按钮区域 - 只在未识别成功时显示 -->
|
||||||
|
<div v-if="!showTranscript" class="record-section">
|
||||||
|
<div
|
||||||
|
class="record-btn"
|
||||||
|
:class="{ 'recording': isRecording, 'disabled': isTranscribing }"
|
||||||
|
:style="{ 'pointer-events': isTranscribing ? 'none' : 'auto' }"
|
||||||
|
@mousedown="startRecording"
|
||||||
|
@mouseup="stopRecording"
|
||||||
|
@mouseleave="isRecording && stopRecording()"
|
||||||
|
@touchstart.prevent="startRecording"
|
||||||
|
@touchend.prevent="stopRecording"
|
||||||
|
>
|
||||||
|
<i :class="isRecording ? 'el-icon-microphone recording-icon' : 'el-icon-microphone'" />
|
||||||
|
<span class="record-text">{{ isRecording ? '录音中...' : '按住说话' }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="recording-tip" :class="{ 'visible': isRecording }">松开后结束录音</p>
|
||||||
|
<!-- 识别中提示 - 固定位置,用opacity控制显示,不占位变化 -->
|
||||||
|
<div class="transcribing-status" :class="{ 'visible': isTranscribing }">
|
||||||
|
<i class="el-icon-loading" /> 正在识别中...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 语音转文字结果区域 - 识别成功后才显示 -->
|
||||||
|
<div v-if="showTranscript" class="transcript-section">
|
||||||
|
<el-input
|
||||||
|
v-model="transcript"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="语音转文字结果..."
|
||||||
|
/>
|
||||||
|
<div class="transcript-actions">
|
||||||
|
<el-button type="primary" size="small" @click="confirmInput">确认</el-button>
|
||||||
|
<el-button size="small" @click="reRecognize">重新识别</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 处理结果展示区域 - 只在确认后显示 -->
|
||||||
|
<div v-if="isConfirmed && result" class="result-section">
|
||||||
|
<div class="result-header">
|
||||||
|
<i class="el-icon-success" />
|
||||||
|
<span>处理结果</span>
|
||||||
|
</div>
|
||||||
|
<div class="result-content">
|
||||||
|
{{ result }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VoiceInputDialog',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
isRecording: false,
|
||||||
|
isTranscribing: false,
|
||||||
|
showTranscript: false, // 是否显示识别结果文本框
|
||||||
|
transcript: '',
|
||||||
|
result: '',
|
||||||
|
isConfirmed: false,
|
||||||
|
recognition: null,
|
||||||
|
recordStartTime: 0,
|
||||||
|
minRecordTime: 500, // 最小录音时间500ms
|
||||||
|
isStopping: false // 防止重复停止
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initSpeechRecognition()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopRecording()
|
||||||
|
if (this.recognition) {
|
||||||
|
try {
|
||||||
|
this.recognition.stop()
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化语音识别
|
||||||
|
initSpeechRecognition() {
|
||||||
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
|
||||||
|
if (SpeechRecognition) {
|
||||||
|
this.recognition = new SpeechRecognition()
|
||||||
|
this.recognition.continuous = false
|
||||||
|
this.recognition.interimResults = true
|
||||||
|
this.recognition.lang = 'zh-CN'
|
||||||
|
|
||||||
|
this.recognition.onresult = (event) => {
|
||||||
|
let finalTranscript = ''
|
||||||
|
let interimTranscript = ''
|
||||||
|
|
||||||
|
for (let i = event.resultIndex; i < event.results.length; i++) {
|
||||||
|
const transcript = event.results[i][0].transcript
|
||||||
|
if (event.results[i].isFinal) {
|
||||||
|
finalTranscript += transcript
|
||||||
|
} else {
|
||||||
|
interimTranscript += transcript
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalTranscript) {
|
||||||
|
this.transcript = finalTranscript
|
||||||
|
this.isTranscribing = false
|
||||||
|
// 识别成功后显示文本框
|
||||||
|
this.showTranscript = true
|
||||||
|
} else if (interimTranscript) {
|
||||||
|
this.transcript = interimTranscript
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recognition.onerror = (event) => {
|
||||||
|
console.error('语音识别错误:', event.error)
|
||||||
|
this.isTranscribing = false
|
||||||
|
this.isRecording = false
|
||||||
|
if (event.error !== 'aborted') {
|
||||||
|
this.$message.error('语音识别失败,请重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recognition.onend = () => {
|
||||||
|
// 如果识别结束但没有结果
|
||||||
|
if (this.isTranscribing && !this.transcript) {
|
||||||
|
this.isTranscribing = false
|
||||||
|
this.$message.warning('未能识别到语音,请重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查浏览器是否支持语音识别
|
||||||
|
checkBrowserSupport() {
|
||||||
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
|
||||||
|
if (!SpeechRecognition) {
|
||||||
|
return { supported: false, message: '您的浏览器不支持语音识别功能,请使用Chrome、Edge或Safari浏览器' }
|
||||||
|
}
|
||||||
|
return { supported: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
// 请求麦克风权限
|
||||||
|
async requestMicrophonePermission() {
|
||||||
|
try {
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||||
|
stream.getTracks().forEach(track => track.stop())
|
||||||
|
return { granted: true }
|
||||||
|
} catch (err) {
|
||||||
|
console.error('麦克风权限请求失败:', err)
|
||||||
|
let message = '无法获取麦克风权限'
|
||||||
|
if (err.name === 'NotAllowedError') {
|
||||||
|
message = '麦克风权限被拒绝,请在浏览器设置中允许使用麦克风'
|
||||||
|
} else if (err.name === 'NotFoundError') {
|
||||||
|
message = '未检测到麦克风设备'
|
||||||
|
} else if (err.name === 'NotSupportedError') {
|
||||||
|
message = '当前环境不支持录音功能'
|
||||||
|
}
|
||||||
|
return { granted: false, message }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示对话框(带权限检查)
|
||||||
|
async show() {
|
||||||
|
const supportCheck = this.checkBrowserSupport()
|
||||||
|
if (!supportCheck.supported) {
|
||||||
|
this.$message.warning(supportCheck.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionCheck = await this.requestMicrophonePermission()
|
||||||
|
if (!permissionCheck.granted) {
|
||||||
|
this.$message.warning(permissionCheck.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visible = true
|
||||||
|
this.reset()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 开始录音
|
||||||
|
startRecording() {
|
||||||
|
if (this.isTranscribing) {
|
||||||
|
this.$message.warning('正在识别中,请稍候...')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.recognition) {
|
||||||
|
this.$message.warning('您的浏览器不支持语音识别功能')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isStopping) return
|
||||||
|
|
||||||
|
this.recordStartTime = Date.now()
|
||||||
|
this.isRecording = true
|
||||||
|
this.transcript = ''
|
||||||
|
this.result = ''
|
||||||
|
this.isConfirmed = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.recognition.start()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('启动语音识别失败:', e)
|
||||||
|
this.isRecording = false
|
||||||
|
this.recordStartTime = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 停止录音
|
||||||
|
stopRecording() {
|
||||||
|
if (!this.isRecording) return
|
||||||
|
|
||||||
|
const recordDuration = Date.now() - this.recordStartTime
|
||||||
|
|
||||||
|
if (recordDuration < this.minRecordTime) {
|
||||||
|
this.isRecording = false
|
||||||
|
this.recordStartTime = 0
|
||||||
|
if (this.recognition) {
|
||||||
|
try {
|
||||||
|
this.recognition.abort()
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$message.warning('录音时间太短,请长按说话')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isRecording = false
|
||||||
|
this.isTranscribing = true
|
||||||
|
|
||||||
|
if (this.recognition) {
|
||||||
|
try {
|
||||||
|
this.recognition.stop()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('停止语音识别失败:', e)
|
||||||
|
this.isTranscribing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recordStartTime = 0
|
||||||
|
},
|
||||||
|
|
||||||
|
// 确认输入
|
||||||
|
confirmInput() {
|
||||||
|
if (!this.transcript.trim()) {
|
||||||
|
this.$message.warning('请先输入或录制语音')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isConfirmed = true
|
||||||
|
this.processVoiceInput(this.transcript)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重新识别 - 恢复初始状态
|
||||||
|
reRecognize() {
|
||||||
|
this.showTranscript = false
|
||||||
|
this.transcript = ''
|
||||||
|
this.result = ''
|
||||||
|
this.isTranscribing = false
|
||||||
|
this.isConfirmed = false
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理语音输入
|
||||||
|
processVoiceInput(text) {
|
||||||
|
this.result = `已处理: "${text}"`
|
||||||
|
},
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
handleClose() {
|
||||||
|
this.stopRecording()
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
reset() {
|
||||||
|
this.isRecording = false
|
||||||
|
this.isTranscribing = false
|
||||||
|
this.showTranscript = false
|
||||||
|
this.transcript = ''
|
||||||
|
this.result = ''
|
||||||
|
this.isConfirmed = false
|
||||||
|
this.recordStartTime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.voice-input-container {
|
||||||
|
padding: 10px 20px 20px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
min-height: 160px;
|
||||||
|
|
||||||
|
.record-btn {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.recording {
|
||||||
|
background: linear-gradient(135deg, #F56C6C 0%, #f78989 100%);
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
|
||||||
|
animation: pulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: linear-gradient(135deg, #909399 0%, #a6a9ad 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
&.recording-icon {
|
||||||
|
animation: shake 0.5s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-tip {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #F56C6C;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transcribing-status {
|
||||||
|
margin-top: 15px;
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 20px; /* 固定高度 */
|
||||||
|
line-height: 20px;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transcript-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.transcript-actions {
|
||||||
|
margin-top: 15px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-section {
|
||||||
|
background: #f0f9eb;
|
||||||
|
border: 1px solid #c2e7b0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
.result-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #67C23A;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-content {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0.4);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 20px rgba(245, 108, 108, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: rotate(0deg); }
|
||||||
|
25% { transform: rotate(-5deg); }
|
||||||
|
75% { transform: rotate(5deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.voice-input-dialog {
|
||||||
|
position: fixed !important;
|
||||||
|
right: 20px !important;
|
||||||
|
bottom: 20px !important;
|
||||||
|
top: auto !important;
|
||||||
|
left: auto !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
.el-dialog__header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -79,6 +79,7 @@ export default {
|
|||||||
'moreMenu': 'MoreMenu',
|
'moreMenu': 'MoreMenu',
|
||||||
'browses': 'browse',
|
'browses': 'browse',
|
||||||
'fz': 'Full screen zoom',
|
'fz': 'Full screen zoom',
|
||||||
|
'speech': 'Voice Control',
|
||||||
'submit': 'Submit Success',
|
'submit': 'Submit Success',
|
||||||
'add': 'Add Success',
|
'add': 'Add Success',
|
||||||
'edit': 'Edit Success',
|
'edit': 'Edit Success',
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export default {
|
|||||||
'moreMenu': '更多菜单',
|
'moreMenu': '更多菜单',
|
||||||
'browses': '浏览',
|
'browses': '浏览',
|
||||||
'fz': '全屏缩放',
|
'fz': '全屏缩放',
|
||||||
|
'speech': '语音控制',
|
||||||
'submit': '提交成功',
|
'submit': '提交成功',
|
||||||
'add': '新增成功',
|
'add': '新增成功',
|
||||||
'edit': '编辑成功',
|
'edit': '编辑成功',
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
|
|
||||||
<div class="right-menu">
|
<div class="right-menu">
|
||||||
<template v-if="device!=='mobile'">
|
<template v-if="device!=='mobile'">
|
||||||
|
<!-- 语音输入按钮 -->
|
||||||
|
<el-tooltip :content="$t('common.speech')" effect="dark" placement="bottom">
|
||||||
|
<div class="right-menu-item hover-effect voice-btn" @click="openVoiceInput">
|
||||||
|
<i class="el-icon-microphone" />
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
<search id="header-search" class="right-menu-item" />
|
<search id="header-search" class="right-menu-item" />
|
||||||
|
|
||||||
<!-- <el-tooltip content="项目文档" effect="dark" placement="bottom">
|
<!-- <el-tooltip content="项目文档" effect="dark" placement="bottom">
|
||||||
@@ -17,6 +24,9 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<notice-icon class="right-menu-item" />
|
<notice-icon class="right-menu-item" />
|
||||||
<notice-icon-reader ref="noticeIconReader" />
|
<notice-icon-reader ref="noticeIconReader" />
|
||||||
|
<voice-input-dialog ref="voiceInputDialog" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <el-tooltip content="布局设置" effect="dark" placement="bottom">
|
<!-- <el-tooltip content="布局设置" effect="dark" placement="bottom">
|
||||||
<size-select id="size-select" class="right-menu-item hover-effect" />
|
<size-select id="size-select" class="right-menu-item hover-effect" />
|
||||||
@@ -73,6 +83,7 @@ import Search from '@/components/HeaderSearch'
|
|||||||
import Avatar from '@/assets/images/avatar.png'
|
import Avatar from '@/assets/images/avatar.png'
|
||||||
import NoticeIcon from '@/views/system/notice/NoticeIcon.vue'
|
import NoticeIcon from '@/views/system/notice/NoticeIcon.vue'
|
||||||
import NoticeIconReader from '@/views/system/notice/NoticeIconReader.vue'
|
import NoticeIconReader from '@/views/system/notice/NoticeIconReader.vue'
|
||||||
|
import VoiceInputDialog from '@/components/VoiceInputDialog'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -84,7 +95,8 @@ export default {
|
|||||||
SizeSelect,
|
SizeSelect,
|
||||||
Search,
|
Search,
|
||||||
Doc,
|
Doc,
|
||||||
TopNav
|
TopNav,
|
||||||
|
VoiceInputDialog
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -162,6 +174,9 @@ export default {
|
|||||||
this.$store.dispatch('LogOut').then(() => {
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
openVoiceInput() {
|
||||||
|
this.$refs.voiceInputDialog && this.$refs.voiceInputDialog.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user