footer_input.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <view
  3. class="footer_input"
  4. :class="[
  5. bottomHeight == 0 ? 'iosBottom' : '',
  6. isFocused && bottomHeight && systemInfoName.osName == 'ios'
  7. ? 'focused'
  8. : '',
  9. ]"
  10. id="chatBottom"
  11. :style="{
  12. bottom: bottomHeight + 'px',
  13. '--bottomHeight': allHeight.keyboardHeight + 'px',
  14. }"
  15. >
  16. <view class="footer_">
  17. <view class="inputs">
  18. <up-textarea
  19. :disabled="disabled"
  20. v-model="inputValue"
  21. maxlength="-1"
  22. :placeholder="$t(placeholder)"
  23. :adjustPosition="useGlobal().adjustPosition"
  24. confirm-type="send"
  25. :showConfirmBar="false"
  26. :ignoreCompositionEvent="false"
  27. autoHeight
  28. @linechange="heightChange"
  29. @focus="isFocused = true"
  30. @blur="onBlur"
  31. @keyboardheightchange="keyboardheightchange"
  32. @update:modelValue="input"
  33. @confirm="submit"
  34. @submit="submit"
  35. />
  36. </view>
  37. <view class="footer_btns">
  38. <view class="footer_icon" @click="imageUploader">
  39. <image class="img" src="@/static/chat/chat_add-icon.png"></image>
  40. </view>
  41. <view class="footer_send_btn" @click="submit">
  42. <image
  43. class="img"
  44. src="@/static/chat/arrows_up-icon.png"
  45. mode="heightFix"
  46. ></image>
  47. </view>
  48. </view>
  49. </view>
  50. </view>
  51. <CameraUpload ref="cameraUploadRef" @confirm="onCameraConfirm" />
  52. </template>
  53. <script setup>
  54. import {
  55. query,
  56. useGlobal,
  57. uploadChooseImage,
  58. $upload,
  59. debounce,
  60. systemInfo,
  61. } from "@/utils";
  62. import { ref, reactive, nextTick, onMounted } from "vue";
  63. import { onShow } from "@dcloudio/uni-app";
  64. import CameraUpload from "./CameraUpload.vue";
  65. const props = defineProps({
  66. disabled: {
  67. type: Boolean,
  68. default: false,
  69. },
  70. placeholder: {
  71. type: String,
  72. default: "请输入消息内容",
  73. },
  74. modelValue: {
  75. type: [String, Number],
  76. default: "",
  77. },
  78. bottomHeight: {
  79. type: Number,
  80. default: 0,
  81. },
  82. });
  83. const emit = defineEmits([
  84. "update:modelValue",
  85. "heights",
  86. "linechange",
  87. "submit",
  88. "imageUp",
  89. ]);
  90. const inputValue = ref(props.modelValue);
  91. const allHeight = reactive({
  92. height: 0,
  93. keyboardHeight: 0,
  94. });
  95. const isFocused = ref(false);
  96. const systemInfoName = systemInfo();
  97. const clear = () => {
  98. inputValue.value = "";
  99. };
  100. function isIOS() {
  101. const userAgent = navigator.userAgent.toLowerCase();
  102. return /iphone|ipad|ipod|ios/.test(userAgent);
  103. }
  104. const emitHeights = debounce((heightData) => {
  105. if (!props.disabled) {
  106. emit("heights", heightData);
  107. }
  108. }, 100);
  109. const submit = () => {
  110. if (!inputValue.value.replace(/\s+/g, "")) return;
  111. if (!props.disabled) {
  112. emit("submit");
  113. }
  114. };
  115. const input = (value) => {
  116. inputValue.value = value;
  117. emit("update:modelValue", value);
  118. };
  119. const getHeight = (id = "#chatBottom") => {
  120. try {
  121. nextTick(async () => {
  122. const res = await query(id);
  123. allHeight.height = res.height;
  124. emitHeights(allHeight);
  125. });
  126. } catch (error) {}
  127. };
  128. const keyboardheightchange = (e) => {
  129. nextTick(() => {
  130. const systemInfo = uni.getSystemInfoSync();
  131. const bottomSafeDistance =
  132. systemInfo.screenHeight - systemInfo.safeArea.bottom;
  133. allHeight.keyboardHeight =
  134. e.detail.height -
  135. (systemInfo.platform === "ios" && isFocused.value
  136. ? bottomSafeDistance
  137. : 0);
  138. allHeight.bottomSafeDistance = isFocused.value && bottomSafeDistance;
  139. emitHeights(allHeight);
  140. });
  141. };
  142. const onBlur = (e) => {
  143. isFocused.value = false;
  144. keyboardheightchange(e);
  145. };
  146. const heightChange = (e) => {
  147. getHeight();
  148. emit("linechange", { detail: e.detail });
  149. };
  150. const cameraUploadRef = ref(null);
  151. const imageUploader = () => {
  152. if (!props.disabled) {
  153. cameraUploadRef.value && cameraUploadRef.value.open();
  154. }
  155. };
  156. const onCameraConfirm = (url) => {
  157. nextTick(() => {
  158. emit("imageUp", url);
  159. });
  160. };
  161. onMounted(() => {
  162. nextTick(() => {
  163. getHeight();
  164. });
  165. });
  166. defineExpose({
  167. getHeight,
  168. clear,
  169. });
  170. </script>
  171. <style lang="less" scoped>
  172. @import url("@/style.less");
  173. .footer_input {
  174. position: fixed;
  175. bottom: 0;
  176. left: 0;
  177. right: 0;
  178. .ver();
  179. padding: 0 30rpx;
  180. background-color: #fafafa;
  181. overflow: hidden;
  182. z-index: 9999;
  183. &.iosBottom {
  184. padding-bottom: constant(safe-area-inset-bottom);
  185. padding-bottom: env(safe-area-inset-bottom);
  186. }
  187. &.focused {
  188. /* #ifdef APP-PLUS */
  189. bottom: calc(var(--bottomHeight) + env(safe-area-inset-bottom)) !important;
  190. /* #endif */
  191. }
  192. .footer_ {
  193. .ver(flex-end);
  194. flex: 1;
  195. padding: 20rpx 0;
  196. .inputs {
  197. min-height: 40px;
  198. flex: 1;
  199. /deep/ .u-textarea {
  200. padding: 9px;
  201. .uni-textarea-placeholder {
  202. line-height: 1.2;
  203. }
  204. .u-textarea__field {
  205. min-height: 0 !important;
  206. }
  207. }
  208. }
  209. .footer_btns {
  210. .ver();
  211. height: 40px;
  212. margin-left: 20rpx;
  213. .footer_icon {
  214. .img {
  215. width: 38rpx;
  216. height: 38rpx;
  217. }
  218. }
  219. .footer_send_btn {
  220. .flex_center();
  221. width: 68rpx;
  222. height: 68rpx;
  223. border-radius: 50%;
  224. background-color: var(--black);
  225. cursor: pointer;
  226. margin-left: 15rpx;
  227. .img {
  228. height: 32rpx;
  229. }
  230. }
  231. }
  232. }
  233. }
  234. </style>