From d601226187d8360c9b480560be0399748d8a1c44 Mon Sep 17 00:00:00 2001 From: RockYang Date: Mon, 26 Aug 2024 07:24:04 +0800 Subject: [PATCH] add download for video --- api/core/app_server.go | 104 ++++++++++---------- web/src/assets/css/home.css | 154 ++++++++++++++++++++++++++++++ web/src/assets/css/index.css | 101 ++++++++++++++++++++ web/src/assets/css/login.css | 99 +++++++++++++++++++ web/src/assets/css/luma.css | 142 +++++++++++++++++++++++++++ web/src/assets/css/luma.styl | 28 +++++- web/src/components/FileSelect.vue | 2 +- web/src/views/Luma.vue | 36 +++++-- 8 files changed, 607 insertions(+), 59 deletions(-) create mode 100644 web/src/assets/css/home.css create mode 100644 web/src/assets/css/index.css create mode 100644 web/src/assets/css/login.css create mode 100644 web/src/assets/css/luma.css diff --git a/api/core/app_server.go b/api/core/app_server.go index 84bc89a8..fab16d1c 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -26,6 +26,7 @@ import ( "io" "net/http" "os" + "path/filepath" "runtime/debug" "strings" "time" @@ -315,58 +316,65 @@ func staticResourceMiddleware() gin.HandlerFunc { url := c.Request.URL.String() // 拦截生成缩略图请求 - if strings.HasPrefix(url, "/static/") && strings.Contains(url, "?imageView2") { - r := strings.SplitAfter(url, "imageView2") - size := strings.Split(r[1], "/") - if len(size) != 8 { - c.String(http.StatusNotFound, "invalid thumb args") - return - } - with := utils.IntValue(size[3], 0) - height := utils.IntValue(size[5], 0) - quality := utils.IntValue(size[7], 75) + if strings.HasPrefix(url, "/static/") { + if strings.Contains(url, "?imageView2") { + r := strings.SplitAfter(url, "imageView2") + size := strings.Split(r[1], "/") + if len(size) != 8 { + c.String(http.StatusNotFound, "invalid thumb args") + return + } + with := utils.IntValue(size[3], 0) + height := utils.IntValue(size[5], 0) + quality := utils.IntValue(size[7], 75) - // 打开图片文件 - filePath := strings.TrimLeft(c.Request.URL.Path, "/") - file, err := os.Open(filePath) - if err != nil { - c.String(http.StatusNotFound, "Image not found") - return - } - defer file.Close() + // 打开图片文件 + filePath := strings.TrimLeft(c.Request.URL.Path, "/") + file, err := os.Open(filePath) + if err != nil { + c.String(http.StatusNotFound, "Image not found") + return + } + defer file.Close() - // 解码图片 - img, _, err := image.Decode(file) - // for .webp image - if err != nil { - img, err = webp.Decode(file) - } - if err != nil { - c.String(http.StatusInternalServerError, "Error decoding image") - return + // 解码图片 + img, _, err := image.Decode(file) + // for .webp image + if err != nil { + img, err = webp.Decode(file) + } + if err != nil { + c.String(http.StatusInternalServerError, "Error decoding image") + return + } + + var newImg image.Image + if height == 0 || with == 0 { + // 固定宽度,高度自适应 + newImg = resize.Resize(uint(with), uint(height), img, resize.Lanczos3) + } else { + // 生成缩略图 + newImg = resize.Thumbnail(uint(with), uint(height), img, resize.Lanczos3) + } + var buffer bytes.Buffer + err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality}) + if err != nil { + logger.Error(err) + c.String(http.StatusInternalServerError, err.Error()) + return + } + + // 设置图片缓存有效期为一年 (365天) + c.Header("Cache-Control", "max-age=31536000, public") + // 直接输出图像数据流 + c.Data(http.StatusOK, "image/jpeg", buffer.Bytes()) + c.Abort() // 中断请求 + } else if strings.Contains(url, "?download=true") { + filename := filepath.Base(url) + c.Header("Content-Disposition", "attachment; filename="+filename) + c.Header("Content-Type", "application/octet-stream") } - var newImg image.Image - if height == 0 || with == 0 { - // 固定宽度,高度自适应 - newImg = resize.Resize(uint(with), uint(height), img, resize.Lanczos3) - } else { - // 生成缩略图 - newImg = resize.Thumbnail(uint(with), uint(height), img, resize.Lanczos3) - } - var buffer bytes.Buffer - err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality}) - if err != nil { - logger.Error(err) - c.String(http.StatusInternalServerError, err.Error()) - return - } - - // 设置图片缓存有效期为一年 (365天) - c.Header("Cache-Control", "max-age=31536000, public") - // 直接输出图像数据流 - c.Data(http.StatusOK, "image/jpeg", buffer.Bytes()) - c.Abort() // 中断请求 } c.Next() } diff --git a/web/src/assets/css/home.css b/web/src/assets/css/home.css new file mode 100644 index 00000000..f037af08 --- /dev/null +++ b/web/src/assets/css/home.css @@ -0,0 +1,154 @@ +.home { + display: flex; + height: 100vh; + width: 100%; + flex-flow: column; +} +.home .header { + display: flex; + justify-content: space-between; + height: 50px; + line-height: 50px; + background-color: #1e1f22; + padding-right: 20px; +} +.home .header .banner { + display: flex; +} +.home .header .banner .logo { + display: flex; + padding: 5px; + cursor: pointer; +} +.home .header .banner .logo .el-image { + width: 48px; + height: 48px; + background-color: #fff; + border-radius: 50%; +} +.home .header .banner .title { + display: flex; + color: #fff; + font-size: 20px; + padding: 0 10px; +} +.home .header .navbar { + display: flex; + flex-flow: row; +} +.home .header .navbar .link-button { + margin-right: 15px; + color: #e1e1e1; + padding: 0 10px; +} +.home .header .navbar .link-button:hover { + background-color: #414141; +} +.home .header .navbar .link-button .iconfont { + font-size: 24px; +} +.home .header .navbar .user-info { + width: 100%; + padding: 5px 0; +} +.home .header .navbar .user-info .el-dropdown-link { + width: 100%; + cursor: pointer; + display: flex; +} +.home .header .navbar .user-info .el-dropdown-link .el-image { + width: 36px; + height: 36px; + border-radius: 50%; +} +.home .header .navbar .user-info .el-dropdown-link .el-icon { + color: #ccc; + line-height: 24px; +} +.home .main { + width: 100%; + display: flex; + flex-flow: row; +} +.home .main .navigator { + display: flex; + flex-flow: column; + width: 60px; + padding: 10px 1px; + border-right: 1px solid #3c3c3c; + background-color: #1e1f22; +} +.home .main .navigator .nav-items { + margin-top: 10px; + padding: 0 5px; +} +.home .main .navigator .nav-items li { + margin-bottom: 15px; + display: flex; + flex-flow: column; +} +.home .main .navigator .nav-items li a { + color: #dadbdc; + border-radius: 10px; + width: 48px; + height: 48px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + background-color: #414348; +} +.home .main .navigator .nav-items li a .el-image { + border-radius: 10px; +} +.home .main .navigator .nav-items li a .iconfont { + font-size: 20px; +} +.home .main .navigator .nav-items li a:hover, +.home .main .navigator .nav-items li a.active { + color: #47fff1; + background-color: #0f7a71; +} +.home .main .navigator .nav-items li .title { + font-size: 12px; + padding-top: 6px; + color: #e5e7eb; + text-align: center; + white-space: nowrap; /* 防止文本换行 */ + overflow: hidden; /* 隐藏溢出内容 */ + text-overflow: unset; /* 使用省略号表示溢出内容 */ +} +.home .main .navigator .nav-items li .active { + color: #47fff1; +} +.home .main .content { + width: 100%; + overflow: auto; + box-sizing: border-box; + background-color: #282c34; +} +.el-popper .more-menus li { + padding: 10px 15px; + cursor: pointer; + border-radius: 5px; + margin: 5px 0; +} +.el-popper .more-menus li .el-image { + position: relative; + top: 5px; + right: 5px; +} +.el-popper .more-menus li:hover { + background-color: #f1f1f1; +} +.el-popper .more-menus li.active { + background-color: #f1f1f1; +} +.el-popper .user-info-menu li a { + width: 100%; + justify-content: left; +} +.el-popper .user-info-menu li a:hover { + text-decoration: none !important; + color: var(--el-primary-text-color); +} diff --git a/web/src/assets/css/index.css b/web/src/assets/css/index.css new file mode 100644 index 00000000..3eaa8878 --- /dev/null +++ b/web/src/assets/css/index.css @@ -0,0 +1,101 @@ +.index-page { + margin: 0; + overflow: hidden; + color: #fff; + display: flex; + justify-content: center; + align-items: baseline; + padding-top: 150px; +} +.index-page .color-bg { + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; +} +.index-page .image-bg { + filter: blur(8px); + background-size: cover; + background-position: center; +} +.index-page .shadow { + box-shadow: rgba(0,0,0,0.3) 0px 0px 3px; +} +.index-page .shadow:hover { + box-shadow: rgba(0,0,0,0.3) 0px 0px 8px; +} +.index-page .menu-box { + position: absolute; + top: 0; + width: 100%; + display: flex; +} +.index-page .menu-box .el-menu { + padding: 0 30px; + width: 100%; + display: flex; + justify-content: space-between; + background: none; + border: none; +} +.index-page .menu-box .el-menu .menu-item { + display: flex; + padding: 20px 0; + color: #fff; +} +.index-page .menu-box .el-menu .menu-item .title { + font-size: 24px; + padding: 10px 10px 0 10px; +} +.index-page .menu-box .el-menu .menu-item .el-image { + height: 50px; + background-color: #fff; +} +.index-page .menu-box .el-menu .menu-item .el-button { + margin-left: 10px; +} +.index-page .menu-box .el-menu .menu-item .el-button span { + margin-left: 5px; +} +.index-page .content { + text-align: center; + position: relative; + display: flex; + flex-flow: column; + align-items: center; +} +.index-page .content h1 { + font-size: 5rem; + margin-bottom: 1rem; +} +.index-page .content p { + font-size: 1.5rem; + margin-bottom: 2rem; +} +.index-page .content .navs { + display: flex; + max-width: 900px; + padding: 20px; +} +.index-page .content .navs .el-space--horizontal { + justify-content: center; +} +.index-page .content .navs .nav-item { + width: 200px; +} +.index-page .content .navs .nav-item .el-button { + width: 100%; + padding: 25px 20px; + font-size: 1.3rem; + transition: all 0.3s ease; +} +.index-page .content .navs .nav-item .el-button .iconfont { + font-size: 24px; + margin-right: 10px; + position: relative; + top: -2px; +} +.index-page .footer .el-link__inner { + color: #fff; +} diff --git a/web/src/assets/css/login.css b/web/src/assets/css/login.css new file mode 100644 index 00000000..0b015d74 --- /dev/null +++ b/web/src/assets/css/login.css @@ -0,0 +1,99 @@ +.bg { + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: #313237; + background-image: url("~@/assets/img/login-bg.jpg"); + background-size: cover; + background-position: center; + background-repeat: repeat-y; +} +.main .contain { + position: fixed; + left: 50%; + top: 40%; + width: 90%; + max-width: 400px; + transform: translate(-50%, -50%); + padding: 20px 10px; + color: #fff; + border-radius: 10px; +} +.main .contain .logo { + text-align: center; +} +.main .contain .logo .el-image { + width: 120px; + cursor: pointer; + background-color: #fff; + border-radius: 50%; +} +.main .contain .header { + width: 100%; + margin-bottom: 24px; + font-size: 24px; + color: $white_v1; + letter-space: 2px; + text-align: center; + padding-top: 10px; +} +.main .contain .content { + width: 100%; + height: auto; + border-radius: 3px; +} +.main .contain .content .block { + margin-bottom: 16px; +} +.main .contain .content .block .el-input__inner { + border: 1px solid $gray-v6 !important; +} +.main .contain .content .block .el-input__inner .el-icon-user, +.main .contain .content .block .el-input__inner .el-icon-lock { + font-size: 20px; +} +.main .contain .content .btn-row { + padding-top: 10px; +} +.main .contain .content .btn-row .login-btn { + width: 100%; + font-size: 16px; + letter-spacing: 2px; +} +.main .contain .content .text-line { + justify-content: center; + padding-top: 10px; + font-size: 14px; +} +.main .contain .content .opt { + padding: 15px; +} +.main .contain .content .opt .el-col { + text-align: center; +} +.main .contain .content .divider { + border-top: 2px solid #c1c1c1; +} +.main .contain .content .clogin { + padding: 15px; + display: flex; + justify-content: center; +} +.main .contain .content .clogin .iconfont { + font-size: 20px; + background: #e9f1f6; + padding: 8px; + border-radius: 50%; + cursor: pointer; +} +.main .contain .content .clogin .iconfont.icon-wechat { + color: #0bc15f; +} +.main .footer { + color: #fff; +} +.main .footer .container { + padding: 20px; +} diff --git a/web/src/assets/css/luma.css b/web/src/assets/css/luma.css new file mode 100644 index 00000000..ec316d99 --- /dev/null +++ b/web/src/assets/css/luma.css @@ -0,0 +1,142 @@ +.page-luma { + display: flex; + height: 100%; + background-color: #0e0808; + overflow: auto; + flex-flow: column; + align-items: center; + background: linear-gradient(180deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3)); +} +.page-luma .prompt-box { + display: flex; + max-width: 56rem; + width: 100%; + padding: 20px; + flex-flow: column; +} +.page-luma .prompt-box .images { + display: flex; + flex-flow: row; + padding-bottom: 10px; + justify-content: center; +} +.page-luma .prompt-box .images .item { + position: relative; +} +.page-luma .prompt-box .images .item .el-image { + width: 100px; + height: 100px; + border-radius: 6px; + margin-right: 10px; +} +.page-luma .prompt-box .images .item .el-icon { + position: absolute; + cursor: pointer; + font-size: 20px; + color: #545454; + right: 10px; + top: 0; +} +.page-luma .prompt-box .images .item .el-icon:hover { + color: #888; +} +.page-luma .prompt-box .prompt-container { + width: 100%; +} +.page-luma .prompt-box .prompt-container .input-container { + background: linear-gradient(90deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3)); + border-radius: 28px; + padding: 10px 20px; + display: flex; + align-items: center; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); +} +.page-luma .prompt-box .prompt-container .input-container .prompt-input { + background: transparent; + border: none; + outline: none; + color: #fff; + font-size: 14px; + width: 100%; + padding: 10px; + resize: none; + white-space: pre-wrap; + word-wrap: break-word; + line-height: 24px; + overflow-wrap: break-word; + scrollbar-width: none; /* 隐藏滚动条 */ +} +.page-luma .prompt-box .prompt-container .input-container .prompt-input::placeholder { + color: rgba(255,255,255,0.6); +} +.page-luma .prompt-box .prompt-container .input-container .prompt-input::-webkit-scrollbar { + display: none; +} +.page-luma .prompt-box .prompt-container .input-container .upload-icon, +.page-luma .prompt-box .prompt-container .input-container .send-icon { + color: #e1e1e1; +} +.page-luma .prompt-box .prompt-container .input-container .upload-icon .iconfont, +.page-luma .prompt-box .prompt-container .input-container .send-icon .iconfont { + font-size: 20px; + cursor: pointer; +} +.page-luma .prompt-box .prompt-container .input-container .upload-icon { + position: relative; +} +.page-luma .video-container { + display: flex; + flex-flow: column; + width: 100%; + padding: 0 40px; +} +.page-luma .video-container .h-title { + color: #fff; + width: 100%; + font-size: 36px; + text-align: left; +} +.page-luma .video-container .videos .item { + margin-bottom: 20px; +} +.page-luma .video-container .videos .item .video-box { + width: 100%; + border-radius: 10px; +} +.page-luma .video-container .videos .item .video-box video, +.page-luma .video-container .videos .item .video-box img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 10px; + cursor: pointer; +} +.page-luma .video-container .videos .item .video-name { + color: #e1e1e1; + font-size: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 6px 0; + text-align: center; +} +.page-luma .video-container .videos .item .opts { + display: flex; + justify-content: center; +} +.page-luma .video-container .videos .item .opts .btn { + margin-right: 10px; + background-color: rgba(255,255,255,0.15); + border: none; + border-radius: 20px; + padding: 3px 15px; + cursor: pointer; + color: #fff; + font-size: 14px; +} +.page-luma .video-container .videos .item .opts .btn .iconfont { + font-size: 12px; +} +.page-luma .video-container .videos .item .opts .btn:hover { + background-color: rgba(255,255,255,0.2); +} diff --git a/web/src/assets/css/luma.styl b/web/src/assets/css/luma.styl index 1f681ed6..557ff651 100644 --- a/web/src/assets/css/luma.styl +++ b/web/src/assets/css/luma.styl @@ -110,10 +110,8 @@ } .videos { - display flex - flex-flow row - .item { + margin-bottom 20px .video-box { width 100% @@ -127,6 +125,7 @@ } } + .video-name { color #e1e1e1 font-size 16px @@ -136,6 +135,29 @@ padding 6px 0 text-align center } + + .opts { + display flex + justify-content center + .btn { + margin-right 10px + background-color hsla(0,0%,100%,.15) + border none + border-radius 20px + padding 3px 15px + cursor pointer + color #ffffff + font-size 14px + + .iconfont { + font-size 12px + } + + &:hover { + background-color hsla(0,0%,100%,.2) + } + } + } } } } diff --git a/web/src/components/FileSelect.vue b/web/src/components/FileSelect.vue index b2a1cb59..d340c460 100644 --- a/web/src/components/FileSelect.vue +++ b/web/src/components/FileSelect.vue @@ -20,7 +20,7 @@ :auto-upload="true" :show-file-list="false" :http-request="afterRead" - accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf" + accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3" > diff --git a/web/src/views/Luma.vue b/web/src/views/Luma.vue index 6a46e0af..94c6cbd7 100644 --- a/web/src/views/Luma.vue +++ b/web/src/views/Luma.vue @@ -33,12 +33,20 @@
- -
{{item.name}}
+
@@ -60,24 +68,38 @@ const videos = ref([ { id: 1, name: 'a dancing girl', - url: 'http://localhost/download/xmind.mp4', + url: 'http://localhost:5678/static/upload/2024/8/1724574661747320.mp4', cover: 'https://storage.cdn-luma.com/dream_machine/d133794f-3124-4059-a9f2-e5fed79f0d5b/video_0_thumb.jpg', playing: false }, { id: 1, - name: 'a dancing girl', - url: 'http://localhost/download/dancing.mp4', + name: 'a dancing girl a dancing girl a dancing girl a dancing girl a dancing girl', + url: 'https://storage.cdn-luma.com/dream_machine/92efa55a-f381-4161-a999-54f8fe460fca/watermarked_video0e5aad607a0644c66850d1d77022db847.mp4', cover: 'https://storage.cdn-luma.com/dream_machine/92efa55a-f381-4161-a999-54f8fe460fca/video_1_thumb.jpg', playing: false }, { id: 1, name: 'a dancing girl', - url: 'http://localhost/download/xmind.mp4', + url: 'https://storage.cdn-luma.com/dream_machine/d133794f-3124-4059-a9f2-e5fed79f0d5b/watermarked_video01944f69966f14d33b6c4486a8cfb8dde.mp4', cover: 'https://storage.cdn-luma.com/dream_machine/d133794f-3124-4059-a9f2-e5fed79f0d5b/video_0_thumb.jpg', playing: false - } + }, + { + id: 1, + name: 'a dancing girl', + url: 'https://storage.cdn-luma.com/dream_machine/92efa55a-f381-4161-a999-54f8fe460fca/watermarked_video0e5aad607a0644c66850d1d77022db847.mp4', + cover: 'https://storage.cdn-luma.com/dream_machine/92efa55a-f381-4161-a999-54f8fe460fca/video_1_thumb.jpg', + playing: false + }, + { + id: 1, + name: 'a dancing girl', + url: 'https://storage.cdn-luma.com/dream_machine/d133794f-3124-4059-a9f2-e5fed79f0d5b/watermarked_video01944f69966f14d33b6c4486a8cfb8dde.mp4', + cover: 'https://storage.cdn-luma.com/dream_machine/d133794f-3124-4059-a9f2-e5fed79f0d5b/video_0_thumb.jpg', + playing: false + }, ])