MyComment.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <template>
  2. <div :class="{ 'comment-active': commentActive }" style="border: 1px solid #eee; margin: 0; position: relative" @click="handleClickBlank">
  3. <textarea
  4. ref="commentRef"
  5. v-model="myComment"
  6. @keyup.enter="sendComment"
  7. @input="handleCommentChange"
  8. @blur="handleBlur"
  9. class="comment-content"
  10. :rows="3"
  11. placeholder="请输入你的评论,可以@成员"
  12. ></textarea>
  13. <div
  14. class="comment-content comment-html-shower"
  15. :class="{ 'no-content': noConent, 'top-div': showHtml, 'bottom-div': showHtml == false }"
  16. v-html="commentHtml"
  17. @click="handleClickHtmlShower"
  18. ></div>
  19. <div class="comment-buttons" v-if="commentActive">
  20. <div style="cursor: pointer">
  21. <Tooltip title="选择@用户">
  22. <user-add-outlined @click="openSelectUser" />
  23. </Tooltip>
  24. <Tooltip title="上传附件">
  25. <PaperClipOutlined @click="uploadVisible = !uploadVisible" />
  26. </Tooltip>
  27. <span title="表情" style="display: inline-block">
  28. <SmileOutlined ref="emojiButton" @click="handleShowEmoji" />
  29. <div style="position: relative" v-show=""> </div>
  30. </span>
  31. </div>
  32. <div v-if="commentActive">
  33. <a-button v-if="inner" @click="noComment" style="margin-right: 10px">取消</a-button>
  34. <a-button type="primary" @click="sendComment" :loading="buttonLoading" :disabled="disabledButton">发 送</a-button>
  35. </div>
  36. </div>
  37. <upload-chunk ref="uploadRef" :visible="uploadVisible" @select="selectFirstFile" />
  38. </div>
  39. <UserSelectModal labelKey="realname" rowKey="username" @register="registerModal" @getSelectResult="setValue" isRadioSelection />
  40. <a-modal v-model:visible="visibleEmoji" :footer="null" wrapClassName="emoji-modal" :closable="false" :width="490">
  41. <template #title>
  42. <span></span>
  43. </template>
  44. <Picker
  45. :pickerStyles="pickerStyles"
  46. :i18n="optionsName"
  47. :data="emojiIndex"
  48. emoji="grinning"
  49. :showPreview="false"
  50. :infiniteScroll="false"
  51. :showSearch="false"
  52. :showSkinTones="false"
  53. set="apple"
  54. @select="showEmoji"
  55. />
  56. </a-modal>
  57. </template>
  58. <script lang="ts">
  59. import { ref, watch, computed, inject } from 'vue';
  60. import { propTypes } from '/@/utils/propTypes';
  61. import { UserAddOutlined, PaperClipOutlined, SmileOutlined } from '@ant-design/icons-vue';
  62. import { Tooltip } from 'ant-design-vue';
  63. import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue';
  64. import { useModal } from '/@/components/Modal';
  65. import UploadChunk from './UploadChunk.vue';
  66. import 'emoji-mart-vue-fast/css/emoji-mart.css';
  67. import { getGloablEmojiIndex, useEmojiHtml } from './useComment';
  68. const optionsName = {
  69. categories: {
  70. recent: '最常用的',
  71. smileys: '表情选择',
  72. people: '人物&身体',
  73. nature: '动物&自然',
  74. foods: '食物&饮料',
  75. activity: '活动',
  76. places: '旅行&地点',
  77. objects: '物品',
  78. symbols: '符号',
  79. flags: '旗帜',
  80. },
  81. };
  82. export default {
  83. name: 'MyComment',
  84. components: {
  85. UserAddOutlined,
  86. Tooltip,
  87. UserSelectModal,
  88. PaperClipOutlined,
  89. UploadChunk,
  90. SmileOutlined,
  91. },
  92. props: {
  93. inner: propTypes.bool.def(false),
  94. inputFocus: {
  95. type: Boolean,
  96. default: false,
  97. },
  98. },
  99. emits: ['cancel', 'comment'],
  100. setup(props, { emit }) {
  101. const uploadVisible = ref(false);
  102. const uploadRef = ref();
  103. //注册model
  104. const [registerModal, { openModal }] = useModal();
  105. const buttonLoading = ref(false);
  106. const myComment = ref<string>('');
  107. function sendComment() {
  108. console.log(myComment.value);
  109. let content = myComment.value;
  110. if (!content && content !== '0') {
  111. disabledButton.value = true;
  112. } else {
  113. buttonLoading.value = true;
  114. let fileList = [];
  115. if (uploadVisible.value == true) {
  116. fileList = uploadRef.value.getUploadFileList();
  117. }
  118. emit('comment', content, fileList);
  119. setTimeout(() => {
  120. buttonLoading.value = false;
  121. }, 350);
  122. }
  123. }
  124. const disabledButton = ref(false);
  125. watch(myComment, () => {
  126. let content = myComment.value;
  127. if (!content && content !== '0') {
  128. disabledButton.value = true;
  129. } else {
  130. disabledButton.value = false;
  131. }
  132. });
  133. function noComment() {
  134. emit('cancel');
  135. }
  136. const commentRef = ref();
  137. watch(
  138. () => props.inputFocus,
  139. (val) => {
  140. if (val == true) {
  141. // commentRef.value.focus()
  142. myComment.value = '';
  143. if (uploadVisible.value == true) {
  144. uploadRef.value.clear();
  145. uploadVisible.value = false;
  146. }
  147. }
  148. },
  149. { deep: true, immediate: true }
  150. );
  151. function openSelectUser() {
  152. openModal(true, {
  153. isUpdate: false,
  154. });
  155. }
  156. function setValue(options) {
  157. console.log('setValue', options);
  158. if (options && options.length > 0) {
  159. const { label, value } = options[0];
  160. if (label && value) {
  161. let str = `${label}[${value}]`;
  162. let temp = myComment.value;
  163. if (!temp) {
  164. myComment.value = '@' + str;
  165. } else {
  166. if (temp.endsWith('@')) {
  167. myComment.value = temp + str;
  168. } else {
  169. myComment.value = '@' + str + ' ' + temp;
  170. }
  171. }
  172. }
  173. }
  174. }
  175. function handleCommentChange() {
  176. //console.log(1,e)
  177. }
  178. watch(
  179. () => myComment.value,
  180. (val) => {
  181. if (val && val.endsWith('@')) {
  182. openSelectUser();
  183. }
  184. }
  185. );
  186. const emojiButton = ref();
  187. function onSelectEmoji(emoji) {
  188. let temp = myComment.value || '';
  189. temp += emoji;
  190. myComment.value = temp;
  191. emojiButton.value.click();
  192. }
  193. const visibleEmoji = ref(false);
  194. function showEmoji(e) {
  195. let temp = myComment.value || '';
  196. let str = e.colons;
  197. if (str.indexOf('::') > 0) {
  198. str = str.substring(0, str.indexOf(':') + 1);
  199. }
  200. myComment.value = temp + str;
  201. visibleEmoji.value = false;
  202. handleBlur();
  203. }
  204. const pickerStyles = {
  205. width: '490px',
  206. /* height: '350px',
  207. top: '0px',
  208. left: '-75px',
  209. position: 'absolute',
  210. 'z-index': 9999*/
  211. };
  212. function handleClickBlank(e) {
  213. console.log('handleClickBlank');
  214. e.preventDefault();
  215. e.stopPropagation();
  216. visibleEmoji.value = false;
  217. commentActive.value = true;
  218. }
  219. function handleShowEmoji(e) {
  220. console.log('handleShowEmoji');
  221. e.preventDefault();
  222. e.stopPropagation();
  223. visibleEmoji.value = !visibleEmoji.value;
  224. }
  225. //const emojiIndex = inject('$globalEmojiIndex')
  226. const emojiIndex = getGloablEmojiIndex();
  227. const { getHtml } = useEmojiHtml(emojiIndex);
  228. const commentHtml = computed(() => {
  229. let temp = myComment.value;
  230. if (!temp) {
  231. return '请输入你的评论,可以@成员';
  232. }
  233. return getHtml(temp);
  234. });
  235. const showHtml = ref(false);
  236. function handleClickHtmlShower(e) {
  237. e.preventDefault();
  238. e.stopPropagation();
  239. showHtml.value = false;
  240. commentRef.value.focus();
  241. console.log(234);
  242. commentActive.value = true;
  243. }
  244. function handleBlur() {
  245. showHtml.value = true;
  246. }
  247. const commentActive = ref(false);
  248. const noConent = computed(() => {
  249. if (myComment.value.length > 0) {
  250. return false;
  251. }
  252. return true;
  253. });
  254. function changeActive() {
  255. if (myComment.value.length == 0) {
  256. commentActive.value = false;
  257. uploadVisible.value = false;
  258. }
  259. }
  260. function selectFirstFile(fileName) {
  261. if (myComment.value.length == 0) {
  262. myComment.value = fileName;
  263. }
  264. }
  265. return {
  266. myComment,
  267. sendComment,
  268. noComment,
  269. disabledButton,
  270. buttonLoading,
  271. commentRef,
  272. registerModal,
  273. openSelectUser,
  274. setValue,
  275. handleCommentChange,
  276. uploadRef,
  277. uploadVisible,
  278. onSelectEmoji,
  279. optionsName,
  280. emojiButton,
  281. emojiIndex,
  282. showEmoji,
  283. pickerStyles,
  284. visibleEmoji,
  285. handleClickBlank,
  286. handleShowEmoji,
  287. commentHtml,
  288. showHtml,
  289. handleClickHtmlShower,
  290. handleBlur,
  291. commentActive,
  292. noConent,
  293. changeActive,
  294. selectFirstFile,
  295. };
  296. },
  297. };
  298. </script>
  299. <style lang="less">
  300. @ventSpace: zxm;
  301. .comment-content {
  302. box-sizing: border-box;
  303. margin: 0;
  304. padding: 0;
  305. font-variant: tabular-nums;
  306. list-style: none;
  307. font-feature-settings: tnum;
  308. position: relative;
  309. display: inline-block;
  310. width: 100%;
  311. padding: 4px 11px;
  312. color: rgba(0, 0, 0, 0.85);
  313. font-size: 15px;
  314. line-height: 1.5715;
  315. background-color: #fff;
  316. background-image: none;
  317. border: 1px solid #d9d9d9;
  318. border-radius: 2px;
  319. transition: all 0.3s;
  320. width: 100%;
  321. border: solid 0px;
  322. outline: none;
  323. .emoji-item {
  324. display: inline-block !important;
  325. width: 0 !important;
  326. }
  327. }
  328. .comment-buttons {
  329. padding: 10px;
  330. display: flex;
  331. justify-content: space-between;
  332. border-top: 1px solid #d9d9d9;
  333. .anticon {
  334. margin: 5px;
  335. }
  336. }
  337. .comment-html-shower {
  338. position: absolute;
  339. top: 0;
  340. left: 0;
  341. height: 70px;
  342. &.bottom-div {
  343. z-index: -99;
  344. }
  345. &.top-div {
  346. z-index: 9;
  347. }
  348. }
  349. .emoji-modal {
  350. > .@{ventSpace}-modal {
  351. right: 25% !important;
  352. margin-right: 16px !important;
  353. }
  354. .@{ventSpace}-modal-header {
  355. padding: 0 !important;
  356. }
  357. .emoji-mart-bar {
  358. display: none;
  359. }
  360. h3.emoji-mart-category-label {
  361. /* display: none;*/
  362. border-bottom: 1px solid #eee;
  363. }
  364. }
  365. .comment-active {
  366. border-color: #1e88e5 !important;
  367. box-shadow:
  368. 0 1px 1px 0 #90caf9,
  369. 0 1px 6px 0 #90caf9;
  370. }
  371. .no-content {
  372. color: #a1a1a1;
  373. }
  374. /**聊天表情本地化*/
  375. .emoji-type-image.emoji-set-apple {
  376. background-image: url('./image/emoji.png');
  377. }
  378. </style>