comment.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <template>
  2. <div class="container">
  3. <!-- 评论输入框 -->
  4. <div class="write-reply" @click="showCommentInput()">
  5. <i class="el-icon-edit"></i>
  6. <span class="add-comment">添加新评论</span>
  7. </div>
  8. <el-collapse-transition>
  9. <div class="input-wrapper" v-if="isShowInput">
  10. <p class="reply-box" v-if="replyUser">{{ replyUser }}:</p>
  11. <el-input
  12. ref="inputComment"
  13. resize="none"
  14. class="gray-bg-input"
  15. v-model="inputComment"
  16. type="textarea"
  17. :rows="4"
  18. autofocus
  19. placeholder="写下你的评论..."
  20. @paste.native.capture.prevent="pasting"
  21. >
  22. </el-input>
  23. <div class="btn-control">
  24. <div class="left">
  25. <el-upload
  26. v-show="fileList.length > 0"
  27. ref="upload"
  28. accept="image/*"
  29. multiple
  30. :action="uploadUrl"
  31. :file-list="fileList"
  32. list-type="picture-card"
  33. :on-change="handleImgChange"
  34. :on-success="handleFrontSuccess"
  35. :on-remove="handleRemove"
  36. >
  37. <i class="el-icon-plus"></i>
  38. </el-upload>
  39. <i class="el-icon-picture finger-point" @click="submitUpload"></i>
  40. </div>
  41. <div class="right">
  42. <span class="cancel" @click="cancel">取消</span>
  43. <el-button
  44. class="confirm"
  45. type="success"
  46. round
  47. @click="commitComment"
  48. >确定</el-button
  49. >
  50. </div>
  51. </div>
  52. </div>
  53. </el-collapse-transition>
  54. <div class="comment" ref="commentRoot" :class="{move:item.id==$route.query.id}" v-for="(item, index) in comments" :key="index">
  55. <div class="info">
  56. <img
  57. class="avatar"
  58. v-if="item.has_one_comment_user"
  59. :src="item.has_one_comment_user.avatar"
  60. />
  61. <div style="margin-left: 10px; flex: 1">
  62. <div class="right-box">
  63. <div class="name">{{ item.uname }}</div>
  64. <div class="date">{{ item.created_at }}</div>
  65. <div class="control finger-point">
  66. <!-- 点赞 -->
  67. <span
  68. :style="{ color: item.is_praise ? '#ec544a' : '' }"
  69. @click.stop="praiseBtn(item.id, '', index, '', item.is_praise)"
  70. >
  71. <i class="iconfont icon-fontclass-tuijian"></i>
  72. {{ item.praise_num }}
  73. </span>
  74. <!-- 评论 -->
  75. <span @click.stop="showCommentInput(item)">
  76. <i class="iconfont icon-fontclass-pinglun2"></i> 回复
  77. </span>
  78. <!-- 赞赏 -->
  79. <span @click.stop="moneyShow(item)" v-if="comment_open == 1">
  80. <i class="iconfont icon-fontclass-shang"></i> 赞赏
  81. </span>
  82. <span v-if="item.is_own == 1" @click.stop="deleteBtn(item)">
  83. <i class="iconfont icon-fontclass-shanchu"></i>
  84. </span>
  85. </div>
  86. </div>
  87. <div class="comment-content">{{ item.content }}</div>
  88. <div class="comment-imgs">
  89. <el-image
  90. v-for="(imgItem, index) in item.image"
  91. :key="index"
  92. :src="imgItem"
  93. fit="contain"
  94. @click.stop="toShowImgViewer(item.image, index)"
  95. ></el-image>
  96. </div>
  97. <div
  98. class="reply finger-point look-child"
  99. @click.stop="showChild = true"
  100. v-if="!showChild && item.child.length > 0"
  101. >
  102. 查看该评论回复
  103. </div>
  104. <div class="reply" v-if="showChild">
  105. <div
  106. class="item"
  107. v-for="(reply, replyIndex) in item.child"
  108. :key="replyIndex"
  109. >
  110. <div class="flex flex-a-c">
  111. <div style="display: flex; align-self: flex-start">
  112. <img
  113. class="reply-avatar"
  114. v-if="reply.has_one_comment_user"
  115. :src="reply.has_one_comment_user.avatar"
  116. alt=""
  117. />
  118. </div>
  119. <div style="margin-left: 10px; flex: 1">
  120. <div class="right-box">
  121. <div class="reply-title">
  122. <span class="from-name">{{ reply.uname }}</span
  123. ><span>: </span>
  124. <span class="to-name" v-if="reply.less_name"
  125. >@{{ reply.less_name }}</span
  126. >
  127. <span class="reply-time">{{ reply.created_at }}</span>
  128. </div>
  129. <div class="control finger-point">
  130. <!-- 点赞 -->
  131. <span
  132. :style="{ color: reply.is_praise ? '#ec544a' : '' }"
  133. @click.stop="
  134. praiseBtn(
  135. reply.major_cm_id,
  136. reply.id,
  137. index,
  138. replyIndex,
  139. reply.is_praise
  140. )
  141. "
  142. >
  143. <i class="iconfont icon-fontclass-tuijian"></i>
  144. {{ reply.praise_num }}
  145. </span>
  146. <!-- 评论 -->
  147. <span @click.stop="showCommentInput(item, reply)">
  148. <i class="iconfont icon-fontclass-pinglun2"></i> 回复
  149. </span>
  150. <span
  151. v-if="reply.is_own == 1"
  152. @click.stop="deleteBtn(reply)"
  153. >
  154. <i class="iconfont icon-fontclass-shanchu"></i>
  155. </span>
  156. </div>
  157. </div>
  158. <div class="reply-content">{{ reply.content }}</div>
  159. <div class="comment-imgs">
  160. <el-image
  161. v-for="(imgItem, index) in reply.image"
  162. :key="index"
  163. :src="imgItem"
  164. fit="contain"
  165. @click.stop="toShowImgViewer(reply.image, index)"
  166. ></el-image>
  167. </div>
  168. </div>
  169. </div>
  170. </div>
  171. </div>
  172. </div>
  173. </div>
  174. </div>
  175. <el-image-viewer
  176. v-if="showViewer"
  177. :initialIndex="initialIndex"
  178. @onClose="showViewer = false"
  179. :url-list="img_list"
  180. />
  181. <div class="check-more finger-point" v-if="showMore" @click="loadMore">
  182. 查看更多
  183. </div>
  184. </div>
  185. </template>
  186. <script>
  187. import ElImageViewer from "~/components/default/image-viewer";
  188. export default {
  189. props: {
  190. showMore: {
  191. type: Boolean,
  192. default: false,
  193. },
  194. comments: {
  195. type: Array,
  196. required: true,
  197. },
  198. comment_open: {},
  199. is_detail: {},
  200. },
  201. data() {
  202. return {
  203. showChild: false,
  204. img_list: [],
  205. showViewer: false,
  206. initialIndex: 0,
  207. isShowInput: false,
  208. replyUser: "",
  209. inputComment: "",//评论内容
  210. host_comment_id: "",
  211. second_comment_id: "",
  212. uploadUrl: "",
  213. fileList: [],
  214. imgData: [],
  215. options_type: "",
  216. move_flag:true
  217. };
  218. },
  219. components: {
  220. ElImageViewer,
  221. },
  222. mounted() {
  223. this.uploadUrl = this.fun.getRealUrl("upload.uploadPic", {});
  224. },
  225. methods: {
  226. move_comment_handle(dom=false){
  227. let ele=this.$refs.commentRoot,y,arr=[];
  228. if(dom){
  229. this.$nextTick(()=>{
  230. for(let i = 0;i<ele.length;i++){
  231. if(ele[i]._prevClass.indexOf("move")!==-1){
  232. y = ele[i].offsetTop + document.documentElement.clientHeight/2;
  233. this.keep_move(ele[i].getBoundingClientRect().top)
  234. }
  235. }
  236. })
  237. }
  238. if(ele.length==1){
  239. y = ele[0].offsetHeight + document.documentElement.clientHeight/2;
  240. this.keep_move(y)
  241. }else if(ele && ele.length>1){
  242. for(let i = 0;i<ele.length;i++){
  243. if(ele[i]._prevClass.indexOf("move")!==-1){
  244. y = ele[i].offsetHeight + document.documentElement.clientHeight/2;
  245. this.keep_move(y)
  246. }else{
  247. arr.push(ele[i]._prevClass)
  248. }
  249. }
  250. }
  251. if(!arr.includes("move")){
  252. this.move_flag = false;
  253. this.loadMore(true)
  254. }
  255. },
  256. keep_move(y){
  257. let timer,speed=1;
  258. timer = setInterval(() => {
  259. speed+=10;
  260. if(speed>=y){
  261. window.scrollTo(0,y)
  262. clearInterval(timer)
  263. }else{
  264. window.scrollTo(0,speed)
  265. }
  266. }, 10);
  267. },
  268. pasting(event) {
  269. let txt = event.clipboardData.getData("Text");
  270. if (typeof txt == "string") {
  271. this.inputComment += txt;
  272. }
  273. let file = null;
  274. const items = (event.clipboardData || window.clipboardData).items;
  275. if (items.length) {
  276. for (let i = 0; i < items.length; i++) {
  277. if (items[i].type.indexOf("image") !== -1) {
  278. file = items[i].getAsFile();
  279. this.handleChange(file);
  280. if (!this.canUpload) {
  281. this.canUpload = !this.canUpload;
  282. }
  283. break;
  284. }
  285. }
  286. }
  287. },
  288. // 上传
  289. handleChange(file, filelist) {
  290. let formData = new FormData();
  291. formData.append("file", file.raw || file);
  292. this.fun
  293. .$post(
  294. "/addons/yun_shop/api.php?i=1&type=5&shop_id=null&route=upload.uploadPic",
  295. formData
  296. )
  297. .then((res) => {
  298. if(res.result==1){
  299. this.fileList.push({url:res.data.img_url,hasSuccess: true})
  300. this.imgData.push({ url: res.data.img_url, uid: file.uid })
  301. }else{
  302. this.$message.error(res.msg);
  303. }
  304. })
  305. .catch((err) => {
  306. console.log(err);
  307. });
  308. },
  309. toShowImgViewer(list, index) {
  310. this.img_list = list;
  311. this.initialIndex = index;
  312. this.showViewer = true;
  313. },
  314. submitUpload() {
  315. this.$refs["upload"].$children[1].$refs.input.click();
  316. },
  317. checkAllSuccess() {
  318. // 检查图片是否上传完成
  319. return Object.keys(this.fileList).every(
  320. (item) => this.fileList[item].hasSuccess
  321. );
  322. },
  323. handleImgChange(file, fileList) {
  324. this.fileList = fileList;
  325. },
  326. handleFrontSuccess(res, file, fileList) {
  327. if (res.result == 1) {
  328. this.fileList.map((item, index) => {
  329. if (item.uid == file.uid) {
  330. this.fileList[index].hasSuccess = true;
  331. this.imgData[index] = { url: res.data.img_url, uid: file.uid };
  332. }
  333. });
  334. console.log(fileList,this.imgData)
  335. } else {
  336. let ind = 0;
  337. this.fileList.map((item, index) => {
  338. if (item.uid == file.uid) {
  339. ind = index;
  340. }
  341. });
  342. this.fileList.splice(ind, 1);
  343. fileList.splice(ind, 1);
  344. this.$message.error(res.msg);
  345. }
  346. },
  347. handleRemove(file) {
  348. this.imgData = this.imgData.filter((item) => {
  349. return item.uid != file.uid;
  350. });
  351. this.fileList = this.fileList.filter((item) => {
  352. return item.uid != file.uid;
  353. });
  354. },
  355. // 点赞
  356. praiseBtn(host_comment_id, second_comment_id, index, chilindex, is_praise) {
  357. let data = {
  358. host_comment_id,
  359. second_comment_id,
  360. index,
  361. chilindex,
  362. is_praise,
  363. };
  364. this.$emit("praiseBtn", data);
  365. },
  366. // 赞赏
  367. moneyShow(item) {
  368. this.$emit("moneyShow", item);
  369. },
  370. deleteBtn(item) {
  371. this.$emit("delReply", item);
  372. },
  373. // 加载更多
  374. loadMore(position=false) {
  375. this.$emit("loadMore",position);
  376. },
  377. // 取消
  378. cancel() {
  379. this.host_comment_id = "";
  380. this.second_comment_id = "";
  381. this.replyUser = "";
  382. this.isShowInput = false;
  383. },
  384. // 提交评论
  385. commitComment() {
  386. if (!this.inputComment) {
  387. this.$message.error("不能发送空白信息");
  388. return;
  389. }
  390. if (!this.checkAllSuccess()) {
  391. this.$message("请等待所有图片上传成功!");
  392. return;
  393. }
  394. let _json = {
  395. content: this.inputComment,
  396. image: this.imgData.map((item) => {
  397. return item.url;
  398. }),
  399. host_comment_id: this.host_comment_id,
  400. second_comment_id: this.second_comment_id,
  401. };
  402. if (this.options_type) {
  403. _json.options_type = this.options_type;
  404. }
  405. this.$emit("confirm", _json);
  406. this.inputComment = "";
  407. this.imgData = [];
  408. this.fileList = [];
  409. this.$refs.upload.clearFiles();
  410. },
  411. // 显示输入框
  412. showCommentInput(item, reply, flag) {
  413. if (reply) {
  414. this.second_comment_id = reply.id;
  415. this.host_comment_id = item.id;
  416. this.options_type = "childReply";
  417. this.replyUser = "回复@" + reply.uname + " ";
  418. } else if (item) {
  419. this.second_comment_id = "";
  420. this.host_comment_id = item.id;
  421. this.options_type = "reply";
  422. this.replyUser = "回复@" + item.uname + " ";
  423. } else {
  424. this.host_comment_id = "";
  425. this.second_comment_id = "";
  426. this.options_type = "";
  427. this.replyUser = "";
  428. }
  429. this.inputComment = "";
  430. this.isShowInput = true;
  431. if (flag != "noFocus") {
  432. this.$nextTick(() => {
  433. this.$refs.inputComment.focus();
  434. });
  435. }
  436. },
  437. },
  438. updated(){
  439. if(this.$route.query.move && this.move_flag){
  440. this.move_comment_handle()
  441. }
  442. }
  443. };
  444. </script>
  445. <style lang="scss" rel="stylesheet/scss">
  446. $color-main: #409eff;
  447. $text-main: #303133;
  448. $text-minor: #909399; //次要文字
  449. .container {
  450. padding: 0 10px;
  451. box-sizing: border-box;
  452. .write-reply {
  453. display: flex;
  454. align-items: center;
  455. font-size: 14px;
  456. color: $text-minor;
  457. padding: 10px;
  458. cursor: pointer;
  459. &:hover {
  460. color: $text-main;
  461. }
  462. .el-icon-edit {
  463. margin-right: 5px;
  464. }
  465. }
  466. .input-wrapper {
  467. padding: 0 10px 10px 10px;
  468. .reply-box {
  469. margin-bottom: 5px;
  470. color: var(--color);
  471. }
  472. .gray-bg-input,
  473. .el-input__inner {
  474. background-color: #296fd8;
  475. }
  476. .btn-control {
  477. display: flex;
  478. justify-content: space-between;
  479. align-items: flex-end;
  480. padding-top: 10px;
  481. .el-icon-picture {
  482. font-size: 24px;
  483. color: #ababab;
  484. border-radius: 8px;
  485. overflow: hidden;
  486. }
  487. .cancel {
  488. font-size: 14px;
  489. color: #606266;
  490. margin-right: 20px;
  491. cursor: pointer;
  492. &:hover {
  493. color: #333;
  494. }
  495. }
  496. .confirm {
  497. font-size: 14px;
  498. padding: 6px 20px;
  499. }
  500. }
  501. }
  502. .move{}
  503. .comment {
  504. display: flex;
  505. flex-direction: column;
  506. padding: 10px;
  507. border-bottom: 1px solid #f2f6fc;
  508. .info {
  509. display: flex;
  510. // align-items: center;
  511. .avatar {
  512. width: 40px;
  513. height: 40px;
  514. border-radius: 50%;
  515. }
  516. .right-box {
  517. display: flex;
  518. align-items: center;
  519. .name {
  520. font-size: 14px;
  521. color: $text-main;
  522. font-weight: 500;
  523. }
  524. .date {
  525. font-size: 12px;
  526. margin-left: 20px;
  527. color: $text-minor;
  528. }
  529. .control {
  530. flex: 1;
  531. text-align: right;
  532. }
  533. .control span {
  534. margin-right: 10px;
  535. }
  536. }
  537. }
  538. .comment-content {
  539. word-break: break-all;
  540. font-size: 16px;
  541. color: $text-main;
  542. line-height: 20px;
  543. padding: 10px 0;
  544. }
  545. .comment-imgs {
  546. display: flex;
  547. flex-wrap: wrap;
  548. .el-image {
  549. cursor: pointer;
  550. width: 60px;
  551. height: 60px;
  552. border-radius: 2px;
  553. margin: 0 10px 10px 0;
  554. }
  555. }
  556. .look-child {
  557. padding-left: 10px;
  558. color: #666666;
  559. }
  560. .look-child:hover {
  561. color: var(--color);
  562. }
  563. .reply {
  564. margin: 10px 0;
  565. border-left: 2px solid #dcdfe6;
  566. .item {
  567. margin: 0 10px;
  568. padding: 10px 0;
  569. border-bottom: 1px dashed #ebeef5;
  570. .reply-avatar {
  571. width: 40px;
  572. height: 40px;
  573. border-radius: 50%;
  574. margin-right: 10px;
  575. }
  576. .reply-title {
  577. .from-name {
  578. color: $color-main;
  579. }
  580. .to-name {
  581. color: $color-main;
  582. margin-left: 5px;
  583. margin-right: 5px;
  584. }
  585. .reply-time {
  586. font-size: 12px;
  587. color: $text-minor;
  588. }
  589. }
  590. .reply-content {
  591. font-size: 14px;
  592. color: $text-main;
  593. padding: 6px 0;
  594. }
  595. }
  596. }
  597. }
  598. .check-more {
  599. padding-top: 10px;
  600. text-align: center;
  601. color: #666666;
  602. }
  603. .check-more:hover {
  604. color: var(--color);
  605. }
  606. }
  607. </style>