Compare commits
3 Commits
9cbe454338
...
b68e0f2a6e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b68e0f2a6e | ||
|
|
76b2187a64 | ||
|
|
027b6555dd |
@ -315,8 +315,8 @@ async function getTodo() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
todo.value = res.data && typeof res.data === 'object' ? res.data : null;
|
todo.value = res.data && typeof res.data === 'object' ? res.data : null;
|
||||||
if (todo.value.customerId && !customer.value) {
|
if (todo.value.customerId) {
|
||||||
getCustomer(todo.value.customerId)
|
await getCustomer(todo.value.customerId)
|
||||||
}
|
}
|
||||||
await loadUserNameMap(String(todo.value?.executeTeamId || ''));
|
await loadUserNameMap(String(todo.value?.executeTeamId || ''));
|
||||||
if (todo.value && todo.value.creatorUserId && !userNameMap.value[todo.value.creatorUserId]) {
|
if (todo.value && todo.value.creatorUserId && !userNameMap.value[todo.value.creatorUserId]) {
|
||||||
@ -342,6 +342,9 @@ async function getCustomer(_id) {
|
|||||||
const [data] = res && Array.isArray(res.data) ? res.data : [];
|
const [data] = res && Array.isArray(res.data) ? res.data : [];
|
||||||
if (data) {
|
if (data) {
|
||||||
customer.value = data
|
customer.value = data
|
||||||
|
} else {
|
||||||
|
toast('获取客户信息失败')
|
||||||
|
uni.navigateBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<full-page :customScroll="true" pageStyle="background:#fff">
|
<full-page pageStyle="background:#fff">
|
||||||
<view v-if="loading" class="loading-container">
|
<view v-if="loading" class="loading-container">
|
||||||
<uni-icons type="spinner-cycle" size="40" color="#999" />
|
<uni-icons type="spinner-cycle" size="40" color="#999" />
|
||||||
<text class="loading-text">加载中...</text>
|
<text class="loading-text">加载中...</text>
|
||||||
|
|||||||
@ -401,11 +401,26 @@ $primary-color: #0877F1;
|
|||||||
|
|
||||||
.input-toolbar {
|
.input-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
|
position: relative;
|
||||||
padding: 16rpx 20rpx;
|
padding: 16rpx 20rpx;
|
||||||
gap: 12rpx;
|
gap: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expand-input-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 16rpx;
|
||||||
|
left: 20rpx;
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.voice-toggle-btn {
|
.voice-toggle-btn {
|
||||||
width: 56rpx;
|
width: 56rpx;
|
||||||
height: 56rpx;
|
height: 56rpx;
|
||||||
@ -414,6 +429,7 @@ $primary-color: #0877F1;
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-toggle-icon {
|
.voice-toggle-icon {
|
||||||
@ -432,6 +448,7 @@ $primary-color: #0877F1;
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.send-btn {
|
.send-btn {
|
||||||
@ -451,6 +468,7 @@ $primary-color: #0877F1;
|
|||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.send-btn:active {
|
.send-btn:active {
|
||||||
@ -509,6 +527,7 @@ $primary-color: #0877F1;
|
|||||||
display: block;
|
display: block;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
height: 80rpx;
|
height: 80rpx;
|
||||||
|
max-height: 500rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-input-btn {
|
.voice-input-btn {
|
||||||
@ -521,6 +540,46 @@ $primary-color: #0877F1;
|
|||||||
line-height: 80rpx;
|
line-height: 80rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expanded-input-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded-input-header {
|
||||||
|
height: 96rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 24rpx 24rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-input-btn {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded-text-input {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 32rpx 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 30rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #333;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.more-panel {
|
.more-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="input-toolbar">
|
<view class="input-toolbar">
|
||||||
|
<view v-if="showExpandButton" class="expand-input-btn" @click="expandInput">
|
||||||
|
<image src="/static/expand.svg" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
<view @click="toggleVoiceInput" class="voice-toggle-btn">
|
<view @click="toggleVoiceInput" class="voice-toggle-btn">
|
||||||
<image v-if="showVoiceInput" src="/static/jianpan.png" class="voice-toggle-icon" mode="aspectFit"></image>
|
<image v-if="showVoiceInput" src="/static/jianpan.png" class="voice-toggle-icon" mode="aspectFit"></image>
|
||||||
<uni-icons v-else type="mic" size="28" color="#666" />
|
<uni-icons v-else type="mic" size="28" color="#666" />
|
||||||
@ -14,7 +17,8 @@
|
|||||||
<!-- :hold-keyboard="true" -->
|
<!-- :hold-keyboard="true" -->
|
||||||
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
|
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
|
||||||
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput" :auto-height="true"
|
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput" :auto-height="true"
|
||||||
:show-confirm-bar="false" :maxlength="-1" :cursor-spacing="40" ref="textareaRef" />
|
:show-confirm-bar="false" :maxlength="-1" :cursor-spacing="40" :focus="normalTextareaFocus"
|
||||||
|
:cursor="inputCursor" ref="textareaRef" />
|
||||||
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
|
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
|
||||||
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord"
|
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord"
|
||||||
:placeholder="isRecording ? '松开发送' : '按住说话'" disabled />
|
:placeholder="isRecording ? '松开发送' : '按住说话'" disabled />
|
||||||
@ -29,6 +33,17 @@
|
|||||||
<text>生成中...</text>
|
<text>生成中...</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="isInputExpanded" class="expanded-input-overlay">
|
||||||
|
<view class="expanded-input-header">
|
||||||
|
<view class="collapse-input-btn" @click="collapseInput">
|
||||||
|
<image src="/static/mini.svg" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<textarea class="expanded-text-input" v-model="inputText" placeholder="我来说两句..."
|
||||||
|
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput" :auto-height="false"
|
||||||
|
:show-confirm-bar="false" :maxlength="-1" :cursor-spacing="40" :focus="expandedTextareaFocus"
|
||||||
|
:cursor="inputCursor" />
|
||||||
|
</view>
|
||||||
<view class="more-panel" v-if="showMorePanel">
|
<view class="more-panel" v-if="showMorePanel">
|
||||||
<view v-for="btn in morePanelButtons" :key="btn.text" class="more-btn" @click="btn.action">
|
<view v-for="btn in morePanelButtons" :key="btn.text" class="more-btn" @click="btn.action">
|
||||||
<image :src="btn.icon" class="more-icon" mode="aspectFit"></image>
|
<image :src="btn.icon" class="more-icon" mode="aspectFit"></image>
|
||||||
@ -62,7 +77,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, onMounted, onUnmounted, nextTick } from "vue";
|
import { computed, getCurrentInstance, ref, onMounted, onUnmounted, nextTick, watch } from "vue";
|
||||||
import {
|
import {
|
||||||
chooseImage,
|
chooseImage,
|
||||||
takePhoto as takePhotoUtil,
|
takePhoto as takePhotoUtil,
|
||||||
@ -108,6 +123,15 @@ const showMorePanel = ref(false);
|
|||||||
const isRecording = ref(false);
|
const isRecording = ref(false);
|
||||||
const recordingText = ref("录音中...");
|
const recordingText = ref("录音中...");
|
||||||
const textareaRef = ref(null);
|
const textareaRef = ref(null);
|
||||||
|
const isInputExpanded = ref(false);
|
||||||
|
const isInputOverFourLines = ref(false);
|
||||||
|
const singleLineInputHeight = ref(0);
|
||||||
|
const normalTextareaFocus = ref(false);
|
||||||
|
const expandedTextareaFocus = ref(false);
|
||||||
|
const inputCursor = ref(-1);
|
||||||
|
let lastInputLength = 0;
|
||||||
|
const instanceProxy = getCurrentInstance()?.proxy;
|
||||||
|
const showExpandButton = computed(() => !showVoiceInput.value && isInputOverFourLines.value && !isInputExpanded.value);
|
||||||
const cloudCustomData = computed(() => {
|
const cloudCustomData = computed(() => {
|
||||||
const arr = [
|
const arr = [
|
||||||
props.chatRoomBusiness.businessType,
|
props.chatRoomBusiness.businessType,
|
||||||
@ -121,6 +145,58 @@ const appendStreamText = (char) => {
|
|||||||
inputText.value += char;
|
inputText.value += char;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateInputLines = async () => {
|
||||||
|
await nextTick();
|
||||||
|
if (showVoiceInput.value || isInputExpanded.value) return;
|
||||||
|
|
||||||
|
const hasExplicitLineBreak = /[\r\n]/.test(inputText.value);
|
||||||
|
if (hasExplicitLineBreak) {
|
||||||
|
const inputLineCount = inputText.value.split(/\r\n|\r|\n/).length;
|
||||||
|
isInputOverFourLines.value = inputLineCount > 4;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputText.value.length <= 1 && singleLineInputHeight.value) {
|
||||||
|
isInputOverFourLines.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = instanceProxy ? uni.createSelectorQuery().in(instanceProxy) : uni.createSelectorQuery();
|
||||||
|
query.select(".text-input").boundingClientRect();
|
||||||
|
query.exec((res) => {
|
||||||
|
const inputRect = res && res[0];
|
||||||
|
if (!inputRect) return;
|
||||||
|
|
||||||
|
if (!singleLineInputHeight.value) singleLineInputHeight.value = inputRect.height;
|
||||||
|
|
||||||
|
const lineHeight = (45 * uni.getSystemInfoSync().windowWidth) / 750;
|
||||||
|
const fourLineHeight = singleLineInputHeight.value + lineHeight * 3;
|
||||||
|
isInputOverFourLines.value = inputRect.height > fourLineHeight + 1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandInput = () => {
|
||||||
|
normalTextareaFocus.value = false;
|
||||||
|
isInputExpanded.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
expandedTextareaFocus.value = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const collapseInput = () => {
|
||||||
|
expandedTextareaFocus.value = false;
|
||||||
|
isInputExpanded.value = false;
|
||||||
|
nextTick(() => {
|
||||||
|
normalTextareaFocus.value = true;
|
||||||
|
updateInputLines();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(inputText, (value) => {
|
||||||
|
lastInputLength = value.length;
|
||||||
|
updateInputLines();
|
||||||
|
});
|
||||||
|
|
||||||
// 录音相关扩展状态(特效 + 取消逻辑)
|
// 录音相关扩展状态(特效 + 取消逻辑)
|
||||||
const recordingDuration = ref(0);
|
const recordingDuration = ref(0);
|
||||||
let recordingTimer = null;
|
let recordingTimer = null;
|
||||||
@ -329,11 +405,14 @@ const sendCustomMessage = async (messageData) => {
|
|||||||
const toggleVoiceInput = () => {
|
const toggleVoiceInput = () => {
|
||||||
showVoiceInput.value = !showVoiceInput.value;
|
showVoiceInput.value = !showVoiceInput.value;
|
||||||
showMorePanel.value = false;
|
showMorePanel.value = false;
|
||||||
|
if (!showVoiceInput.value) updateInputLines();
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleMorePanel = () => {
|
const toggleMorePanel = () => {
|
||||||
showMorePanel.value = !showMorePanel.value;
|
showMorePanel.value = !showMorePanel.value;
|
||||||
|
const wasVoiceInput = showVoiceInput.value;
|
||||||
showVoiceInput.value = false;
|
showVoiceInput.value = false;
|
||||||
|
if (wasVoiceInput) updateInputLines();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理图片选择
|
// 处理图片选择
|
||||||
@ -590,7 +669,19 @@ function handleInputFocus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleInput(e) {
|
function handleInput(e) {
|
||||||
// textarea 输入时触发,可以在这里处理额外逻辑
|
const value = e?.detail?.value || "";
|
||||||
|
const cursor = e?.detail?.cursor;
|
||||||
|
const isPastedText = value.length > lastInputLength + 1;
|
||||||
|
|
||||||
|
if (isPastedText && Number.isInteger(cursor)) {
|
||||||
|
nextTick(() => {
|
||||||
|
inputCursor.value = -1;
|
||||||
|
nextTick(() => {
|
||||||
|
inputCursor.value = cursor;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
emit("scrollToBottom");
|
emit("scrollToBottom");
|
||||||
});
|
});
|
||||||
@ -599,6 +690,7 @@ function handleInput(e) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 初始化录音管理器
|
// 初始化录音管理器
|
||||||
initRecorderManager();
|
initRecorderManager();
|
||||||
|
updateInputLines();
|
||||||
|
|
||||||
// 监听关闭功能栏事件
|
// 监听关闭功能栏事件
|
||||||
uni.$on("closeMorePanel", () => {
|
uni.$on("closeMorePanel", () => {
|
||||||
|
|||||||
1
static/expand.svg
Normal file
1
static/expand.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1792130980667" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1700" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M459.712 507.568L208 759.28V528H128v368h368v-80H264.56l251.728-251.792-56.576-56.64zM528 128v80h231.44L507.712 459.712l56.576 56.576L816 264.56V496h80V128H528z" fill="#565D64" p-id="1701"></path></svg>
|
||||||
|
After Width: | Height: | Size: 534 B |
1
static/mini.svg
Normal file
1
static/mini.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1792131033804" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3515" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M104.704 338.752a64 64 0 0 1 90.496 0L512 655.552l316.8-316.8a64 64 0 0 1 90.496 90.496l-362.048 362.048a64 64 0 0 1-90.496 0L104.704 429.248a64 64 0 0 1 0-90.496z" p-id="3516"></path></svg>
|
||||||
|
After Width: | Height: | Size: 523 B |
Loading…
x
Reference in New Issue
Block a user