Skip to content

Commit 6c0bac3

Browse files
committed
feat: 反馈支持上传视频
1 parent c1aff49 commit 6c0bac3

3 files changed

Lines changed: 208 additions & 31 deletions

File tree

src/assets/css/dashboard/feedback_single.less

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,39 @@
195195
}
196196
.m-attachment {
197197
.u-detail {
198-
.flex;
199-
flex-wrap: wrap;
198+
display: flex;
199+
flex-direction: column;
200+
gap: 12px;
201+
}
202+
.u-attachment-summary {
203+
color: #909399;
204+
.fz(12px, 1.6);
205+
}
206+
.u-media-grid {
207+
display: grid;
208+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
209+
gap: 12px;
200210
}
201-
.u-img {
202-
.mr(10px);
203-
.mb(10px);
211+
.u-media-card {
212+
overflow: hidden;
213+
border: 1px solid #ebeef5;
214+
border-radius: 8px;
215+
background: #fff;
204216
}
205-
.el-image {
206-
.size(100px);
217+
.u-media-meta {
218+
padding: 8px 10px;
219+
.fz(12px, 1.4);
220+
color: #606266;
221+
border-top: 1px solid #f2f6fc;
222+
background: #fafafa;
223+
}
224+
.el-image,
225+
.u-media-video {
226+
display: block;
227+
width: 100%;
228+
height: 180px;
229+
background: #000;
230+
object-fit: cover;
207231
}
208232
}
209233

@@ -284,6 +308,17 @@
284308
flex-wrap: wrap;
285309
padding-left: 10px;
286310
}
311+
.m-single-content {
312+
.m-attachment {
313+
.u-media-grid {
314+
grid-template-columns: 1fr;
315+
}
316+
.el-image,
317+
.u-media-video {
318+
height: 220px;
319+
}
320+
}
321+
}
287322
}
288323

