index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. <template>
  2. <view class="comment-v">
  3. <view class="comment_inner">
  4. <view class="text_box">
  5. <div class="u-input-wrapper">
  6. <u-input :type="textarea.type" v-model="dataForm.text" placeholder="请输入" :height='textarea.height'
  7. ref="textRef" :focus="textFocus" :border='textarea.border' :maxlength="textarea.maxlength"
  8. :auto-height="textarea.autoHeight" class="text_input" @input="handleContentChange" />
  9. <div class="remainingCharacters">{{ dataForm.text.length }}/{{textarea.maxlength}}</div>
  10. </div>
  11. </view>
  12. <view class="box" :style="{ bottom: popupOpenBottom + 'rpx'}">
  13. <scroll-view :scroll-y="true" style="height: 550rpx;" class="scroll_view" @click="hideDrawer">
  14. <view class="comment-area">
  15. <view class="img_box">
  16. <view class="u-preview-wrap" v-for="(item, index) in dataForm.imgList" :key="index">
  17. <view class="u-delete-icon" @tap.stop="deleteItem(index)">
  18. <u-icon class="u-icon" name="close" size="20" color="#ffffff"></u-icon>
  19. </view>
  20. <image class="u-preview-image" :src="baseURL+(item.thumbUrl||item.url)"
  21. mode="aspectFill" @tap.stop="doPreviewImage(baseURL+item.url)"></image>
  22. </view>
  23. </view>
  24. <view v-for='(item,index) in dataForm.file' :key="index"
  25. class="jnpf-file-item u-type-primary u-flex u-line-1" @tap='downLoad(item)'>
  26. <view class="jnpf-file-item-txt u-line-1">{{item.name}}</view>
  27. <view class="closeBox u-flex-col" @click.stop="delFile(index)">
  28. <text class="icon-ym icon-ym-nav-close closeTxt u-flex"></text>
  29. </view>
  30. </view>
  31. </view>
  32. </scroll-view>
  33. <view class="btn_box">
  34. <view class="u-flex">
  35. <view class="btn_item">
  36. <view class="icon-ym icon-ym-roll-call" size="mini" @click="openSelectUser()">
  37. </view>
  38. </view>
  39. <view class="btn_item">
  40. <view class="icon-ym icon-ym-comment-img"
  41. v-if="dataForm.imgList && dataForm.imgList.length >= 9"
  42. @click="clickImgUploadOverCount">
  43. </view>
  44. <u-upload :custom-btn="true" :action="comUploadUrl+type" :header="uploadHeaders"
  45. ref="uUpload" :max-size="10*1024*1024" :max-count="9" :show-upload-list="false"
  46. :show-progress="false" @on-success="onImgSuccess" @on-change="onImgChange"
  47. :show-tips="false" @on-oversize="onImgOversize">
  48. <template #addBtn>
  49. <view class="slot-btn" hover-class="slot-btn__hover" hover-stay-time="150">
  50. <view class="icon-ym icon-ym-comment-img"></view>
  51. </view>
  52. </template>
  53. </u-upload>
  54. </view>
  55. <!-- #ifndef APP-HARMONY -->
  56. <view class="btn_item">
  57. <CommentFile v-model="dataForm.file" :limit="9" :fileSize="10"
  58. :currentCount="dataForm.file?dataForm.file.length:0" ref="commentFile" />
  59. </view>
  60. <!-- #endif -->
  61. <view class="btn_item">
  62. <view class="icon-ym icon-ym-emoji" size="mini" @click="chooseEmoji"></view>
  63. </view>
  64. </view>
  65. <view class="btn_item">
  66. <u-button type="primary" @click="handleClick" :disabled="submitDisabled"
  67. size="medium">发送</u-button>
  68. </view>
  69. </view>
  70. </view>
  71. </view>
  72. <view class="popup-layer u-border-top" :class="popupLayerClass" @touchmove.stop.prevent="discard">
  73. <swiper class="emoji-swiper" indicator-dots="true" duration="150" v-show="showEmoji">
  74. <swiper-item v-for="(page,pid) in emojiTree" :key="pid">
  75. <view v-for="(em,eid) in page" :key="eid" @click="addEmoji(em)" class="emoji-item">
  76. <image mode="widthFix" :src="getEmojiUrl(em.url)" class="emoji-item-img"></image>
  77. </view>
  78. </swiper-item>
  79. </swiper>
  80. </view>
  81. <FlowUserModal v-model="flowUserModalShow" :taskId="taskId" :selectType="'custom'" :multiple="true"
  82. ref="flowUserModal" @confirm="handleSelectUser" @close="closeFlowUserModal" />
  83. </view>
  84. </template>
  85. <script>
  86. import {
  87. getDownloadUrl
  88. } from '@/api/common'
  89. import CommentFile from './comment-file/index.vue'
  90. import FlowUserModal from './comment-user-select/index.vue'
  91. import {
  92. emojiList,
  93. emojiTree,
  94. imagesMap
  95. } from '../flowBefore/emoji'
  96. export default {
  97. components: {
  98. CommentFile,
  99. FlowUserModal
  100. },
  101. data() {
  102. return {
  103. dataForm: {
  104. text: '',
  105. file: [],
  106. imgList: [],
  107. },
  108. type: 'annexpic',
  109. deletable: true,
  110. uploadHeaders: {
  111. Authorization: this.token
  112. },
  113. token: '',
  114. tabIndex: 0,
  115. percent: '',
  116. list: [],
  117. textarea: {
  118. type: 'textarea',
  119. border: true,
  120. height: 440,
  121. autoHeight: false,
  122. maxlength: 500
  123. },
  124. taskId: null,
  125. replyId: null,
  126. submitDisabled: true,
  127. selectUserType: '',
  128. selectionStart: 0,
  129. flowUserModalShow: false,
  130. textFocus: true,
  131. popupLayerClass: '',
  132. popupOpenBottom: 20,
  133. showEmoji: false,
  134. emojiList,
  135. emojiTree,
  136. };
  137. },
  138. computed: {
  139. baseURL() {
  140. return this.define.baseURL
  141. },
  142. comUploadUrl() {
  143. return this.define.comUploadUrl
  144. },
  145. },
  146. onReady() {
  147. },
  148. onLoad(e) {
  149. let data = JSON.parse(decodeURIComponent(e.data))
  150. this.taskId = data.taskId
  151. if (data.replyId) {
  152. this.replyId = data.replyId
  153. uni.setNavigationBarTitle({
  154. title: '回复评论'
  155. });
  156. }
  157. },
  158. watch: {
  159. dataForm: {
  160. handler: function(val) {
  161. this.setSubmitDisabled()
  162. },
  163. deep: true,
  164. immediate: true
  165. },
  166. },
  167. methods: {
  168. clickImgUploadOverCount() {
  169. uni.showToast({
  170. title: '最多可以上传9张图片',
  171. icon: 'none'
  172. });
  173. return false;
  174. },
  175. discard() {
  176. return;
  177. },
  178. chooseEmoji() {
  179. if (this.showEmoji) return this.hideDrawer()
  180. this.showEmoji = true;
  181. this.openDrawer();
  182. },
  183. addEmoji(em) {
  184. this.dataForm.text += em.alt;
  185. },
  186. getEmojiUrl(url) {
  187. return imagesMap[url.replace('.', '')]
  188. },
  189. openDrawer() {
  190. this.popupLayerClass = 'showLayer';
  191. setTimeout(() => {
  192. this.popupOpenBottom = 315;
  193. }, 150);
  194. },
  195. hideDrawer() {
  196. this.popupLayerClass = '';
  197. setTimeout(() => {
  198. this.showEmoji = false;
  199. this.popupOpenBottom = 20;
  200. }, 50);
  201. },
  202. getFocus() {
  203. this.textFocus = false
  204. setTimeout(() => {
  205. this.textFocus = true
  206. }, 50);
  207. },
  208. setSubmitDisabled() {
  209. this.submitDisabled = !this.dataForm.text
  210. },
  211. openSelectUser() {
  212. this.selectUserType = 'btn';
  213. this.selectionStart = -1;
  214. this.flowUserModalShow = true
  215. },
  216. closeFlowUserModal() {
  217. this.flowUserModalShow = false
  218. },
  219. handleContentChange(value) {
  220. if (!value || !value.endsWith("@")) {
  221. return;
  222. }
  223. this.selectUserType = 'input';
  224. this.selectionStart = value.length;
  225. this.flowUserModalShow = true
  226. },
  227. handleSelectUser(data) {
  228. if (!data.length || !data.length) return;
  229. let addContent = this.selectUserType === 'btn' ? '@' : '';
  230. for (let i = 0; i < data.length; i++) {
  231. let str = (i > 0 ? '@' : '') + `{${data[i].fullName}}`;
  232. addContent += str;
  233. }
  234. if (this.selectionStart === -1) {
  235. this.dataForm.text += addContent;
  236. this.textFocus = false
  237. this.getFocus();
  238. } else {
  239. let oldValue = this.dataForm.text;
  240. let rangeIndex = this.selectionStart + addContent.length;
  241. this.dataForm.text = oldValue.slice(0, this.selectionStart) + addContent + oldValue.slice(this
  242. .selectionStart);
  243. this.textFocus = false
  244. this.getFocus();
  245. }
  246. },
  247. handleClick() {
  248. const query = {
  249. text: this.dataForm.text,
  250. file: JSON.stringify(this.dataForm.file),
  251. image: JSON.stringify(this.dataForm.imgList),
  252. replyId: this.replyId
  253. }
  254. uni.$emit('comment', query);
  255. uni.navigateBack();
  256. },
  257. //文件下载
  258. downLoad(item) {
  259. // #ifdef MP
  260. this.previewFile(item)
  261. // #endif
  262. // #ifndef MP
  263. getDownloadUrl('annex', item.fileId).then(res => {
  264. const fileUrl = this.baseURL + res.data.url + '&name=' + item.name;
  265. // #ifdef H5
  266. window.location.href = fileUrl;
  267. // #endif
  268. // #ifdef APP-PLUS
  269. this.downloadFile(res.data.url);
  270. // #endif
  271. })
  272. // #endif
  273. },
  274. previewFile(item) {
  275. let fileTypes = ['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx']
  276. let url = item.url
  277. let fileType = url.split('.')[1]
  278. if (fileTypes.includes(fileType)) {
  279. uni.downloadFile({
  280. url: this.baseURL + url,
  281. success: (res) => {
  282. var filePath = res.tempFilePath;
  283. uni.openDocument({
  284. filePath: encodeURI(filePath),
  285. showMenu: true,
  286. fileType: fileType,
  287. success: (res) => {},
  288. fail(err) {}
  289. });
  290. }
  291. });
  292. } else {
  293. this.$u.toast(
  294. '该文件类型无法打开'
  295. )
  296. }
  297. },
  298. //文件删除
  299. delFile(index) {
  300. uni.showModal({
  301. title: '提示',
  302. content: '是否删除该文件?',
  303. success: res => {
  304. if (res.confirm) {
  305. this.dataForm.file.splice(index, 1)
  306. this.$refs.commentFile.delFile(this.dataForm.file);
  307. } else if (res.cancel) {}
  308. }
  309. });
  310. },
  311. downloadFile(url) {
  312. uni.downloadFile({
  313. url: this.baseURL + url,
  314. success: res => {
  315. if (res.statusCode === 200) {
  316. const filePath = res.tempFilePath;
  317. uni.openDocument({
  318. filePath: escape(filePath),
  319. success: ress => {},
  320. fail(err) {}
  321. });
  322. }
  323. }
  324. });
  325. },
  326. onImgSuccess(data, index, lists, name) {
  327. if (data.code == 200) {
  328. this.dataForm.imgList.push({
  329. name: lists[index].file.name,
  330. fileId: data.data.name,
  331. url: data.data.url,
  332. thumbUrl: data.data.thumbUrl,
  333. })
  334. // this.$emit('input', this.fileList)
  335. } else {
  336. lists.splice(index, 1)
  337. this.$u.toast(data.msg)
  338. }
  339. },
  340. onImgChange(res, index, lists, name) {
  341. const isTopLimit = lists.length > 9;
  342. if (isTopLimit) {
  343. uni.showToast({
  344. title: '最多可以上传9张图片',
  345. icon: 'none'
  346. });
  347. return false
  348. }
  349. const isRightSize = lists[index].file.size < 10 * 1024 * 1024;
  350. if (!isRightSize) {
  351. uni.showToast({
  352. title: '图片大小超过10MB',
  353. icon: 'none'
  354. });
  355. return false;
  356. }
  357. /*
  358. let isAccept = new RegExp('image/!*').test(file.type);
  359. if (!isAccept) {
  360. this.$message({ message: '请上传图片', type: 'error', duration: 1000 })
  361. return
  362. }
  363. return isRightSize && isAccept;*/
  364. },
  365. onImgOversize(res, index, lists, name) {
  366. uni.showToast({
  367. title: '图片大小超过10MB',
  368. icon: 'none'
  369. });
  370. return false;
  371. },
  372. doPreviewImage(url) {
  373. const images = this.dataForm.imgList.map(item => this.baseURL + item.url);
  374. uni.previewImage({
  375. urls: images,
  376. current: url,
  377. success: () => {},
  378. fail: () => {
  379. uni.showToast({
  380. title: '预览图片失败',
  381. icon: 'none'
  382. });
  383. }
  384. });
  385. },
  386. deleteItem(index) {
  387. uni.showModal({
  388. title: '提示',
  389. content: '是否删除该图片?',
  390. success: res => {
  391. if (res.confirm) {
  392. this.$refs.uUpload.remove(index);
  393. this.dataForm.imgList.splice(index, 1)
  394. // this.$emit('input', this.fileList)
  395. uni.showToast({
  396. title: '移除成功',
  397. icon: 'none'
  398. });
  399. }
  400. }
  401. });
  402. }
  403. }
  404. }
  405. </script>
  406. <style lang="scss">
  407. page {
  408. height: 100%;
  409. }
  410. .comment-v {
  411. width: 100%;
  412. height: 100%;
  413. display: flex;
  414. flex-direction: column;
  415. margin: 0 auto;
  416. // .flowBefore-actions{
  417. // width: 90%;
  418. // left: 50%;
  419. // transform: translateX(-50%);
  420. // bottom: 20rpx;
  421. // .buttom-btn{
  422. // border-radius: 10rpx;
  423. // }
  424. // }
  425. .uni-textarea-compute {
  426. height: 470rpx !important;
  427. }
  428. .comment_inner {
  429. display: flex;
  430. flex-direction: column;
  431. background-color: #FFFFFF;
  432. height: 100%;
  433. padding: 0 30rpx;
  434. .text_box {
  435. flex: 0.35;
  436. .u-input-wrapper {
  437. border: 1px solid #E7E7E7;
  438. margin-top: 10px;
  439. }
  440. .remainingCharacters {
  441. width: 99%;
  442. text-align: right;
  443. }
  444. .text_input {
  445. border: 0px;
  446. }
  447. // .input_textarea{
  448. // height: 470rpx !important;
  449. // }
  450. }
  451. .box {
  452. width: 100%;
  453. display: flex;
  454. flex-direction: column;
  455. position: absolute;
  456. .scroll_view {
  457. .comment-area {
  458. height: 550rpx;
  459. display: flex;
  460. flex-direction: column;
  461. align-items: flex-end;
  462. justify-content: flex-end;
  463. margin-bottom: 28rpx;
  464. .img_box {
  465. width: 100%;
  466. display: flex;
  467. flex-direction: row;
  468. flex-wrap: wrap;
  469. .u-preview-wrap {
  470. width: 110rpx;
  471. height: 110rpx;
  472. overflow: hidden;
  473. margin: 10rpx;
  474. background: rgb(244, 245, 246);
  475. position: relative;
  476. border-radius: 10rpx;
  477. /* #ifndef APP-NVUE */
  478. display: flex;
  479. /* #endif */
  480. align-items: center;
  481. justify-content: center;
  482. .u-preview-image {
  483. display: block;
  484. width: 100%;
  485. height: 100%;
  486. border-radius: 10rpx;
  487. }
  488. .u-delete-icon {
  489. position: absolute;
  490. top: 10rpx;
  491. right: 10rpx;
  492. z-index: 10;
  493. background-color: $u-type-error;
  494. border-radius: 100rpx;
  495. width: 34rpx;
  496. height: 34rpx;
  497. /* #ifndef APP-NVUE */
  498. display: flex;
  499. /* #endif */
  500. align-items: center;
  501. justify-content: center;
  502. }
  503. .u-icon {
  504. /* #ifndef APP-NVUE */
  505. display: flex;
  506. /* #endif */
  507. align-items: center;
  508. justify-content: center;
  509. }
  510. }
  511. }
  512. .jnpf-file-item {
  513. width: 100%;
  514. justify-content: space-between;
  515. flex-direction: row;
  516. .jnpf-file-item-txt {
  517. flex: 1;
  518. }
  519. .closeBox {
  520. height: 60rpx;
  521. justify-content: space-evenly;
  522. flex: 0.2;
  523. .closeTxt {
  524. width: 34rpx;
  525. height: 34rpx;
  526. border-radius: 50%;
  527. background-color: #909194;
  528. color: #FFFFFF;
  529. font-size: 22rpx;
  530. align-items: center;
  531. justify-content: center;
  532. }
  533. }
  534. }
  535. }
  536. }
  537. .btn_box {
  538. width: 100%;
  539. display: flex;
  540. flex-direction: row;
  541. // justify-content: flex-start;
  542. align-items: center;
  543. .btn_item {
  544. margin-right: 30rpx;
  545. margin-left: 10rpx;
  546. .icon-ym {
  547. font-size: 48rpx;
  548. }
  549. }
  550. .btn_item:last-child {
  551. margin-left: auto;
  552. margin-right: 50rpx;
  553. }
  554. .submit_item {
  555. background-color: red;
  556. }
  557. .slot-btn {
  558. .img_icon {
  559. width: 80rpx;
  560. height: 80rpx;
  561. text-align: center;
  562. line-height: 80rpx;
  563. &:before {
  564. content: "\e987";
  565. font-size: 60rpx;
  566. color: #666666;
  567. }
  568. }
  569. }
  570. .file_icon {
  571. width: 80rpx;
  572. height: 80rpx;
  573. text-align: center;
  574. line-height: 80rpx;
  575. &:before {
  576. font-size: 60rpx;
  577. color: #666666;
  578. }
  579. }
  580. }
  581. }
  582. }
  583. }
  584. .popup-layer {
  585. &.showLayer {
  586. transform: translate3d(0, -42vw, 0);
  587. }
  588. transition: all .15s linear;
  589. width: 100%;
  590. height: 42vw;
  591. padding: 20rpx 2%;
  592. background-color: #f2f2f2;
  593. position: fixed;
  594. z-index: 20;
  595. top: 100%;
  596. .emoji-swiper {
  597. height: 40vw;
  598. swiper-item {
  599. display: flex;
  600. align-content: flex-start;
  601. flex-wrap: wrap;
  602. .emoji-item {
  603. width: 12vw;
  604. height: 12vw;
  605. display: flex;
  606. justify-content: center;
  607. align-items: center;
  608. .emoji-item-img {
  609. width: 8.4vw;
  610. height: 8.4vw;
  611. }
  612. }
  613. }
  614. }
  615. .more-layer {
  616. width: 100%;
  617. height: 42vw;
  618. .list {
  619. width: 100%;
  620. display: flex;
  621. flex-wrap: wrap;
  622. .box {
  623. width: 18vw;
  624. height: 18vw;
  625. border-radius: 20rpx;
  626. background-color: #fff;
  627. display: flex;
  628. justify-content: center;
  629. align-items: center;
  630. margin: 0 3vw 2vw 3vw;
  631. .icon {
  632. font-size: 70rpx;
  633. }
  634. }
  635. }
  636. }
  637. }
  638. </style>