@jx3box/jx3box-ui 2.2.15 → 2.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,6 +93,8 @@ export default {
93
93
  jdt: "Top 100 Dungeons",
94
94
  wiki: "Wiki",
95
95
  cj: "Achievement Wiki",
96
+ achievement: "Achievement Wiki",
97
+ treasure: "Adventure Wiki",
96
98
  item: "Item Wiki",
97
99
  quest: "Quest Wiki",
98
100
  knowledge: "General Wiki",
@@ -321,9 +323,15 @@ export default {
321
323
  },
322
324
  commentUpload: {
323
325
  tip: "Upload up to {count} {types} images, each no larger than {size} MB",
326
+ tipWithVideo: "Upload up to {count} attachments. Supported: {types}. Images up to {imageSize} MB, videos up to {videoSize} MB",
324
327
  exceed: "You can upload up to {count} images!",
328
+ exceedWithVideo: "You can upload up to {count} attachments!",
329
+ onlyTypes: "Only {types} image formats are supported!",
330
+ onlyTypesWithVideo: "Only {types} image and video formats are supported!",
325
331
  maxSize: "Each image must be no larger than {size} MB!",
332
+ maxSizeVideo: "Each video must be no larger than {size} MB!",
326
333
  uploadFailed: "Image upload failed!",
334
+ uploadFailedWithVideo: "Attachment upload failed!",
327
335
  },
328
336
  quickReply: { title: "Quick Reply" },
329
337
  replyForReply: { reply: "Reply", placeholder: "Enter reply...", submit: "Submit", collapse: "Collapse" },
@@ -427,6 +435,7 @@ export default {
427
435
  adminDrop: {
428
436
  manage: "Manage",
429
437
  setting: "Settings",
438
+ edit: "Edit",
430
439
  message: "Message",
431
440
  push: "Push",
432
441
  pictureTask: "Refresh Image",
@@ -93,6 +93,8 @@ export default {
93
93
  jdt: "Top 100 Bí Cảnh",
94
94
  wiki: "Wiki",
95
95
  cj: "Wiki Thành Tựu",
96
+ achievement: "Wiki Thành Tựu",
97
+ treasure: "Wiki Kỳ Ngộ",
96
98
  item: "Wiki Vật Phẩm",
97
99
  quest: "Wiki Nhiệm Vụ",
98
100
  knowledge: "Wiki Kiến Thức Chung",
@@ -309,9 +311,15 @@ export default {
309
311
  commentWithReply: { defaultName: "Hiệp khách vô danh", top: "Ghim", star: "Nổi bật", secret: "Bí mật", commentSuccess: "Bình luận thành công!", deleteSuccess: "Xóa thành công!" },
310
312
  commentUpload: {
311
313
  tip: "Tải tối đa {count} ảnh định dạng {types}, mỗi ảnh không quá {size} MB",
314
+ tipWithVideo: "Tải tối đa {count} tệp đính kèm. Hỗ trợ: {types}. Ảnh không quá {imageSize} MB, video không quá {videoSize} MB",
312
315
  exceed: "Chỉ được tải tối đa {count} ảnh!",
316
+ exceedWithVideo: "Chỉ được tải tối đa {count} tệp đính kèm!",
317
+ onlyTypes: "Chỉ hỗ trợ ảnh định dạng {types}!",
318
+ onlyTypesWithVideo: "Chỉ hỗ trợ ảnh và video định dạng {types}!",
313
319
  maxSize: "Mỗi ảnh không được vượt quá {size} MB!",
320
+ maxSizeVideo: "Mỗi video không được vượt quá {size} MB!",
314
321
  uploadFailed: "Tải ảnh thất bại!",
322
+ uploadFailedWithVideo: "Tải tệp đính kèm thất bại!",
315
323
  },
316
324
  quickReply: { title: "Trả lời nhanh" },
317
325
  replyForReply: { reply: "Trả lời", placeholder: "Nhập trả lời...", submit: "Gửi", collapse: "Thu gọn" },
@@ -415,6 +423,7 @@ export default {
415
423
  adminDrop: {
416
424
  manage: "Quản lý",
417
425
  setting: "Cài đặt",
426
+ edit: "Chỉnh sửa",
418
427
  message: "Nhắn tin",
419
428
  push: "Đẩy",
420
429
  pictureTask: "Làm mới ảnh",
@@ -93,6 +93,8 @@ export default {
93
93
  jdt: "秘境百强",
94
94
  wiki: "百科",
95
95
  cj: "成就百科",
96
+ achievement: "成就百科",
97
+ treasure: "奇遇百科",
96
98
  item: "物品百科",
97
99
  quest: "任务百科",
98
100
  knowledge: "通识百科",
@@ -379,9 +381,15 @@ export default {
379
381
  },
380
382
  commentUpload: {
381
383
  tip: "最多上传 {count} 张 {types} 格式图片,单张图片不能超过 {size} MB",
384
+ tipWithVideo: "最多上传 {count} 个附件,支持 {types},图片不超过 {imageSize} MB,视频不超过 {videoSize} MB",
382
385
  exceed: "最多上传 {count} 张图片!",
386
+ exceedWithVideo: "最多上传 {count} 个附件!",
387
+ onlyTypes: "仅支持 {types} 格式图片!",
388
+ onlyTypesWithVideo: "仅支持 {types} 格式的图片和视频!",
383
389
  maxSize: "单张图片大小不能超过 {size} MB!",
390
+ maxSizeVideo: "单个视频大小不能超过 {size} MB!",
384
391
  uploadFailed: "图片上传失败!",
392
+ uploadFailedWithVideo: "附件上传失败!",
385
393
  },
386
394
  quickReply: {
387
395
  title: "快捷回复",
@@ -541,6 +549,7 @@ export default {
541
549
  adminDrop: {
542
550
  manage: "管理",
543
551
  setting: "设置",
552
+ edit: "编辑",
544
553
  message: "私信",
545
554
  push: "推送",
546
555
  pictureTask: "刷图",
@@ -93,6 +93,8 @@ export default {
93
93
  jdt: "秘境百強",
94
94
  wiki: "百科",
95
95
  cj: "成就百科",
96
+ achievement: "成就百科",
97
+ treasure: "奇遇百科",
96
98
  item: "物品百科",
97
99
  quest: "任務百科",
98
100
  knowledge: "通識百科",
@@ -314,9 +316,15 @@ export default {
314
316
  commentWithReply: { defaultName: "無名小俠", top: "置頂", star: "精華", secret: "悄悄話", commentSuccess: "評論成功!", deleteSuccess: "刪除成功!" },
315
317
  commentUpload: {
316
318
  tip: "最多上傳 {count} 張 {types} 格式圖片,單張圖片不能超過 {size} MB",
319
+ tipWithVideo: "最多上傳 {count} 個附件,支援 {types},圖片不超過 {imageSize} MB,影片不超過 {videoSize} MB",
317
320
  exceed: "最多上傳 {count} 張圖片!",
321
+ exceedWithVideo: "最多上傳 {count} 個附件!",
322
+ onlyTypes: "僅支援 {types} 格式圖片!",
323
+ onlyTypesWithVideo: "僅支援 {types} 格式的圖片和影片!",
318
324
  maxSize: "單張圖片大小不能超過 {size} MB!",
325
+ maxSizeVideo: "單個影片大小不能超過 {size} MB!",
319
326
  uploadFailed: "圖片上傳失敗!",
327
+ uploadFailedWithVideo: "附件上傳失敗!",
320
328
  },
321
329
  quickReply: { title: "快捷回覆" },
322
330
  replyForReply: { reply: "回覆", placeholder: "輸入回覆...", submit: "提交", collapse: "收起" },
@@ -420,6 +428,7 @@ export default {
420
428
  adminDrop: {
421
429
  manage: "管理",
422
430
  setting: "設定",
431
+ edit: "編輯",
423
432
  message: "私訊",
424
433
  push: "推送",
425
434
  pictureTask: "刷圖",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jx3box/jx3box-ui",
3
- "version": "2.2.15",
3
+ "version": "2.2.17",
4
4
  "description": "JX3BOX Vue3 UI",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,6 +8,10 @@ const KEY = "cmt_order";
8
8
  const { __Links } = JX3BOX;
9
9
  const $ = $next({ interceptor: false, mute: true });
10
10
 
11
+ function getLocalOrderMode() {
12
+ return localStorage.getItem(KEY) || "DESC";
13
+ }
14
+
11
15
  export async function getOrderMode() {
12
16
  if (User.isLogin()) {
13
17
  return $cms({ mute: true })
@@ -17,12 +21,14 @@ export async function getOrderMode() {
17
21
  },
18
22
  })
19
23
  .then((res) => {
20
- return res.data.data;
24
+ return res.data.data || getLocalOrderMode();
25
+ })
26
+ .catch(() => {
27
+ return getLocalOrderMode();
21
28
  });
22
29
  } else {
23
30
  return new Promise((resolve) => {
24
- const key = localStorage.getItem(KEY) || "DESC";
25
- resolve(key);
31
+ resolve(getLocalOrderMode());
26
32
  });
27
33
  }
28
34
  }
package/src/App.vue CHANGED
@@ -171,7 +171,7 @@
171
171
  <a>100473604</a>
172
172
  </strong>
173
173
  </RightSideMsg>
174
- <PostTopic type="bps" :id="48857"></PostTopic>
174
+ <PostTopic type="bps" :id="0"></PostTopic>
175
175
  <div id="directory"></div>
176
176
  <PostVersion :post="post"></PostVersion>
177
177
  <PostCollection :id="59" />
@@ -232,8 +232,8 @@ export default {
232
232
  return {
233
233
  tab: "content",
234
234
 
235
- post_id: "23240",
236
- post: "",
235
+ post_id: "19382",
236
+ post: {},
237
237
  client: location.href.includes("origin") ? "origin" : "std",
238
238
  item1: null,
239
239
  item2: null,
@@ -249,6 +249,7 @@ export default {
249
249
 
250
250
  // 通用
251
251
  userpop: false,
252
+ uploadBanner: "",
252
253
  homeworkVisible: false,
253
254
 
254
255
  showSuspendCommon: false,
@@ -277,10 +278,12 @@ export default {
277
278
  console.log(data);
278
279
  },
279
280
  loadPost() {
280
- getPost(this.post_id).then((res) => {
281
- this.post = res.data.data;
282
- this.$forceUpdate();
283
- });
281
+ getPost(this.post_id)
282
+ .then((res) => {
283
+ this.post = res.data.data;
284
+ this.$forceUpdate();
285
+ })
286
+ .catch(() => {});
284
287
  },
285
288
  filterMeta(val) {
286
289
  console.log(val);
@@ -336,19 +339,23 @@ export default {
336
339
  },
337
340
  loadCommunity: function () {
338
341
  // 72
339
- getTopicDetails(4339).then((res) => {
340
- this.community = res.data.data;
341
- });
342
+ getTopicDetails(4339)
343
+ .then((res) => {
344
+ this.community = res.data.data;
345
+ })
346
+ .catch(() => {});
342
347
  },
343
348
  loadWiki() {
344
- wiki.mix({ type: "achievement", id: 12572, client: this.client }).then((res) => {
345
- const { post, source, compatible, isEmpty, users } = res;
346
- this.wiki_post = {
347
- post: post,
348
- source: source,
349
- users,
350
- };
351
- });
349
+ wiki.mix({ type: "achievement", id: 12572, client: this.client })
350
+ .then((res) => {
351
+ const { post, source, compatible, isEmpty, users } = res;
352
+ this.wiki_post = {
353
+ post: post,
354
+ source: source,
355
+ users,
356
+ };
357
+ })
358
+ .catch(() => {});
352
359
  },
353
360
  },
354
361
  mounted() {
@@ -59,7 +59,7 @@ export default {
59
59
  });
60
60
  },
61
61
  showIcon(medal) {
62
- return __cdn + "design/medals/user/" + medal + ".gif";
62
+ return __cdn + "design/medals/user/" + medal + ".webp";
63
63
  },
64
64
  getMedalLink(medal) {
65
65
  if (medal.medal_url) return `${__Root}${medal.medal_url}`;
@@ -2,14 +2,26 @@
2
2
  <div class="u-cmt">
3
3
  <div class="u-text" v-if="content != ''" v-html="renderContent"></div>
4
4
  <div class="u-attachements" v-if="attachments.length">
5
- <el-image
6
- v-for="url in attachments"
7
- :key="url"
8
- :src="showAttachment(url)"
9
- :preview-src-list="[showPreview(url)]"
10
- lazy
11
- ></el-image>
5
+ <template v-for="url in attachments" :key="url">
6
+ <video
7
+ v-if="isVideoAttachment(url)"
8
+ class="u-attachment-video"
9
+ :src="showPreview(url)"
10
+ playsinline
11
+ preload="metadata"
12
+ @click="openVideoPreview(url)"
13
+ ></video>
14
+ <el-image
15
+ v-else
16
+ :src="showAttachment(url)"
17
+ :preview-src-list="[showPreview(url)]"
18
+ lazy
19
+ ></el-image>
20
+ </template>
12
21
  </div>
22
+ <el-dialog v-model="videoPreview.visible" class="u-comment-video-dialog" width="min(92vw, 760px)">
23
+ <video class="u-comment-video-preview" :src="videoPreview.url" controls playsinline></video>
24
+ </el-dialog>
13
25
  <div class="u-toolbar u-toolbar--primary">
14
26
  <div class="u-toolbar-left">
15
27
  <el-button v-if="!currentUserHadLike" link size="small" @click="doLike(true)"
@@ -122,6 +134,7 @@
122
134
  <Uploader
123
135
  v-if="showUploader"
124
136
  ref="uploader"
137
+ :support-video="supportVideo"
125
138
  @onFinish="attachmentUploadFinish"
126
139
  @onError="attachmentUplodError"
127
140
  />
@@ -146,6 +159,8 @@ import { formatContent } from "../../utils/emotion";
146
159
  import Emotion from "@jx3box/jx3box-emotion/src/Emotion2.vue";
147
160
  import i18nMixin from "../../i18n/mixin";
148
161
 
162
+ const VIDEO_EXTENSIONS = ["mp4", "mov", "webm", "m4v"];
163
+
149
164
  function fillZero(num) {
150
165
  return num > 9 ? num : `0${num}`;
151
166
  }
@@ -212,6 +227,10 @@ export default {
212
227
  type: [Boolean, Number],
213
228
  default: false,
214
229
  },
230
+ supportVideo: {
231
+ type: Boolean,
232
+ default: false,
233
+ },
215
234
  },
216
235
  components: {
217
236
  Uploader,
@@ -229,6 +248,10 @@ export default {
229
248
  previewList: [],
230
249
  currentUserHadLike: this.isLike,
231
250
  hasLikeCount: this.likes,
251
+ videoPreview: {
252
+ url: "",
253
+ visible: false,
254
+ },
232
255
 
233
256
  renderContent: "",
234
257
  };
@@ -368,7 +391,19 @@ export default {
368
391
  showPreview: function (val) {
369
392
  return resolveImagePath(val);
370
393
  },
394
+ openVideoPreview(val) {
395
+ this.videoPreview = {
396
+ url: this.showPreview(val),
397
+ visible: true,
398
+ };
399
+ },
400
+ isVideoAttachment(val) {
401
+ const cleanUrl = this.showPreview(val).split("?")[0].split("#")[0];
402
+ const ext = cleanUrl.split(".").pop()?.toLowerCase() || "";
403
+ return VIDEO_EXTENSIONS.includes(ext);
404
+ },
371
405
  showAttachment: function (val) {
406
+ if (this.isVideoAttachment(val)) return this.showPreview(val);
372
407
  return resolveImagePath(val) + "?x-oss-process=style/comment_thumb";
373
408
  },
374
409
  },
@@ -446,6 +481,17 @@ export default {
446
481
  .el-image {
447
482
  margin-right: 20px;
448
483
  }
484
+ .u-attachment-video {
485
+ display: inline-block;
486
+ width: 180px;
487
+ height: 120px;
488
+ max-width: 100%;
489
+ margin-right: 20px;
490
+ object-fit: cover;
491
+ cursor: pointer;
492
+ background: #000;
493
+ vertical-align: top;
494
+ }
449
495
  }
450
496
  }
451
497
  .u-up {
@@ -479,6 +525,25 @@ export default {
479
525
  }
480
526
  }
481
527
 
528
+ .u-comment-video-dialog {
529
+ margin-top: 5vh;
530
+
531
+ .el-dialog__body {
532
+ max-height: calc(100vh - 120px);
533
+ padding: 12px;
534
+ overflow: hidden;
535
+ }
536
+ }
537
+
538
+ .u-comment-video-preview {
539
+ max-width: 100%;
540
+ max-height: calc(100vh - 160px);
541
+ display: block;
542
+ margin: 0 auto;
543
+ object-fit: contain;
544
+ background: #000;
545
+ }
546
+
482
547
  .c-comment-subbox {
483
548
  margin-top: 10px;
484
549
  .el-form-item {
@@ -11,14 +11,26 @@
11
11
  <!-- <p v-for="(p, index) in getPList(content)" :key="index" v-html="formatContent(p)"></p> -->
12
12
  </div>
13
13
  <div class="u-attachements" v-if="attachments.length">
14
- <el-image
15
- v-for="url in attachments"
16
- :key="url"
17
- :src="showAttachment(url)"
18
- :preview-src-list="[showPreview(url)]"
19
- lazy
20
- ></el-image>
14
+ <template v-for="url in attachments" :key="url">
15
+ <video
16
+ v-if="isVideoAttachment(url)"
17
+ class="u-attachment-video"
18
+ :src="showPreview(url)"
19
+ playsinline
20
+ preload="metadata"
21
+ @click="openVideoPreview(url)"
22
+ ></video>
23
+ <el-image
24
+ v-else
25
+ :src="showAttachment(url)"
26
+ :preview-src-list="[showPreview(url)]"
27
+ lazy
28
+ ></el-image>
29
+ </template>
21
30
  </div>
31
+ <el-dialog v-model="videoPreview.visible" class="u-comment-video-dialog" width="min(92vw, 760px)">
32
+ <video class="u-comment-video-preview" :src="videoPreview.url" controls playsinline></video>
33
+ </el-dialog>
22
34
  <div class="u-toolbar">
23
35
  <el-button class="u-admin" v-if="!currentUserHadLike" link size="small" @click="doLike(true)"
24
36
  ><img class="u-up" svg-inline src="../../assets/img/comment/heart_1.svg" alt=""/>{{
@@ -73,6 +85,9 @@
73
85
  import { resolveImagePath, authorLink } from "@jx3box/jx3box-common/js/utils";
74
86
  import { formatContent } from "../../utils/emotion";
75
87
  import i18nMixin from "../../i18n/mixin";
88
+
89
+ const VIDEO_EXTENSIONS = ["mp4", "mov", "webm", "m4v"];
90
+
76
91
  function fillZero(num) {
77
92
  return num > 9 ? num : `0${num}`;
78
93
  }
@@ -137,6 +152,10 @@ export default {
137
152
  showInput: false,
138
153
  currentUserHadLike: this.isLike,
139
154
  hasLikeCount: this.likes,
155
+ videoPreview: {
156
+ url: "",
157
+ visible: false,
158
+ },
140
159
 
141
160
  renderContent: "",
142
161
  };
@@ -160,9 +179,21 @@ export default {
160
179
  profileLink: function (uid) {
161
180
  return authorLink(uid);
162
181
  },
182
+ isVideoAttachment(val) {
183
+ const cleanUrl = this.showPreview(val).split("?")[0].split("#")[0];
184
+ const ext = cleanUrl.split(".").pop()?.toLowerCase() || "";
185
+ return VIDEO_EXTENSIONS.includes(ext);
186
+ },
163
187
  showAttachment: function (val) {
188
+ if (this.isVideoAttachment(val)) return this.showPreview(val);
164
189
  return resolveImagePath(val) + "?x-oss-process=style/comment_thumb";
165
190
  },
191
+ openVideoPreview(val) {
192
+ this.videoPreview = {
193
+ url: this.showPreview(val),
194
+ visible: true,
195
+ };
196
+ },
166
197
  async formatContent(str) {
167
198
  this.renderContent = await formatContent(str);
168
199
  },
@@ -265,6 +296,16 @@ export default {
265
296
  }
266
297
  .u-attachements {
267
298
  margin-top: 10px;
299
+ .u-attachment-video {
300
+ display: inline-block;
301
+ width: 180px;
302
+ height: 120px;
303
+ max-width: 100%;
304
+ object-fit: cover;
305
+ cursor: pointer;
306
+ background: #000;
307
+ vertical-align: top;
308
+ }
268
309
  }
269
310
  .u-reply-label{
270
311
  font-size: 13px;
@@ -275,4 +316,23 @@ export default {
275
316
  }
276
317
  }
277
318
  }
319
+
320
+ .u-comment-video-dialog {
321
+ margin-top: 5vh;
322
+
323
+ .el-dialog__body {
324
+ max-height: calc(100vh - 120px);
325
+ padding: 12px;
326
+ overflow: hidden;
327
+ }
328
+ }
329
+
330
+ .u-comment-video-preview {
331
+ max-width: 100%;
332
+ max-height: calc(100vh - 160px);
333
+ display: block;
334
+ margin: 0 auto;
335
+ object-fit: contain;
336
+ background: #000;
337
+ }
278
338
  </style>
@@ -43,6 +43,7 @@
43
43
  <Uploader
44
44
  class="u-uploader"
45
45
  ref="uploader"
46
+ :support-video="supportVideo"
46
47
  @onFinish="attachmentUploadFinish"
47
48
  @onError="attachmentUploadError"
48
49
  v-if="showUploader"
@@ -75,6 +76,10 @@ export default {
75
76
  type: Boolean,
76
77
  default: false,
77
78
  },
79
+ supportVideo: {
80
+ type: Boolean,
81
+ default: false,
82
+ },
78
83
  },
79
84
  mounted() {
80
85
  if (this.isBottom) this.inputId = "textarea-bottom";
@@ -31,6 +31,7 @@
31
31
  :content="item.content"
32
32
  :comment-id="item.id"
33
33
  :attachments="stringToArray(item.attachments)"
34
+ :support-video="supportVideo"
34
35
  :can-delete="power.can_del || power.uid == item.userId"
35
36
  :can-set-top="(power.is_author || power.is_editor) && !item.is_top"
36
37
  :can-cancel-top="(power.is_author || power.is_editor) && item.is_top"
@@ -55,6 +56,7 @@
55
56
  :data="replyList"
56
57
  :pager="pager"
57
58
  :power="power"
59
+ :support-video="supportVideo"
58
60
  @addNewReply="addNewReply"
59
61
  @deleteReply="deleteReply"
60
62
  @goto="gotoReplyListIndex"
@@ -99,6 +101,10 @@ export default {
99
101
  type: String,
100
102
  default: "",
101
103
  },
104
+ supportVideo: {
105
+ type: Boolean,
106
+ default: false,
107
+ },
102
108
  },
103
109
  components: {
104
110
  CommentContent,
@@ -28,6 +28,7 @@
28
28
  <Uploader
29
29
  v-if="showUploader"
30
30
  ref="uploader"
31
+ :support-video="supportVideo"
31
32
  @onFinish="attachmentUploadFinish"
32
33
  @onError="attachmentUploadError"
33
34
  />
@@ -66,6 +67,10 @@ export default {
66
67
  type: [Number, String],
67
68
  default: 0,
68
69
  },
70
+ supportVideo: {
71
+ type: Boolean,
72
+ default: false,
73
+ },
69
74
  },
70
75
  data: function () {
71
76
  return {
@@ -33,6 +33,7 @@
33
33
  :username="reply.displayName"
34
34
  :user-href="profileLink(reply.userId)"
35
35
  :current-id="reply.id"
36
+ :support-video="supportVideo"
36
37
  @hideForm="showReplyForReplyFrom = false"
37
38
  @doReply="doReply"
38
39
  />
@@ -55,6 +56,10 @@ export default {
55
56
  type: Object,
56
57
  required: true,
57
58
  },
59
+ supportVideo: {
60
+ type: Boolean,
61
+ default: false,
62
+ },
58
63
  },
59
64
  components: {
60
65
  CommentAvatar,
@@ -6,6 +6,7 @@
6
6
  :key="reply.id"
7
7
  :reply="reply"
8
8
  :power="power"
9
+ :support-video="supportVideo"
9
10
  @deleteReply="deleteReply"
10
11
  @addReply="addReply"
11
12
  @setLikeComment="setLikeComment"
@@ -59,6 +60,10 @@ export default {
59
60
  total: 0,
60
61
  }),
61
62
  },
63
+ supportVideo: {
64
+ type: Boolean,
65
+ default: false,
66
+ },
62
67
  },
63
68
  components: {
64
69
  ReplyItem,
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div class="c-comment-upload">
3
3
  <el-upload
4
4
  action="https://cms.jx3box.com/api/cms/upload"
5
5
  ref="upload"
@@ -9,6 +9,7 @@
9
9
  :file-list="fileList"
10
10
  :limit="maxCount"
11
11
  :accept="acceptedExtensions.reduce((acc, cur) => acc + `.${cur},`, '')"
12
+ :headers="uploadHeaders"
12
13
  multiple
13
14
  with-credentials
14
15
  :on-exceed="onExceed"
@@ -18,58 +19,134 @@
18
19
  :on-error="onError"
19
20
  >
20
21
  <el-icon><Plus></Plus></el-icon>
21
- <template #tip>
22
- <div class="el-upload__tip">
23
- {{
24
- $jx3boxT(
25
- "jx3boxUi.commentUpload.tip",
26
- "最多上传 {count} 张 {types} 格式图片,单张图片不能超过 {size} MB",
27
- {
28
- count: maxCount,
29
- types: acceptedExtensions.join(" / ").toUpperCase(),
30
- size: maxSize / 1024 / 1024,
31
- }
32
- )
33
- }}
22
+ <template #file="{ file }">
23
+ <div class="u-file-card">
24
+ <video
25
+ v-if="isVideoFile(file)"
26
+ class="u-file-video"
27
+ :src="showFilePreview(file)"
28
+ muted
29
+ playsinline
30
+ preload="metadata"
31
+ ></video>
32
+ <img v-else class="el-upload-list__item-thumbnail" :src="showFilePreview(file)" alt="" />
33
+ <span class="el-upload-list__item-actions">
34
+ <span class="el-upload-list__item-preview" @click.stop="handlePictureCardPreview(file)">
35
+ <el-icon><ZoomIn /></el-icon>
36
+ </span>
37
+ <span class="el-upload-list__item-delete" @click.stop="handleFileRemove(file)">
38
+ <el-icon><Delete /></el-icon>
39
+ </span>
40
+ </span>
34
41
  </div>
35
42
  </template>
43
+ <template #tip>
44
+ <div class="el-upload__tip">{{ tipText }}</div>
45
+ </template>
36
46
  </el-upload>
37
- <el-dialog v-model="dialogVisible">
38
- <img width="60%" :src="dialogImageUrl" alt />
47
+ <el-dialog v-model="dialogVisible" class="u-preview-dialog" width="min(92vw, 760px)">
48
+ <img v-if="!dialogMediaIsVideo" class="u-preview-image" :src="dialogMediaUrl" alt />
49
+ <video v-else class="u-preview-video" :src="dialogMediaUrl" controls playsinline></video>
39
50
  </el-dialog>
40
51
  </div>
41
52
  </template>
42
53
 
43
54
  <script>
44
55
  import i18nMixin from "../../i18n/mixin";
56
+ import { getTokenFromUrl } from "@jx3box/jx3box-common/js/utils";
57
+
58
+ const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif"];
59
+ const VIDEO_EXTENSIONS = ["mp4", "mov", "webm", "m4v"];
60
+
45
61
  export default {
46
62
  name: "CommentUploader",
47
63
  mixins: [i18nMixin],
64
+ props: {
65
+ supportVideo: {
66
+ type: Boolean,
67
+ default: false,
68
+ },
69
+ },
48
70
  data() {
49
71
  return {
50
- dialogImageUrl: "",
72
+ dialogMediaUrl: "",
73
+ dialogMediaIsVideo: false,
51
74
  dialogVisible: false,
52
75
  fileList: [],
53
76
  successList: [],
54
77
  uploadedMap: {},
55
78
 
56
- acceptedExtensions: ["jpg", "jpeg", "png", "gif"],
57
79
  maxCount: 5,
58
- maxSize: 2 * 1024 * 1024,
80
+ imageMaxSize: 2 * 1024 * 1024,
81
+ videoMaxSize: 30 * 1024 * 1024,
59
82
  };
60
83
  },
61
84
  emits: ["onFinish", "onError"],
85
+ computed: {
86
+ acceptedExtensions() {
87
+ return this.supportVideo ? [...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS] : [...IMAGE_EXTENSIONS];
88
+ },
89
+ acceptedLabels() {
90
+ return this.acceptedExtensions;
91
+ },
92
+ tipText() {
93
+ if (this.supportVideo) {
94
+ return this.$jx3boxT(
95
+ "jx3boxUi.commentUpload.tipWithVideo",
96
+ "最多上传 {count} 个附件,支持 {types},图片不超过 {imageSize} MB,视频不超过 {videoSize} MB",
97
+ {
98
+ count: this.maxCount,
99
+ types: this.acceptedLabels.join(" / ").toUpperCase(),
100
+ imageSize: this.imageMaxSize / 1024 / 1024,
101
+ videoSize: this.videoMaxSize / 1024 / 1024,
102
+ }
103
+ );
104
+ }
105
+
106
+ return this.$jx3boxT("jx3boxUi.commentUpload.tip", "最多上传 {count} 张 {types} 格式图片,单张图片不能超过 {size} MB", {
107
+ count: this.maxCount,
108
+ types: this.acceptedLabels.join(" / ").toUpperCase(),
109
+ size: this.imageMaxSize / 1024 / 1024,
110
+ });
111
+ },
112
+ uploadHeaders() {
113
+ const token = this.readAuthToken();
114
+ if (!token) return {};
115
+ return {
116
+ Authorization: `Basic ${btoa(`${token}:cms common request`)}`,
117
+ };
118
+ },
119
+ },
62
120
  methods: {
121
+ readAuthToken() {
122
+ const tokenFromUrl = getTokenFromUrl();
123
+ if (tokenFromUrl) return tokenFromUrl;
124
+ if (typeof localStorage === "undefined") return "";
125
+ return localStorage.getItem("__token") || localStorage.getItem("token") || "";
126
+ },
63
127
  handlePictureCardPreview(file) {
64
- this.dialogImageUrl = file.url;
128
+ const source = this.showFilePreview(file);
129
+ this.dialogMediaUrl = source;
130
+ this.dialogMediaIsVideo = this.isVideoFile(file);
65
131
  this.dialogVisible = true;
66
132
  },
133
+ handleFileRemove(file) {
134
+ if (this.$refs.upload?.handleRemove) {
135
+ this.$refs.upload.handleRemove(file);
136
+ } else {
137
+ this.removeFile(file.uid);
138
+ }
139
+ },
67
140
  onExceed() {
68
141
  this.$notify({
69
142
  title: "",
70
- message: this.$jx3boxT("jx3boxUi.commentUpload.exceed", "最多上传 {count} 张图片!", {
71
- count: this.maxCount,
72
- }),
143
+ message: this.supportVideo
144
+ ? this.$jx3boxT("jx3boxUi.commentUpload.exceedWithVideo", "最多上传 {count} 个附件!", {
145
+ count: this.maxCount,
146
+ })
147
+ : this.$jx3boxT("jx3boxUi.commentUpload.exceed", "最多上传 {count} 张图片!", {
148
+ count: this.maxCount,
149
+ }),
73
150
  type: "error",
74
151
  duration: 3000,
75
152
  position: "bottom-right",
@@ -83,11 +160,13 @@ export default {
83
160
  if (!this.acceptedExtensions.includes(ext)) {
84
161
  this.$notify({
85
162
  title: "",
86
- message: this.$jx3boxT(
87
- "jx3boxUi.commentUpload.onlyTypes",
88
- "仅支持 {types} 格式图片!",
89
- { types: this.acceptedExtensions.join(" / ").toUpperCase() }
90
- ),
163
+ message: this.supportVideo
164
+ ? this.$jx3boxT("jx3boxUi.commentUpload.onlyTypesWithVideo", "仅支持 {types} 格式的图片和视频!", {
165
+ types: this.acceptedLabels.join(" / ").toUpperCase(),
166
+ })
167
+ : this.$jx3boxT("jx3boxUi.commentUpload.onlyTypes", "仅支持 {types} 格式图片!", {
168
+ types: this.acceptedLabels.join(" / ").toUpperCase(),
169
+ }),
91
170
  type: "error",
92
171
  duration: 3000,
93
172
  position: "bottom-right",
@@ -96,16 +175,18 @@ export default {
96
175
  return;
97
176
  }
98
177
 
99
- if (file.size > this.maxSize) {
178
+ const isVideo = this.isVideoFile(file);
179
+ const maxSize = isVideo ? this.videoMaxSize : this.imageMaxSize;
180
+ if (file.size > maxSize) {
100
181
  this.$notify({
101
182
  title: "",
102
- message: this.$jx3boxT(
103
- "jx3boxUi.commentUpload.maxSize",
104
- "单张图片大小不能超过 {size} MB!",
105
- {
106
- size: this.maxSize / 1024 / 1024,
107
- }
108
- ),
183
+ message: isVideo
184
+ ? this.$jx3boxT("jx3boxUi.commentUpload.maxSizeVideo", "单个视频大小不能超过 {size} MB!", {
185
+ size: this.videoMaxSize / 1024 / 1024,
186
+ })
187
+ : this.$jx3boxT("jx3boxUi.commentUpload.maxSize", "单张图片大小不能超过 {size} MB!", {
188
+ size: this.imageMaxSize / 1024 / 1024,
189
+ }),
109
190
  type: "error",
110
191
  duration: 3000,
111
192
  position: "bottom-right",
@@ -113,6 +194,10 @@ export default {
113
194
  this.removeFile(file.uid);
114
195
  return;
115
196
  }
197
+
198
+ if (!file.url && file.raw) {
199
+ file.url = URL.createObjectURL(file.raw);
200
+ }
116
201
  },
117
202
  onRemove(file, fileList) {
118
203
  this.fileList = fileList;
@@ -144,7 +229,9 @@ export default {
144
229
  onError() {
145
230
  this.$notify({
146
231
  title: "",
147
- message: this.$jx3boxT("jx3boxUi.commentUpload.uploadFailed", "图片上传失败!"),
232
+ message: this.supportVideo
233
+ ? this.$jx3boxT("jx3boxUi.commentUpload.uploadFailedWithVideo", "附件上传失败!")
234
+ : this.$jx3boxT("jx3boxUi.commentUpload.uploadFailed", "图片上传失败!"),
148
235
  type: "error",
149
236
  duration: 3000,
150
237
  position: "bottom-right",
@@ -157,6 +244,13 @@ export default {
157
244
  this.$refs.upload.handleStart(file);
158
245
  }
159
246
  },
247
+ isVideoFile(file) {
248
+ const ext = (file?.name || file?.url || "").split(".").pop()?.toLowerCase() || "";
249
+ return VIDEO_EXTENSIONS.includes(ext);
250
+ },
251
+ showFilePreview(file) {
252
+ return file?.url || (file?.raw ? URL.createObjectURL(file.raw) : "");
253
+ },
160
254
  removeFile(uid) {
161
255
  this.fileList = this.fileList.filter((item) => item.uid !== uid);
162
256
  if (uid && this.uploadedMap[uid]) delete this.uploadedMap[uid];
@@ -165,3 +259,46 @@ export default {
165
259
  },
166
260
  };
167
261
  </script>
262
+
263
+ <style lang="less">
264
+ .c-comment-upload {
265
+ .u-file-card {
266
+ width: 100%;
267
+ height: 100%;
268
+ }
269
+
270
+ .u-file-video {
271
+ width: 100%;
272
+ height: 100%;
273
+ display: block;
274
+ object-fit: cover;
275
+ background: #f5f7fa;
276
+ }
277
+
278
+ }
279
+
280
+ .u-preview-dialog {
281
+ margin-top: 5vh;
282
+
283
+ .el-dialog__body {
284
+ max-height: calc(100vh - 120px);
285
+ padding: 12px;
286
+ overflow: hidden;
287
+ text-align: center;
288
+ }
289
+ }
290
+
291
+ .u-preview-image,
292
+ .u-preview-video {
293
+ max-width: 100%;
294
+ max-height: calc(100vh - 160px);
295
+ display: block;
296
+ margin: 0 auto;
297
+ object-fit: contain;
298
+ }
299
+
300
+ .u-preview-video {
301
+ width: auto;
302
+ background: #000;
303
+ }
304
+ </style>
@@ -144,12 +144,23 @@ export default {
144
144
  },
145
145
  methods: {
146
146
  loadData: function () {
147
- getPostBoxcoinRecords(this.postType, this.postId, this.params).then((res) => {
148
- this.list = res.data.data?.list || [];
149
- this.total = res.data.data.page.total;
150
- this.boxcoin = res.data.data.fromManager + res.data.data.fromUser;
151
- this.$emit("update:boxcoin", this.boxcoin);
152
- });
147
+ this.loading = true;
148
+ getPostBoxcoinRecords(this.postType, this.postId, this.params)
149
+ .then((res) => {
150
+ this.list = res.data.data?.list || [];
151
+ this.total = res.data.data.page.total;
152
+ this.boxcoin = res.data.data.fromManager + res.data.data.fromUser;
153
+ this.$emit("update:boxcoin", this.boxcoin);
154
+ })
155
+ .catch(() => {
156
+ this.list = [];
157
+ this.total = 0;
158
+ this.boxcoin = 0;
159
+ this.$emit("update:boxcoin", 0);
160
+ })
161
+ .finally(() => {
162
+ this.loading = false;
163
+ });
153
164
  },
154
165
  recovery: function (item, i) {
155
166
  this.$alert(
@@ -54,7 +54,7 @@
54
54
  <!-- 评论 -->
55
55
  <div ref="commentView" class="m-single-comment">
56
56
  <el-divider content-position="left">{{ $jx3boxT("jx3boxUi.cmsSingle.comment", "评论") }}</el-divider>
57
- <Comment :id="id" category="post" v-if="id && allow_comment" />
57
+ <Comment :id="id" category="post" :support-video="supportVideo" v-if="id && allow_comment" />
58
58
  <el-alert
59
59
  :title="$jx3boxT('jx3boxUi.cmsSingle.commentDisabled', '作者没有开启评论功能')"
60
60
  type="warning"
@@ -119,6 +119,10 @@ export default {
119
119
  type: Boolean,
120
120
  default: true,
121
121
  },
122
+ supportVideo: {
123
+ type: Boolean,
124
+ default: false,
125
+ },
122
126
  },
123
127
  data: function () {
124
128
  return {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <el-container class="c-comment" :class="{ 'c-comment-mask': showMask }" v-loading="loading">
3
3
  <el-main>
4
- <CommentInputForm @submit="userSubmitInputForm" />
4
+ <CommentInputForm :support-video="supportVideo" @submit="userSubmitInputForm" />
5
5
  <div class="c-comment-panel">
6
6
  <div class="u-order">
7
7
  <span class="u-label">{{ $jx3boxT("jx3boxUi.comment.sortMode", "排序模式:") }}</span>
@@ -44,6 +44,7 @@
44
44
  :item="item"
45
45
  :category="category"
46
46
  :power="commentPower"
47
+ :support-video="supportVideo"
47
48
  @deleteComment="deleteComment"
48
49
  @setTopComment="setTopComment"
49
50
  @setStarComment="setStarComment"
@@ -59,6 +60,7 @@
59
60
  <CommentInputForm
60
61
  @submit="userSubmitInputForm"
61
62
  :isBottom="commentList.length > 5"
63
+ :support-video="supportVideo"
62
64
  v-if="commentList.length > 5"
63
65
  />
64
66
  <div class="u-pages">
@@ -108,6 +110,10 @@ export default {
108
110
  type: String,
109
111
  default: "",
110
112
  },
113
+ supportVideo: {
114
+ type: Boolean,
115
+ default: false,
116
+ },
111
117
  },
112
118
  components: {
113
119
  // CommentAvatar,
@@ -249,56 +249,91 @@ export default {
249
249
  },
250
250
  },
251
251
  methods: {
252
+ resetBoxcoinQuota() {
253
+ this.admin_max = 0;
254
+ this.admin_min = 0;
255
+ this.admin_left = 0;
256
+ this.admin_total = 0;
257
+ this.admin_points = [100];
258
+ this.total_limit = 0;
259
+ this.post_type_used = 0;
260
+ this.user_left = 0;
261
+ this.user_points = [100];
262
+ },
252
263
  loadBoxcoinConfig: function () {
253
264
  User.isLogin() &&
254
- getPostBoxcoinConfig(this.postType).then((res) => {
255
- this.admin_max = res.data.data.limit.admin_max || 0;
256
- this.admin_min = res.data.data.limit.admin_min || 0;
257
- this.admin_points = res.data.data.limit.admin_points || [10, 1000];
258
- this.admin_left = res.data.data.asManagerBoxCoinRemain || 0;
259
- this.admin_total = res.data.data.asManagerBoxCoinTotal || 0;
260
- this.total_limit = res.data.data.limit.total_limit || 0;
261
- this.post_type_used = res.data.data.asPostTypeBoxcoinHasUsedTotalAtCurrentYear || 0;
265
+ getPostBoxcoinConfig(this.postType)
266
+ .then((res) => {
267
+ this.admin_max = res.data.data.limit.admin_max || 0;
268
+ this.admin_min = res.data.data.limit.admin_min || 0;
269
+ this.admin_points = res.data.data.limit.admin_points || [10, 1000];
270
+ this.admin_left = res.data.data.asManagerBoxCoinRemain || 0;
271
+ this.admin_total = res.data.data.asManagerBoxCoinTotal || 0;
272
+ this.total_limit = res.data.data.limit.total_limit || 0;
273
+ this.post_type_used = res.data.data.asPostTypeBoxcoinHasUsedTotalAtCurrentYear || 0;
262
274
 
263
- this.user_points = res.data.data.limit.user_points || [10, 1000];
264
- // 根据多端展示剩余币
265
- // 作品是n端,接受n端币+all币
266
- if (this.finalClient == "origin") {
267
- this.user_left = res.data.data.asUserBoxCoinRemainOrigin + res.data.data.asUserBoxCoinRemainAll;
268
- } else if (this.finalClient == "std") {
269
- this.user_left = res.data.data.asUserBoxCoinRemainStd + res.data.data.asUserBoxCoinRemainAll;
270
- } else {
271
- this.user_left =
272
- res.data.data.asUserBoxCoinRemainAll +
273
- res.data.data.asUserBoxCoinRemainStd +
274
- res.data.data.asUserBoxCoinRemainOrigin;
275
- }
275
+ this.user_points = res.data.data.limit.user_points || [10, 1000];
276
+ // 根据多端展示剩余币
277
+ // 作品是n端,接受n端币+all币
278
+ if (this.finalClient == "origin") {
279
+ this.user_left = res.data.data.asUserBoxCoinRemainOrigin + res.data.data.asUserBoxCoinRemainAll;
280
+ } else if (this.finalClient == "std") {
281
+ this.user_left = res.data.data.asUserBoxCoinRemainStd + res.data.data.asUserBoxCoinRemainAll;
282
+ } else {
283
+ this.user_left =
284
+ res.data.data.asUserBoxCoinRemainAll +
285
+ res.data.data.asUserBoxCoinRemainStd +
286
+ res.data.data.asUserBoxCoinRemainOrigin;
287
+ }
288
+ })
289
+ .catch(() => {
290
+ this.resetBoxcoinQuota();
291
+ });
292
+ getBoxcoinStatus()
293
+ .then((res) => {
294
+ this.boxcoin_enable = !!~~res.data?.data?.val;
295
+ })
296
+ .catch(() => {
297
+ this.boxcoin_enable = 0;
276
298
  });
277
- getBoxcoinStatus().then((res) => {
278
- this.boxcoin_enable = !!~~res.data?.data?.val;
279
- });
280
299
 
281
300
  getConfig({
282
301
  key: "admin_boxcoin_visible",
283
- }).then((res) => {
284
- this.admin_boxcoin_visible = Number(res?.val);
285
- });
302
+ })
303
+ .then((res) => {
304
+ this.admin_boxcoin_visible = Number(res?.val);
305
+ })
306
+ .catch(() => {
307
+ this.admin_boxcoin_visible = 1;
308
+ });
286
309
 
287
310
  getConfig({
288
311
  key: `level_has_gift_permission`,
289
- }).then((res) => {
290
- User.isLogin() &&
291
- User.getAsset().then((data) => {
292
- const asset = data;
293
- this.lvEnough = asset && asset.experience >= Number(res?.val);
294
- });
295
- });
312
+ })
313
+ .then((res) => {
314
+ User.isLogin() &&
315
+ User.getAsset()
316
+ .then((data) => {
317
+ const asset = data;
318
+ this.lvEnough = asset && asset.experience >= Number(res?.val);
319
+ })
320
+ .catch(() => {
321
+ this.lvEnough = false;
322
+ });
323
+ })
324
+ .catch(() => {
325
+ this.lvEnough = false;
326
+ });
296
327
 
297
328
  User.isLogin() &&
298
- getUserPermission().then((res) => {
299
- const permissions = res.data.data.permission?.map((item) => item.action);
300
- this.hasPermission = permissions.includes(`manage_boxcoin_${this.postType}`);
301
- });
329
+ getUserPermission()
330
+ .then((res) => {
331
+ const permissions = res.data.data.permission?.map((item) => item.action);
332
+ this.hasPermission = permissions.includes(`manage_boxcoin_${this.postType}`);
333
+ })
334
+ .catch(() => {
335
+ this.hasPermission = false;
336
+ });
302
337
  },
303
338
  // 用户打赏
304
339
  updateRecord: function (data) {