refactor: migrate styles to Tailwind and clean up components
- remove SCSS style files for ChatQuickAccess and ChatInputArea, replace with inline Tailwind utilities - uncomment ChatQuickAccess component in ChatMainList to re-enable the quick access bar - simplify speech recognition logic in ChatInputArea, update input placeholder text - replace direct uni.showToast calls with a shared helper function - remove unused keyboard height change listener and redundant keyboard hiding code
This commit is contained in:
@@ -1,58 +1,49 @@
|
||||
<template>
|
||||
<div class="input-area-wrapper" @touchend="handleVoiceTouchEndFromContainer"
|
||||
@touchcancel="handleVoiceTouchEndFromContainer">
|
||||
<div v-if="!visibleWaveBtn || speechProvider !== 'wechat'" class="area-input">
|
||||
<div v-if="!visibleWaveBtn || speechProvider !== 'wechat'"
|
||||
class="mx-[8px] mb-[6px] flex min-h-[42px] items-end rounded-[21px] bg-white px-[8px] py-[6px] shadow-[0_2px_12px_rgba(18,39,75,0.06)]">
|
||||
<!-- 语音/键盘切换 -->
|
||||
<div v-if="isSpeechRecognitionSupported" class="input-container-voice" @click="toggleVoiceMode">
|
||||
<img class="voice-icon" v-if="!isVoiceMode"
|
||||
<div v-if="isSpeechRecognitionSupported" class="flex h-[30px] w-[30px] shrink-0 items-center justify-center"
|
||||
@click="toggleVoiceMode">
|
||||
<img class="h-[22px] w-[22px]" v-if="!isVoiceMode"
|
||||
src="https://oss.nianxx.cn/mp/static/version_101/home/input_voice_icon.png" />
|
||||
<img class="voice-icon" v-else src="https://oss.nianxx.cn/mp/static/version_101/home/input_keyboard_icon.png" />
|
||||
<img class="h-[22px] w-[22px]" v-else
|
||||
src="https://oss.nianxx.cn/mp/static/version_101/home/input_keyboard_icon.png" />
|
||||
</div>
|
||||
|
||||
<!-- 输入框/语音按钮容器 -->
|
||||
<div class="input-button-container"
|
||||
:class="{ 'input-button-container--no-voice': !isSpeechRecognitionSupported }">
|
||||
<textarea ref="textareaRef" v-if="!isVoiceMode" class="textarea" type="text" cursor-spacing="20"
|
||||
confirm-type="send" v-model="inputMessage" auto-height :focus="isFocused" :confirm-hold="true"
|
||||
:placeholder="placeholder" :show-confirm-bar="false" :hold-keyboard="holdKeyboard" :adjust-position="true"
|
||||
:disable-default-padding="true" maxlength="300" @confirm="sendMessage" @focus="handleFocus" @blur="handleBlur"
|
||||
@touchstart="handleTouchStart" @touchend="handleTouchEnd" />
|
||||
<div class="min-w-0 flex-1" :class="{ 'pl-[4px]': !isSpeechRecognitionSupported }">
|
||||
<textarea ref="textareaRef" v-if="!isVoiceMode" v-model="inputMessage" rows="1" maxlength="300"
|
||||
:placeholder="placeholder"
|
||||
class="block h-[22px] max-h-[92px] min-h-[22px] w-full resize-none overflow-x-hidden overflow-y-auto border-0 bg-transparent p-0 text-[14px] leading-[22px] text-[#333] outline-none appearance-none placeholder:text-[#A5AAB4] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
@input="adjustTextareaHeight" @focus="handleFocus" @blur="handleBlur" @touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd" />
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<div v-if="isVoiceMode" class="hold-to-talk-button" @longpress="handleVoiceTouchStart"
|
||||
@touchend="handleVoiceTouchEnd" @touchcancel="handleVoiceTouchEnd">
|
||||
|
||||
<div v-if="isVoiceMode"
|
||||
class="flex h-[36px] w-full select-none items-center justify-center bg-transparent text-[14px] leading-[22px] text-[#333] active:bg-gray-100"
|
||||
@longpress="handleVoiceTouchStart" @touchend="handleVoiceTouchEnd" @touchcancel="handleVoiceTouchEnd">
|
||||
按住 说话
|
||||
</div>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<div v-if="isVoiceMode" class="hold-to-talk-button" @touchstart="handleVoiceTouchStart"
|
||||
@touchend="handleVoiceTouchEnd" @touchcancel="handleVoiceTouchEnd">
|
||||
<RecordingWaveBtn v-if="visibleWaveBtn" class="recording-wave-inline" ref="recordingWaveBtnRef" />
|
||||
<text v-else>按住 说话</text>
|
||||
</div>
|
||||
<!-- #endif -->
|
||||
</div>
|
||||
|
||||
<div class="input-container-send">
|
||||
<div class="input-container-send-btn" @click="sendMessage">
|
||||
<div v-if="isSessionActive" class="send-stop"> </div>
|
||||
<img v-else class="send-icon" src="https://oss.nianxx.cn/mp/static/version_101/home/input_send_icon.png" />
|
||||
<div class="flex h-[30px] w-[34px] shrink-0 items-center justify-end">
|
||||
<div
|
||||
class="flex h-[30px] w-[30px] items-center justify-center rounded-full [background:radial-gradient(39%_39%_at_97%_81%,#79dffb_0%,rgba(138,227,252,0)_100%),radial-gradient(54%_54%_at_3%_70%,#8afcf8_0%,rgba(138,252,248,0)_100%),#0CCD58] shadow-[0_4px_10px_rgba(12,205,88,0.22)]"
|
||||
@click="sendMessage">
|
||||
<div v-if="isSessionActive" class="h-[10px] w-[10px] rounded-[3px] bg-white"> </div>
|
||||
<img v-else class="h-[20px] w-[20px]"
|
||||
src="https://oss.nianxx.cn/mp/static/version_101/home/input_send_icon.png" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
|
||||
<!-- 录音按钮 -->
|
||||
<RecordingWaveBtn v-if="visibleWaveBtn" ref="recordingWaveBtnRef" />
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<yao-asdRealSpeech v-if="isSpeechRecognitionSupported && appSpeechVisible" :key="appSpeechKey" ref="appSpeechRef"
|
||||
:options="appSpeechOptions" @result="handleAppSpeechResult" @change="handleAppSpeechChange" />
|
||||
<!-- #endif -->
|
||||
|
||||
<div class="color-99A0AE font-size-9 text-center text-gray-400">
|
||||
<div class="text-center text-[9px] leading-[12px] text-[#A5AAB4]">
|
||||
内容由AI大模型生成,请仔细鉴别
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,24 +57,9 @@ import RecordingWaveBtn from "@/components/Speech/RecordingWaveBtn.vue";
|
||||
let manager = null;
|
||||
let speechProvider = "";
|
||||
const isSpeechRecognitionEnabled = ref(true);
|
||||
const isSpeechRecognitionSupported = ref(false);
|
||||
const isSpeechRecognitionSupported = ref(true);
|
||||
let appSpeechOptions = {};
|
||||
|
||||
|
||||
// WechatSI 是微信小程序插件,App 原生基座没有 requirePlugin。
|
||||
// #ifdef MP-WEIXIN
|
||||
const plugin = requirePlugin("WechatSI");
|
||||
manager = plugin.getRecordRecognitionManager();
|
||||
isSpeechRecognitionSupported.value = isSpeechRecognitionEnabled.value && !!manager;
|
||||
speechProvider = manager ? "wechat" : "";
|
||||
// #endif
|
||||
|
||||
// App 端使用 yao-asdRealSpeech;apikey 请在 src/constant/speech.js 中配置。
|
||||
// #ifdef APP-PLUS
|
||||
isSpeechRecognitionSupported.value = isSpeechRecognitionEnabled.value;
|
||||
speechProvider = "app";
|
||||
// #endif
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
holdKeyboard: Boolean,
|
||||
@@ -104,8 +80,9 @@ const recordingWaveBtnRef = ref(null);
|
||||
const appSpeechRef = ref(null);
|
||||
const appSpeechKey = ref(0);
|
||||
const appSpeechVisible = ref(true);
|
||||
const placeholder = ref('请输入');
|
||||
const placeholder = ref("快告诉小七您在想什么~");
|
||||
const inputMessage = ref(props.modelValue || "");
|
||||
const maxTextareaHeight = 92;
|
||||
const isFocused = ref(false);
|
||||
const keyboardHeight = ref(0);
|
||||
const isVoiceMode = ref(false);
|
||||
@@ -148,6 +125,14 @@ const startWatchDog = (timeout = 10000) => {
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
const adjustTextareaHeight = () => {
|
||||
const textarea = textareaRef.value;
|
||||
if (!textarea) return;
|
||||
|
||||
textarea.style.height = "22px";
|
||||
textarea.style.height = `${Math.min(textarea.scrollHeight, maxTextareaHeight)}px`;
|
||||
};
|
||||
|
||||
const clearAppStopFallback = () => {
|
||||
if (appStopFallbackTimer) {
|
||||
clearTimeout(appStopFallbackTimer);
|
||||
@@ -202,6 +187,7 @@ watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
inputMessage.value = val;
|
||||
nextTick(adjustTextareaHeight);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -211,6 +197,7 @@ watch(inputMessage, (val) => {
|
||||
if (val !== props.modelValue) {
|
||||
emit("update:modelValue", val);
|
||||
}
|
||||
nextTick(adjustTextareaHeight);
|
||||
});
|
||||
|
||||
// 切换语音/文本模式
|
||||
@@ -236,10 +223,7 @@ const handleVoiceTouchStart = () => {
|
||||
showRecordingUI();
|
||||
} else if (speechProvider === "app") {
|
||||
if (!appSpeechOptions.apikey) {
|
||||
uni.showToast({
|
||||
title: "请先配置语音识别API Key",
|
||||
icon: "none",
|
||||
});
|
||||
showToast("请先配置语音识别API Key");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -253,10 +237,7 @@ const handleVoiceTouchStart = () => {
|
||||
const appSpeech = appSpeechRef.value;
|
||||
if (!appSpeech || typeof appSpeech.start !== "function") {
|
||||
isAppSpeechStarting.value = false;
|
||||
uni.showToast({
|
||||
title: "语音组件未初始化",
|
||||
icon: "none",
|
||||
});
|
||||
showToast("语音组件未初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -488,17 +469,8 @@ const handleAppSpeechChange = (msg) => {
|
||||
|
||||
// 监听键盘高度变化
|
||||
onMounted(() => {
|
||||
// 监听键盘弹起
|
||||
uni.onKeyboardHeightChange((res) => {
|
||||
keyboardHeight.value = res.height;
|
||||
if (res.height) {
|
||||
emit("keyboardShow", res.height);
|
||||
} else {
|
||||
emit("keyboardHide");
|
||||
}
|
||||
});
|
||||
|
||||
initRecord();
|
||||
nextTick(adjustTextareaHeight);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -533,10 +505,6 @@ const hideKeyboardAfterSend = () => {
|
||||
if (textarea && typeof textarea.blur === "function") {
|
||||
textarea.blur();
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
uni.hideKeyboard();
|
||||
});
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
@@ -590,16 +558,8 @@ const blurInput = () => {
|
||||
if (textarea && typeof textarea.blur === "function") {
|
||||
textarea.blur();
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
uni.hideKeyboard();
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({ focusInput, blurInput });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
.area-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 24px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 0px 20px 0px rgba(52, 25, 204, 0.05);
|
||||
margin: 0 12px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.input-container-voice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-self: flex-end;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
.voice-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-button-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
&--no-voice {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.hold-to-talk-button {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.recording-wave-inline {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-bottom: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.input-container-send {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-self: flex-end;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
.input-container-send-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(
|
||||
39% 39% at 97% 81%,
|
||||
#79dffb 0%,
|
||||
rgba(138, 227, 252, 0) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
54% 54% at 3% 70%,
|
||||
#8afcf8 0%,
|
||||
rgba(138, 252, 248, 0) 100%
|
||||
),
|
||||
#0ccd58;
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.send-stop {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.textarea {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,12 +119,12 @@
|
||||
</div>
|
||||
|
||||
<!-- 输入框区域 -->
|
||||
<!-- <div>
|
||||
<div>
|
||||
<ChatQuickAccess />
|
||||
<ChatInputArea ref="inputAreaRef" v-model="inputMessage" :holdKeyboard="holdKeyboard"
|
||||
:is-session-active="isSessionActive" :stop-request="stopRequest" @send="sendMessageAction"
|
||||
@noHideKeyboard="handleNoHideKeyboard" @keyboardShow="handleKeyboardShow" @keyboardHide="handleKeyboardHide" />
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="quick-access flex flex-row ml-12 pt-8 pb-8 scroll-x whitespace-nowrap">
|
||||
<div class="item border-box rounded-50 flex flex-row items-center" v-for="(item, index) in itemList" :key="index"
|
||||
@click="sendReply(item)">
|
||||
<div
|
||||
class="flex flex-row gap-x-[9px] ml-[12px] py-[8px] overflow-x-auto whitespace-nowrap [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
|
||||
<div class="border border-white bg-white/50 px-3 py-1 rounded-[50px] flex flex-row items-center"
|
||||
v-for="(item, index) in itemList" :key="index" @click="sendReply(item)">
|
||||
<div class="flex items-center justify-center">
|
||||
<img v-if="item.icon" class="icon" :src="item.icon" />
|
||||
<span class="font-size-14 theme-color-500 line-height-20">
|
||||
<img v-if="item.icon" class="w-5 h-5 mr-[2px]" :src="item.icon" />
|
||||
<span class="text-[14px] text-[#2D91FF] leading-5">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -67,7 +68,3 @@ const sendReply = (item) => {
|
||||
uni.$emit(SEND_MESSAGE_COMMAND_TYPE, item);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.quick-access {
|
||||
gap: 0 9px;
|
||||
}
|
||||
|
||||
.item {
|
||||
border: 1px solid #fff;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
Reference in New Issue
Block a user