289324
.m-reply .c-comment .el-main {

src/components/dashboard/feedback/add.vue

Lines changed: 130 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
</div>
2626
<div class="m-feedback-actions">
2727
<div class="m-feedback-attachment">
28+
<div class="u-attachment-tip">支持图片和视频,单个文件最大 30MB,最多上传 {{ max }} 个附件</div>
2829
<el-upload
2930
ref="upload"
3031
class="u-upload avatar-uploader"
@@ -36,20 +37,36 @@
3637
:on-change="change"
3738
:on-exceed="exceed"
3839
:limit="max"
39-
title="上传图片"
40+
title="上传图片或视频"
4041
with-credentials
41-
accept="image/jpg, image/jpeg, image/gif, image/png, image/bmp"
42+
accept="image/jpg, image/jpeg, image/gif, image/png, image/bmp, video/mp4, video/quicktime, video/webm, video/ogg, video/x-m4v"
4243
multiple
4344
>
45+
<template #file="{ file }">
46+
<div class="u-upload-item" @click.stop="handlePictureCardPreview(file)">
47+
<img v-if="!isVideoFile(file)" class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
48+
<div v-else class="u-upload-video">
49+
<video class="u-upload-video__player" :src="file.url" muted preload="metadata"></video>
50+
<i class="el-icon-video-play u-upload-video__icon"></i>
51+
</div>
52+
</div>
53+
</template>
4454
<i class="el-icon-plus avatar-uploader-icon"></i>
4555
</el-upload>
46-
<el-dialog v-model="dialogVisible">
47-
<img width="100%" :src="dialogImageUrl" alt="" />
56+
<el-dialog v-model="dialogVisible" class="m-media-preview">
57+
<video
58+
v-if="dialogFileType === 'video'"
59+
class="u-preview-video"
60+
:src="dialogImageUrl"
61+
controls
62+
:autoplay="false"
63+
></video>
64+
<img v-else width="100%" :src="dialogImageUrl" alt="" />
4865
</el-dialog>
4966
</div>
5067
<div class="m-feedback-visible">
5168
<span class="u-label">是否公开:</span>
52-
<el-checkbox class="u-checkbox" :true-value="1" :fasle-value="0" v-model="form.public"></el-checkbox>
69+
<el-checkbox class="u-checkbox" :true-value="1" :false-value="0" v-model="form.public"></el-checkbox>
5370
<!-- <el-tooltip v-show="!canSubmit" content="必须先填写类型,子类和内容">
5471
<i class="el-icon-question"></i>
5572
</el-tooltip> -->
@@ -104,6 +121,7 @@ export default {
104121
uploadedMap: {},
105122
loading: false,
106123
dialogImageUrl: "",
124+
dialogFileType: "image",
107125
dialogVisible: false,
108126
};
109127
},
@@ -119,7 +137,38 @@ export default {
119137
},
120138
},
121139
methods: {
140+
isVideoFile(file) {
141+
const type = file?.raw?.type || file?.type || "";
142+
if (type.startsWith("video/")) return true;
143+
return this.isVideoUrl(file?.url || file?.response?.data?.[0] || "");
144+
},
145+
isVideoUrl(url) {
146+
return /\.(mp4|mov|m4v|webm|ogg)(\?.*)?$/i.test(url || "");
147+
},
148+
validateFile(file) {
149+
const raw = file?.raw || file;
150+
const type = raw?.type || "";
151+
const size = raw?.size || 0;
152+
const isImage = type.startsWith("image/");
153+
const isVideo = type.startsWith("video/");
154+
155+
if (!isImage && !isVideo) {
156+
this.$message.warning("仅支持上传图片或视频文件");
157+
return false;
158+
}
159+
160+
if (size > 30 * 1024 * 1024) {
161+
this.$message.warning("单个附件大小不能超过 30MB");
162+
return false;
163+
}
164+
165+
return true;
166+
},
122167
change(file) {
168+
if (!this.validateFile(file)) {
169+
this.$refs.upload?.handleRemove(file);
170+
return;
171+
}
123172
if (file.status === "success" || this.uploadedMap[file.uid]) return;
124173
const fdata = new FormData();
125174
fdata.append("file", file.raw);
@@ -147,6 +196,7 @@ export default {
147196
this.uploadedMap[file.uid] = imageUrl;
148197
this.imgs = [...this.imgs, imageUrl];
149198
file.url = imageUrl;
199+
file.name = file.name || imageUrl.split("/").pop();
150200
file.status = "success";
151201
})
152202
.catch((err) => {
@@ -159,15 +209,16 @@ export default {
159209
}
160210
});
161211
},
162-
// 图片上限
212+
// 附件上限
163213
exceed: function () {
164-
this.$message.warning(`上传的图片个数最多为${this.max}`);
214+
this.$message.warning(`上传的附件个数最多为${this.max}`);
165215
},
166216
handlePictureCardPreview: function (file) {
167217
this.dialogImageUrl = file.url;
218+
this.dialogFileType = this.isVideoFile(file) ? "video" : "image";
168219
this.dialogVisible = true;
169220
},
170-
// 移除图片
221+
// 移除附件
171222
remove: function (file) {
172223
const imageUrl = this.uploadedMap[file?.uid] || file?.response?.data?.[0] || file?.url;
173224
this.imgs = this.imgs.filter((img) => img !== imageUrl);
@@ -185,15 +236,23 @@ export default {
185236
.then((res) => {
186237
this.$message.success("提交成功");
187238
this.$refs.upload.clearFiles();
188-
this.form = this.$options.data().form;
189-
this.imgs = [];
190-
this.uploadedMap = {};
239+
this.resetForm();
191240
})
192241
.finally(() => {
193242
this.loading = false;
194243
});
195244
},
245+
resetForm() {
246+
this.form = this.$options.data().form;
247+
this.imgs = [];
248+
this.uploadedMap = {};
249+
},
196250
addFile(file) {
251+
if (!this.validateFile(file)) return false;
252+
if ((this.$refs.upload?.uploadFiles?.length || 0) >= this.max) {
253+
this.exceed();
254+
return false;
255+
}
197256
this.$refs.upload.handleStart(file);
198257
return false;
199258
},
@@ -233,16 +292,52 @@ export default {
233292
.mt(20px);
234293
// .mb(10px);
235294
.u-upload {
295+
.el-upload-list {
296+
display: flex;
297+
flex-wrap: wrap;
298+
gap: 10px;
299+
}
236300
.el-upload--picture-card,
237301
.el-upload-list__item {
238-
height: 40px;
239-
width: 40px;
240-
line-height: 40px;
302+
height: 72px;
303+
width: 72px;
304+
line-height: 72px;
241305
}
242306
.el-icon-plus {
243307
.fz(16px);
244308
}
245309
}
310+
.u-attachment-tip {
311+
color: #909399;
312+
.fz(12px, 1.6);
313+
.mb(8px);
314+
}
315+
.u-upload-item {
316+
width: 100%;
317+
height: 100%;
318+
cursor: pointer;
319+
overflow: hidden;
320+
}
321+
.u-upload-video {
322+
position: relative;
323+
width: 100%;
324+
height: 100%;
325+
background: #111;
326+
&__player {
327+
width: 100%;
328+
height: 100%;
329+
object-fit: cover;
330+
}
331+
&__icon {
332+
position: absolute;
333+
top: 50%;
334+
left: 50%;
335+
color: #fff;
336+
.fz(20px);
337+
transform: translate(-50%, -50%);
338+
text-shadow: 0 2px 8px rgb(0 0 0 / 35%);
339+
}
340+
}
246341
}
247342
.m-public {
248343
.mb(10px);
@@ -275,14 +370,31 @@ export default {
275370
padding-left: 40px;
276371
padding-right: 40px;
277372
}
373+
.m-media-preview {
374+
.u-preview-video {
375+
width: 100%;
376+
max-height: 70vh;
377+
background: #000;
378+
}
379+
}
278380
279381
@media screen and (max-width: @phone) {
280382
.m-add-feedback {
281-
.type-box {
282-
.el-form-item {
283-
margin-bottom: 10px;
383+
.type-box {
384+
.el-form-item {
385+
margin-bottom: 10px;
386+
}
387+
}
388+
.m-feedback-actions {
389+
.u-upload {
390+
.el-upload--picture-card,
391+
.el-upload-list__item {
392+
height: 64px;
393+
width: 64px;
394+
line-height: 64px;
395+
}
396+
}
284397
}
285398
}
286399
}
287-
}
288400
</style>

src/components/dashboard/feedback/single.vue

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,31 @@
126126
</div>
127127
</div>
128128
<div class="m-attachment m-textarea">
129-
<el-divider content-position="left"
130-
><i class="el-icon-picture-outline-round"></i> 附件截图</el-divider
131-
>
132-
<div class="u-detail">
133-
<div class="u-img" v-for="(img, index) in data.images" :key="index">
134-
<el-image :src="img" lazy :preview-src-list="data.images"></el-image>
129+
<el-divider content-position="left"><i class="el-icon-picture-outline-round"></i> 附件内容</el-divider>
130+
<div v-if="attachments.length" class="u-detail">
131+
<div class="u-attachment-summary">
132+
共 {{ attachments.length }} 个附件
133+
<span v-if="imageAttachments.length || videoAttachments.length">
134+
<span v-if="imageAttachments.length">,图片 {{ imageAttachments.length }} 张</span>
135+
<span v-if="videoAttachments.length">,视频 {{ videoAttachments.length }} 个</span>
136+
</span>
137+
</div>
138+
<div class="u-media-grid">
139+
<div class="u-media-card is-image" v-for="(img, index) in imageAttachments" :key="`img-${index}`">
140+
<el-image :src="img" fit="cover" lazy :preview-src-list="imageAttachments"></el-image>
141+
<div class="u-media-meta">图片 {{ index + 1 }}</div>
142+
</div>
143+
<div
144+
class="u-media-card is-video"
145+
v-for="(video, index) in videoAttachments"
146+
:key="`video-${index}`"
147+
>
148+
<video class="u-media-video" :src="video" controls preload="metadata" :autoplay="false"></video>
149+
<div class="u-media-meta">视频 {{ index + 1 }}</div>
150+
</div>
135151
</div>
136152
</div>
153+
<el-empty v-else description="暂无附件"></el-empty>
137154
</div>
138155
<div class="m-feedback-thx">
139156
<el-divider content-position="left"
@@ -364,6 +381,15 @@ export default {
364381
client() {
365382
return __clients[this.data.client];
366383
},
384+
attachments() {
385+
return Array.isArray(this.data?.images) ? this.data.images.filter(Boolean) : [];
386+
},
387+
imageAttachments() {
388+
return this.attachments.filter((item) => !this.isVideoUrl(item));
389+
},
390+
videoAttachments() {
391+
return this.attachments.filter((item) => this.isVideoUrl(item));
392+
},
367393
},
368394
watch: {
369395
id: {
@@ -466,6 +492,7 @@ export default {
466492
this.loading = true;
467493
let res = await getFeedback(this.id);
468494
this.data = res.data.data;
495+
this.data.images = Array.isArray(this.data.images) ? this.data.images : [];
469496
this.data.content = this.data.content.replace(/\n/g, "<br>");
470497
this.done = true;
471498
} catch (e) {
@@ -509,6 +536,9 @@ export default {
509536
sanitizedHTML(html) {
510537
return DOMPurify.sanitize(html);
511538
},
539+
isVideoUrl(url) {
540+
return /\.(mp4|mov|m4v|webm|ogg)(\?.*)?$/i.test(url || "");
541+
},
512542
513543
// 打赏相关
514544
onThx() {

0 commit comments

Comments
 (0)