main.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <!--
  2. - Copyright (C) 2018-2019
  3. - All rights reserved, Designed By www.joolun.com
  4. YSHOP源码:
  5. ① 移除多余的 rep 为前缀的变量,让 message 消息更简单
  6. ② 代码优化,补充注释,提升阅读性
  7. ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入
  8. ④ 支持发送【视频】消息时,支持新建视频
  9. -->
  10. <template>
  11. <el-tabs type="border-card" v-model="currentTab">
  12. <!-- 类型 1:文本 -->
  13. <el-tab-pane :name="ReplyType.Text">
  14. <template #label>
  15. <el-row align="middle"><Icon icon="ep:document" /> {{t('mp.text')}}</el-row>
  16. </template>
  17. <TabText v-model="reply.content" />
  18. </el-tab-pane>
  19. <!-- 类型 2:图片 -->
  20. <el-tab-pane :name="ReplyType.Image">
  21. <template #label>
  22. <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> {{t('mp.image')}}</el-row>
  23. </template>
  24. <TabImage v-model="reply" />
  25. </el-tab-pane>
  26. <!-- 类型 3:语音 -->
  27. <el-tab-pane :name="ReplyType.Voice">
  28. <template #label>
  29. <el-row align="middle"><Icon icon="ep:phone" /> {{t('mp.voice')}}</el-row>
  30. </template>
  31. <TabVoice v-model="reply" />
  32. </el-tab-pane>
  33. <!-- 类型 4:视频 -->
  34. <el-tab-pane :name="ReplyType.Video">
  35. <template #label>
  36. <el-row align="middle"><Icon icon="ep:share" /> {{t('mp.video')}}</el-row>
  37. </template>
  38. <TabVideo v-model="reply" />
  39. </el-tab-pane>
  40. <!-- 类型 5:图文 -->
  41. <el-tab-pane :name="ReplyType.News">
  42. <template #label>
  43. <el-row align="middle"><Icon icon="ep:reading" /> {{t('mp.graphic')}}</el-row>
  44. </template>
  45. <TabNews v-model="reply" :news-type="newsType" />
  46. </el-tab-pane>
  47. <!-- 类型 6:音乐 -->
  48. <el-tab-pane :name="ReplyType.Music">
  49. <template #label>
  50. <el-row align="middle"><Icon icon="ep:service" />{{t('mp.music')}}</el-row>
  51. </template>
  52. <TabMusic v-model="reply" />
  53. </el-tab-pane>
  54. </el-tabs>
  55. </template>
  56. <script lang="ts" setup>
  57. import { Reply, NewsType, ReplyType, createEmptyReply } from './components/types'
  58. import TabText from './components/TabText.vue'
  59. import TabImage from './components/TabImage.vue'
  60. import TabVoice from './components/TabVoice.vue'
  61. import TabVideo from './components/TabVideo.vue'
  62. import TabNews from './components/TabNews.vue'
  63. import TabMusic from './components/TabMusic.vue'
  64. const { t } = useI18n()
  65. defineOptions({ name: 'WxReplySelect' })
  66. interface Props {
  67. modelValue: Reply
  68. newsType?: NewsType
  69. }
  70. const props = withDefaults(defineProps<Props>(), {
  71. newsType: () => NewsType.Published
  72. })
  73. const emit = defineEmits<{
  74. (e: 'update:modelValue', v: Reply)
  75. }>()
  76. const reply = computed<Reply>({
  77. get: () => props.modelValue,
  78. set: (val) => emit('update:modelValue', val)
  79. })
  80. // 作为多个标签保存各自Reply的缓存
  81. const tabCache = new Map<ReplyType, Reply>()
  82. // 采用独立的ref来保存当前tab,避免在watch标签变化,对reply进行赋值会产生了循环调用
  83. const currentTab = ref<ReplyType>(props.modelValue.type || ReplyType.Text)
  84. watch(
  85. currentTab,
  86. (newTab, oldTab) => {
  87. // 第一次进入:oldTab 为 undefined
  88. // 判断 newTab 是因为 Reply 为 Partial
  89. if (oldTab === undefined || newTab === undefined) {
  90. return
  91. }
  92. tabCache.set(oldTab, unref(reply))
  93. // 从缓存里面取出新tab内容,有则覆盖Reply,没有则创建空Reply
  94. const temp = tabCache.get(newTab)
  95. if (temp) {
  96. reply.value = temp
  97. } else {
  98. let newData = createEmptyReply(reply)
  99. newData.type = newTab
  100. reply.value = newData
  101. }
  102. },
  103. {
  104. immediate: true
  105. }
  106. )
  107. /** 清除除了`type`, `accountId`的字段 */
  108. const clear = () => {
  109. reply.value = createEmptyReply(reply)
  110. }
  111. defineExpose({
  112. clear
  113. })
  114. </script>
  115. <style lang="scss" scoped>
  116. .select-item {
  117. width: 280px;
  118. padding: 10px;
  119. margin: 0 auto 10px;
  120. border: 1px solid #eaeaea;
  121. }
  122. .select-item2 {
  123. padding: 10px;
  124. margin: 0 auto 10px;
  125. border: 1px solid #eaeaea;
  126. }
  127. .ope-row {
  128. padding-top: 10px;
  129. text-align: center;
  130. }
  131. .input-margin-bottom {
  132. margin-bottom: 2%;
  133. }
  134. .item-name {
  135. overflow: hidden;
  136. font-size: 12px;
  137. text-align: center;
  138. text-overflow: ellipsis;
  139. white-space: nowrap;
  140. }
  141. .el-form-item__content {
  142. line-height: unset !important;
  143. }
  144. .col-select {
  145. width: 49.5%;
  146. height: 160px;
  147. padding: 50px 0;
  148. border: 1px solid rgb(234 234 234);
  149. }
  150. .col-select2 {
  151. height: 160px;
  152. padding: 50px 0;
  153. border: 1px solid rgb(234 234 234);
  154. }
  155. .col-add {
  156. float: right;
  157. width: 49.5%;
  158. height: 160px;
  159. padding: 50px 0;
  160. border: 1px solid rgb(234 234 234);
  161. }
  162. .avatar-uploader-icon {
  163. width: 100px !important;
  164. height: 100px !important;
  165. font-size: 28px;
  166. line-height: 100px !important;
  167. color: #8c939d;
  168. text-align: center;
  169. border: 1px solid #d9d9d9;
  170. }
  171. .material-img {
  172. width: 100%;
  173. }
  174. .thumb-div {
  175. display: inline-block;
  176. text-align: center;
  177. }
  178. .item-infos {
  179. width: 30%;
  180. margin: auto;
  181. }
  182. </style>