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:
duanshuwen
2026-05-28 23:29:26 +08:00
parent 35b4eb3cca
commit 5296fbccbc
5 changed files with 50 additions and 223 deletions

View File

@@ -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-asdRealSpeechapikey 请在 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>

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}