feat:输入框的调整
This commit is contained in:
@@ -1,62 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="input-area-wrapper">
|
<view class="input-area-wrapper">
|
||||||
<view v-if="!visibleWaveBtn" class="area-input">
|
<view v-if="!visibleWaveBtn" class="area-input">
|
||||||
<!-- 语音/键盘切换 -->
|
<!-- 语音/键盘切换 -->
|
||||||
<view class="input-container-voice" @click="toggleVoiceMode">
|
<view class="input-container-voice" @click="toggleVoiceMode">
|
||||||
<image v-if="!isVoiceMode" src="/static/input_voice_icon.png"></image>
|
<image
|
||||||
<image v-else src="/static/input_keyboard_icon.png"></image>
|
v-if="!isVoiceMode"
|
||||||
</view>
|
src="/static/input_voice_icon.png"
|
||||||
|
></image>
|
||||||
|
<image v-else src="/static/input_keyboard_icon.png"></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 输入框/语音按钮容器 -->
|
<!-- 输入框/语音按钮容器 -->
|
||||||
<view class="input-button-container">
|
<view class="input-button-container">
|
||||||
<textarea
|
<textarea
|
||||||
ref="textareaRef"
|
ref="textareaRef"
|
||||||
v-if="!isVoiceMode"
|
v-if="!isVoiceMode"
|
||||||
:class="['textarea', ios ? 'ios' : 'android']"
|
:class="['textarea', ios ? 'ios' : 'android']"
|
||||||
type="text"
|
type="text"
|
||||||
cursor-spacing="65"
|
cursor-spacing="65"
|
||||||
confirm-type="send"
|
confirm-type="send"
|
||||||
v-model="inputMessage"
|
v-model="inputMessage"
|
||||||
auto-height
|
auto-height
|
||||||
:confirm-hold="true"
|
:confirm-hold="true"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:show-confirm-bar="false"
|
:show-confirm-bar="false"
|
||||||
:hold-keyboard="holdKeyboard"
|
:hold-keyboard="holdKeyboard"
|
||||||
:adjust-position="true"
|
:adjust-position="true"
|
||||||
:disable-default-padding="true"
|
:disable-default-padding="true"
|
||||||
maxlength="300"
|
maxlength="300"
|
||||||
@confirm="sendMessage"
|
@confirm="sendMessage"
|
||||||
@focus="handleFocus"
|
@focus="handleFocus"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
@touchstart="handleTouchStart"
|
@touchstart="handleTouchStart"
|
||||||
@touchend="handleTouchEnd"
|
@touchend="handleTouchEnd"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<view
|
<view
|
||||||
v-if="isVoiceMode"
|
v-if="isVoiceMode"
|
||||||
class="hold-to-talk-button"
|
class="hold-to-talk-button"
|
||||||
@longpress="handleVoiceTouchStart"
|
@longpress="handleVoiceTouchStart"
|
||||||
@touchend="handleVoiceTouchEnd"
|
@touchend="handleVoiceTouchEnd"
|
||||||
@touchcancel="handleVoiceTouchEnd"
|
@touchcancel="handleVoiceTouchEnd"
|
||||||
>
|
>
|
||||||
按住 说话
|
按住 说话
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="input-container-send">
|
||||||
|
<view class="input-container-send-btn" @click="sendMessage">
|
||||||
|
<image
|
||||||
|
v-if="props.isSessionActive"
|
||||||
|
src="/static/input_stop_icon.png"
|
||||||
|
></image>
|
||||||
|
<image v-else src="/static/input_send_icon.png"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="input-container-send">
|
<!-- 录音按钮 -->
|
||||||
<view class="input-container-send-btn" @click="sendMessage">
|
<RecordingWaveBtn v-if="visibleWaveBtn" ref="recordingWaveBtnRef" />
|
||||||
<image
|
|
||||||
v-if="props.isSessionActive"
|
|
||||||
src="/static/input_stop_icon.png"
|
|
||||||
></image>
|
|
||||||
<image v-else src="/static/input_send_icon.png"></image>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 录音按钮 -->
|
|
||||||
<RecordingWaveBtn v-if="visibleWaveBtn" ref="recordingWaveBtnRef" />
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -67,18 +70,18 @@ const plugin = requirePlugin("WechatSI");
|
|||||||
const manager = plugin.getRecordRecognitionManager();
|
const manager = plugin.getRecordRecognitionManager();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: String,
|
modelValue: String,
|
||||||
holdKeyboard: Boolean,
|
holdKeyboard: Boolean,
|
||||||
isSessionActive: Boolean,
|
isSessionActive: Boolean,
|
||||||
stopRequest: Function,
|
stopRequest: Function,
|
||||||
});
|
});
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
"update:modelValue",
|
"update:modelValue",
|
||||||
"send",
|
"send",
|
||||||
"noHideKeyboard",
|
"noHideKeyboard",
|
||||||
"keyboardShow",
|
"keyboardShow",
|
||||||
"keyboardHide",
|
"keyboardHide",
|
||||||
"sendVoice",
|
"sendVoice",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const textareaRef = ref(null);
|
const textareaRef = ref(null);
|
||||||
@@ -92,142 +95,142 @@ const visibleWaveBtn = ref(false);
|
|||||||
|
|
||||||
// 判断当前平台是否为iOS
|
// 判断当前平台是否为iOS
|
||||||
const ios = computed(() => {
|
const ios = computed(() => {
|
||||||
return uni.getSystemInfoSync().platform === "ios";
|
return uni.getSystemInfoSync().platform === "ios";
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保持和父组件同步
|
// 保持和父组件同步
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(val) => {
|
(val) => {
|
||||||
inputMessage.value = val;
|
inputMessage.value = val;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 当子组件的 inputMessage 变化时,通知父组件(但要避免循环更新)
|
// 当子组件的 inputMessage 变化时,通知父组件(但要避免循环更新)
|
||||||
watch(inputMessage, (val) => {
|
watch(inputMessage, (val) => {
|
||||||
// 只有当值真正不同时才emit,避免循环更新
|
// 只有当值真正不同时才emit,避免循环更新
|
||||||
if (val !== props.modelValue) {
|
if (val !== props.modelValue) {
|
||||||
emit("update:modelValue", val);
|
emit("update:modelValue", val);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 切换语音/文本模式
|
// 切换语音/文本模式
|
||||||
const toggleVoiceMode = () => {
|
const toggleVoiceMode = () => {
|
||||||
isVoiceMode.value = !isVoiceMode.value;
|
isVoiceMode.value = !isVoiceMode.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理语音按钮长按开始
|
// 处理语音按钮长按开始
|
||||||
const handleVoiceTouchStart = () => {
|
const handleVoiceTouchStart = () => {
|
||||||
manager.start({ lang: "zh_CN" });
|
manager.start({ lang: "zh_CN" });
|
||||||
|
|
||||||
visibleWaveBtn.value = true;
|
visibleWaveBtn.value = true;
|
||||||
|
|
||||||
// 启动音频条动画
|
// 启动音频条动画
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (recordingWaveBtnRef.value) {
|
if (recordingWaveBtnRef.value) {
|
||||||
recordingWaveBtnRef.value.startAnimation();
|
recordingWaveBtnRef.value.startAnimation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理语音按钮长按结束
|
// 处理语音按钮长按结束
|
||||||
const handleVoiceTouchEnd = () => {
|
const handleVoiceTouchEnd = () => {
|
||||||
manager.stop();
|
manager.stop();
|
||||||
|
|
||||||
// 停止音频条动画
|
// 停止音频条动画
|
||||||
if (recordingWaveBtnRef.value) {
|
if (recordingWaveBtnRef.value) {
|
||||||
recordingWaveBtnRef.value.stopAnimation();
|
recordingWaveBtnRef.value.stopAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
visibleWaveBtn.value = false;
|
visibleWaveBtn.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理发送原语音
|
// 处理发送原语音
|
||||||
const initRecord = () => {
|
const initRecord = () => {
|
||||||
manager.onRecognize = (res) => {
|
manager.onRecognize = (res) => {
|
||||||
let text = res.result;
|
let text = res.result;
|
||||||
inputMessage.value = text;
|
inputMessage.value = text;
|
||||||
};
|
};
|
||||||
// 识别结束事件
|
// 识别结束事件
|
||||||
manager.onStop = (res) => {
|
manager.onStop = (res) => {
|
||||||
console.log(res, 37);
|
console.log(res, 37);
|
||||||
let text = res.result;
|
let text = res.result;
|
||||||
|
|
||||||
if (text == "") {
|
if (text == "") {
|
||||||
console.log("没有说话");
|
console.log("没有说话");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inputMessage.value = text;
|
inputMessage.value = text;
|
||||||
// 在语音识别完成后发送消息
|
// 在语音识别完成后发送消息
|
||||||
emit("send", text);
|
emit("send", text);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听键盘高度变化
|
// 监听键盘高度变化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 监听键盘弹起
|
// 监听键盘弹起
|
||||||
uni.onKeyboardHeightChange((res) => {
|
uni.onKeyboardHeightChange((res) => {
|
||||||
keyboardHeight.value = res.height;
|
keyboardHeight.value = res.height;
|
||||||
if (res.height) {
|
if (res.height) {
|
||||||
emit("keyboardShow", res.height);
|
emit("keyboardShow", res.height);
|
||||||
} else {
|
} else {
|
||||||
emit("keyboardHide");
|
emit("keyboardHide");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
initRecord();
|
initRecord();
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (props.isSessionActive) {
|
if (props.isSessionActive) {
|
||||||
// 如果会话进行中,调用停止请求函数
|
// 如果会话进行中,调用停止请求函数
|
||||||
if (props.stopRequest) {
|
if (props.stopRequest) {
|
||||||
props.stopRequest();
|
props.stopRequest();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 否则发送新消息
|
// 否则发送新消息
|
||||||
if (!inputMessage.value.trim()) return;
|
if (!inputMessage.value.trim()) return;
|
||||||
emit("send", inputMessage.value);
|
emit("send", inputMessage.value);
|
||||||
|
|
||||||
// 发送后保持焦点(可选)
|
// 发送后保持焦点(可选)
|
||||||
if (props.holdKeyboard && textareaRef.value) {
|
if (props.holdKeyboard && textareaRef.value) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
textareaRef.value.focus();
|
textareaRef.value.focus();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFocus = () => {
|
const handleFocus = () => {
|
||||||
isFocused.value = true;
|
isFocused.value = true;
|
||||||
emit("noHideKeyboard");
|
emit("noHideKeyboard");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
isFocused.value = false;
|
isFocused.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchStart = () => {
|
const handleTouchStart = () => {
|
||||||
emit("noHideKeyboard");
|
emit("noHideKeyboard");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchEnd = () => {
|
const handleTouchEnd = () => {
|
||||||
emit("noHideKeyboard");
|
emit("noHideKeyboard");
|
||||||
};
|
};
|
||||||
|
|
||||||
// 手动聚焦输入框
|
// 手动聚焦输入框
|
||||||
const focusInput = () => {
|
const focusInput = () => {
|
||||||
if (textareaRef.value) {
|
if (textareaRef.value) {
|
||||||
textareaRef.value.focus();
|
textareaRef.value.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 手动失焦输入框
|
// 手动失焦输入框
|
||||||
const blurInput = () => {
|
const blurInput = () => {
|
||||||
if (textareaRef.value) {
|
if (textareaRef.value) {
|
||||||
textareaRef.value.blur();
|
textareaRef.value.blur();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
@@ -236,92 +239,87 @@ defineExpose({ focusInput });
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.area-input {
|
.area-input {
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
border-radius: 22px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
box-shadow: 0px 0px 20px 0px rgba(52, 25, 204, 0.05);
|
|
||||||
margin: 0 12px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
.input-container-voice {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
border-radius: 22px;
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
|
|
||||||
image {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-button-container {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hold-to-talk-button {
|
|
||||||
width: 100%;
|
|
||||||
height: 44px;
|
|
||||||
color: #333333;
|
|
||||||
font-size: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
transition: all 0.2s ease;
|
box-shadow: 0px 0px 20px 0px rgba(52, 25, 204, 0.05);
|
||||||
user-select: none;
|
margin: 0 12px;
|
||||||
-webkit-user-select: none;
|
margin-bottom: 8px;
|
||||||
}
|
|
||||||
|
|
||||||
.input-container-send {
|
.input-container-voice {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 44px;
|
align-self: flex-end;
|
||||||
height: 44px;
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
|
||||||
.input-container-send-btn {
|
image {
|
||||||
display: flex;
|
width: 22px;
|
||||||
align-items: center;
|
height: 22px;
|
||||||
justify-content: center;
|
}
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
image {
|
.input-button-container {
|
||||||
width: 28px;
|
flex: 1;
|
||||||
height: 28px;
|
position: relative;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea {
|
|
||||||
flex: 1;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 92px;
|
|
||||||
min-height: 44px;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: normal;
|
|
||||||
|
|
||||||
&.android {
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ios {
|
.hold-to-talk-button {
|
||||||
padding: 6px 0;
|
width: 100%;
|
||||||
|
height: 44px;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #ffffff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
.input-container-send {
|
||||||
color: #cccccc;
|
display: flex;
|
||||||
line-height: normal;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-self: flex-end;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
|
||||||
|
.input-container-send-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
.textarea {
|
||||||
outline: none;
|
flex: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 92px;
|
||||||
|
min-height: 22px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
margin: 6px 0;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #cccccc;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user