messages.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <script>
  2. import { hoursTimeFormat } from "utils";
  3. export default {
  4. name: "LemonMessages",
  5. components: {},
  6. props: {
  7. reverseUserId: String,
  8. timeRange: {
  9. type: Number,
  10. default: 1
  11. },
  12. timeFormat: {
  13. type: Function,
  14. default(val) {
  15. return hoursTimeFormat(val);
  16. }
  17. },
  18. messages: {
  19. type: Array,
  20. default: () => []
  21. }
  22. },
  23. data() {
  24. return {
  25. _loading: false,
  26. _loadend: false
  27. };
  28. },
  29. render() {
  30. return (
  31. <div class="lemon-messages" ref="wrap" on-scroll={this._handleScroll}>
  32. <div
  33. class={[
  34. "lemon-messages__load",
  35. `lemon-messages__load--${this._loadend ? "end" : "ing"}`
  36. ]}
  37. >
  38. {this._loadend ? this._renderLoadEnd() : this._renderLoading()}
  39. </div>
  40. {this.messages.map((message, index) => {
  41. const node = [];
  42. const tagName = `lemon-message-${message.type}`;
  43. const prev = this.messages[index - 1];
  44. if (
  45. prev &&
  46. this.msecRange &&
  47. message.sendTime - prev.sendTime > this.msecRange
  48. ) {
  49. node.push(
  50. <lemon-message-event
  51. attrs={{
  52. message: {
  53. id: "__time__",
  54. type: "event",
  55. content: this.timeFormat(message.sendTime)
  56. }
  57. }}
  58. />
  59. );
  60. }
  61. node.push(
  62. <tagName
  63. ref="message"
  64. refInFor={true}
  65. attrs={{
  66. timeFormat: this.msecRange > 0 ? () => {} : this.timeFormat,
  67. message: message,
  68. reverse: this.reverseUserId == message.fromUser.id,
  69. hiddenTitle: false
  70. }}
  71. />
  72. );
  73. return node;
  74. })}
  75. </div>
  76. );
  77. },
  78. computed: {
  79. msecRange() {
  80. return this.timeRange * 1000 * 60;
  81. }
  82. },
  83. watch: {},
  84. methods: {
  85. _renderLoading() {
  86. return <i class="lemon-icon-loading lemonani-spin" />;
  87. },
  88. _renderLoadEnd() {
  89. return <span>暂无更多消息</span>;
  90. },
  91. loaded() {
  92. this._loadend = true;
  93. },
  94. resetLoadState() {
  95. this._loading = false;
  96. this._loadend = false;
  97. },
  98. async _handleScroll(e) {
  99. const { target } = e;
  100. if (
  101. target.scrollTop == 0 &&
  102. this._loading == false &&
  103. this._loadend == false
  104. ) {
  105. this._loading = true;
  106. await this.$nextTick();
  107. const hst = target.scrollHeight;
  108. this.$emit("reach-top", async isEnd => {
  109. await this.$nextTick();
  110. target.scrollTop = target.scrollHeight - hst;
  111. this._loading = false;
  112. this._loadend = !!isEnd;
  113. });
  114. }
  115. },
  116. async scrollToBottom() {
  117. await this.$nextTick();
  118. const { wrap } = this.$refs;
  119. if (wrap) {
  120. wrap.scrollTop = wrap.scrollHeight;
  121. }
  122. }
  123. },
  124. created() {},
  125. mounted() {}
  126. };
  127. </script>
  128. <style lang="stylus">
  129. @import '~styles/utils/index'
  130. +b(lemon-messages)
  131. height 400px
  132. overflow-x hidden
  133. overflow-y auto
  134. scrollbar-light()
  135. padding 10px 15px
  136. +e(time)
  137. text-align center
  138. font-size 12px
  139. +e(load)
  140. user-select none
  141. font-size 12px
  142. text-align center
  143. color #999
  144. line-height 30px
  145. +m(ing)
  146. font-size 22px
  147. </style>