899 lines
20 KiB
Vue
899 lines
20 KiB
Vue
<template>
|
||
<div :class="prefixCls">
|
||
<div class="left">
|
||
<div class="upload">
|
||
<div class="upload-title">
|
||
<div class="upload-title-left">
|
||
{{ $t('common.uploadImage') }}
|
||
</div>
|
||
<div class="upload-title-tip">
|
||
{{ $t('common.uploadImageTip') }}
|
||
</div>
|
||
</div>
|
||
<!-- 上传方式选择 -->
|
||
<div class="upload-type-select">
|
||
<a-radio-group v-model="uploadType" @change="handleUploadTypeChange">
|
||
<a-radio value="upload">{{ $t('common.uploadImage') }}</a-radio>
|
||
<a-radio value="template">{{ $t('common.selectTemplate') }}</a-radio>
|
||
</a-radio-group>
|
||
</div>
|
||
<!-- 上传图片方式 -->
|
||
<div v-if="uploadType === 'upload'" class="upload-content">
|
||
<mf-image-upload
|
||
listType="draggable"
|
||
uploadHeight="200px"
|
||
:show-file-list="false"
|
||
:content="$t('common.uploadFirstPlaceholder')"
|
||
v-model="firstUrl" />
|
||
|
||
<mf-image-upload
|
||
listType="draggable"
|
||
uploadHeight="200px"
|
||
:show-file-list="false"
|
||
:content="$t('common.uploadLastPlaceholder')"
|
||
v-model="lastUrl" />
|
||
</div>
|
||
<!-- 选择模版方式 -->
|
||
<div v-else class="template-content">
|
||
<div v-if="selectedTemplatePreview" class="template-preview">
|
||
<a-image
|
||
:src="selectedTemplatePreview"
|
||
fit="contain"
|
||
:preview="false"
|
||
class="preview-image" />
|
||
<div class="template-preview-actions">
|
||
<a-button type="primary" @click="openTemplateDialog">
|
||
{{ $t('common.reselectTemplate') }}
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
<div v-else class="template-placeholder">
|
||
<mf-button type="primary" size="large" @click="openTemplateDialog">
|
||
{{ $t('common.selectTemplate') }}
|
||
</mf-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="tags"
|
||
v-for="tag in tags">
|
||
<div class="tags-title">{{ tag.title }}</div>
|
||
<a-radio-group
|
||
:model-value="selectedTags[tag.id]"
|
||
@change="handleGroupChange(tag.id, $event)">
|
||
<a-radio
|
||
v-for="item in tag.children"
|
||
:value="item.value">
|
||
{{ item.title }}
|
||
</a-radio>
|
||
</a-radio-group>
|
||
</div>
|
||
<!-- <div class="text">
|
||
<a-textarea
|
||
v-model="text"
|
||
:placeholder="$t('common.textVideoPlaceholder')" />
|
||
</div> -->
|
||
<mf-button
|
||
class="submit"
|
||
type="primary"
|
||
size="large"
|
||
:loading="generateLoading"
|
||
@click="generateVideo">
|
||
{{
|
||
price
|
||
? `${$t('common.createVideo', { price: price })}`
|
||
: $t('common.generateVideo')
|
||
}}
|
||
</mf-button>
|
||
<div class="submit-tip">
|
||
{{ $t('common.generateTip') }}
|
||
</div>
|
||
</div>
|
||
<!-- 模版选择弹窗 -->
|
||
<a-modal
|
||
v-model:visible="templateDialogVisible"
|
||
:title="$t('common.selectTemplate')"
|
||
:footer="false"
|
||
width="50%"
|
||
@cancel="handleTemplateDialogClose">
|
||
<div class="template-dialog-wrapper">
|
||
<div class="template-dialog-content">
|
||
<a-spin :loading="templateLoading">
|
||
<div class="template-grid">
|
||
<div
|
||
class="template-item"
|
||
v-for="template in templateList"
|
||
:key="template.id"
|
||
:class="{ 'selected': selectedTemplate && selectedTemplate.id === template.id }"
|
||
@click="selectTemplate(template)">
|
||
<a-image
|
||
:src="getImageUrl(template.imageUrl || template.img_url)"
|
||
:preview="false"
|
||
fit="cover" />
|
||
</div>
|
||
</div>
|
||
<div v-if="templateList.length === 0" class="empty-template">
|
||
{{ $t('common.noTemplates') }}
|
||
</div>
|
||
</a-spin>
|
||
</div>
|
||
<div class="template-dialog-footer">
|
||
<a-button @click="handleTemplateDialogClose">{{ $t('common.cancel') }}</a-button>
|
||
<a-button type="primary" :disabled="!selectedTemplate" @click="handleConfirmTemplate">{{ $t('common.confirm') }}</a-button>
|
||
</div>
|
||
</div>
|
||
</a-modal>
|
||
<div
|
||
class="right"
|
||
v-if="showResult">
|
||
<div class="right-close">
|
||
<mf-icon
|
||
value="icon-close"
|
||
@click="close" />
|
||
</div>
|
||
<div class="result-video">
|
||
<a-spin
|
||
:tip="$t('common.videoLoadingText')"
|
||
:loading="videoLoading">
|
||
<video
|
||
v-if="videoUrl"
|
||
:src="videoUrl"
|
||
autoplay
|
||
muted
|
||
playsinline
|
||
controls
|
||
style="width: 100%; height: 100%" />
|
||
</a-spin>
|
||
</div>
|
||
|
||
<div class="action">
|
||
<mf-button
|
||
v-if="videoUrl"
|
||
@click="saveVideo">
|
||
<a-image
|
||
:width="20"
|
||
:height="20"
|
||
:preview="false"
|
||
src="/images/btn_bctp@2x.png" />
|
||
{{ $t('common.saveVideo') }}
|
||
</mf-button>
|
||
<mf-button
|
||
v-if="videoId"
|
||
@click="viewVideo"
|
||
:loading="viewLoading">
|
||
<a-image
|
||
:width="20"
|
||
:height="20"
|
||
:preview="false"
|
||
src="/images/btn_scsp@2x.png" />
|
||
{{ $t('common.viewVideo') }}
|
||
</mf-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapGetters } from 'vuex'
|
||
export default {
|
||
data() {
|
||
return {
|
||
prefixCls: 'fast-video',
|
||
firstUrl: '',
|
||
lastUrl: '',
|
||
text: '',
|
||
interval: null,
|
||
videoUrl: null,
|
||
videoLoading: false,
|
||
generateLoading: false,
|
||
videoId: null,
|
||
viewLoading: false,
|
||
hasNavGuard: false,
|
||
price: null,
|
||
tags: [],
|
||
selectedTags: {},
|
||
showResult: !this.$base.isMobile(),
|
||
id: null,
|
||
// 上传方式选择
|
||
uploadType: 'upload', // 'upload' 或 'template'
|
||
// 模版选择相关
|
||
templateDialogVisible: false,
|
||
templateList: [],
|
||
templateLoading: false,
|
||
selectedTemplate: null,
|
||
selectedTemplatePreview: '', // 选中的模版预览图URL
|
||
maxPollAttempts: 40 // 最大轮询次数(3秒 * 40 = 120秒超时)
|
||
}
|
||
},
|
||
beforeUnmount() {
|
||
this.destroyInterval()
|
||
},
|
||
mounted() {
|
||
let { url, text } = this.$route.query || {}
|
||
// 安全验证:只接受有效的URL格式
|
||
if (url) {
|
||
if (typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/'))) {
|
||
this.firstUrl = url
|
||
}
|
||
}
|
||
if (text) {
|
||
this.text = text
|
||
}
|
||
this.$axios({
|
||
url: 'api/manager/selectInfo',
|
||
method: 'GET',
|
||
data: {
|
||
aiType: '21'
|
||
}
|
||
}).then((res) => {
|
||
this.price = res.data?.price
|
||
this.id = res.data?.id;
|
||
this.getTags()
|
||
})
|
||
},
|
||
computed: {
|
||
...mapGetters(['lang'])
|
||
},
|
||
watch: {
|
||
lang() {
|
||
this.getTags()
|
||
}
|
||
},
|
||
methods: {
|
||
handleGroupChange(id, value) {
|
||
this.selectedTags[id] = value
|
||
},
|
||
close() {
|
||
this.showResult = false
|
||
},
|
||
// 上传方式改变
|
||
handleUploadTypeChange() {
|
||
// 切换上传方式时,清空之前的选择
|
||
if (this.uploadType === 'upload') {
|
||
this.selectedTemplatePreview = ''
|
||
this.selectedTemplate = null
|
||
this.firstUrl = ''
|
||
} else {
|
||
this.firstUrl = ''
|
||
}
|
||
},
|
||
// 打开模版选择弹窗
|
||
openTemplateDialog() {
|
||
this.templateDialogVisible = true
|
||
this.selectedTemplate = null
|
||
// 加载模板列表
|
||
this.loadTemplateList()
|
||
},
|
||
// 加载模板列表
|
||
loadTemplateList() {
|
||
this.templateLoading = true
|
||
// 快捷生视频,ai_id = 21
|
||
this.$axios({
|
||
url: 'api/template/getTemplateList',
|
||
method: 'GET',
|
||
data: {
|
||
position: 0,
|
||
aiId: 21
|
||
}
|
||
}).then(res => {
|
||
this.templateList = res.rows || []
|
||
// 如果之前已选择过模版,恢复选中状态
|
||
if (this.selectedTemplatePreview && this.templateList.length > 0) {
|
||
const prevTemplate = this.templateList.find(t =>
|
||
this.getImageUrl(t.imageUrl || t.img_url) === this.selectedTemplatePreview
|
||
)
|
||
if (prevTemplate) {
|
||
this.selectedTemplate = prevTemplate
|
||
}
|
||
}
|
||
}).catch(err => {
|
||
console.error('加载模板列表失败:', err)
|
||
this.templateList = []
|
||
}).finally(() => {
|
||
this.templateLoading = false
|
||
})
|
||
},
|
||
// 选择模板
|
||
selectTemplate(template) {
|
||
this.selectedTemplate = template
|
||
},
|
||
// 获取图片URL
|
||
getImageUrl(url) {
|
||
if (!url) return ''
|
||
if (url.startsWith('/admin')) {
|
||
return '/api' + url
|
||
} else if (url.startsWith('/api')) {
|
||
return url
|
||
} else if (url.startsWith('http://') || url.startsWith('https://')) {
|
||
return url
|
||
} else {
|
||
return '/api' + url
|
||
}
|
||
},
|
||
// 获取模板名称(根据当前语言)
|
||
getTemplateName(template) {
|
||
if (!template) return ''
|
||
// 繁体中文显示 chineseContent,其他语言显示 englishContent
|
||
if (this.lang === 'zh_HK') {
|
||
return template.chineseContent || template.name || ''
|
||
}
|
||
return template.englishContent || template.name || ''
|
||
},
|
||
// 确认选择模板
|
||
handleConfirmTemplate() {
|
||
if (this.selectedTemplate) {
|
||
const imageUrl = this.getImageUrl(this.selectedTemplate.imageUrl || this.selectedTemplate.img_url)
|
||
this.selectedTemplatePreview = imageUrl
|
||
this.firstUrl = {
|
||
url: imageUrl
|
||
}
|
||
this.templateDialogVisible = false
|
||
}
|
||
},
|
||
// 关闭模版选择弹窗
|
||
handleTemplateDialogClose() {
|
||
this.templateDialogVisible = false
|
||
// 不重置selectedTemplate,保持用户的选择状态,方便二次选择
|
||
},
|
||
getTags() {
|
||
this.$axios({
|
||
url: 'api/tag/list',
|
||
method: 'GET',
|
||
data: {
|
||
aiId: this.id
|
||
}
|
||
}).then((res) => {
|
||
this.tags = this.$datas.recurrence(res.data)
|
||
if (!this.$datas.isEmpty(this.tags)) {
|
||
this.tags.map((item) => {
|
||
let children = item.children || []
|
||
let firstChild = children[0]
|
||
if (firstChild) {
|
||
this.selectedTags[item.id] = firstChild.id
|
||
}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
async saveVideo() {
|
||
if (!this.videoUrl) {
|
||
this.$message.error(this.$t('common.downloadFailed') || '视频URL为空')
|
||
return
|
||
}
|
||
try {
|
||
// 获取视频的 blob 数据
|
||
const response = await fetch(this.videoUrl)
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`)
|
||
}
|
||
const blob = await response.blob()
|
||
|
||
// 从 URL 中提取文件名,如果没有则使用默认名称
|
||
const urlParts = this.videoUrl.split('/')
|
||
let fileName = urlParts[urlParts.length - 1] || 'video.mp4'
|
||
// 移除查询参数
|
||
fileName = fileName.split('?')[0]
|
||
// 如果没有扩展名,尝试从 Content-Type 获取
|
||
if (!fileName.includes('.')) {
|
||
const contentType = response.headers.get('content-type') || 'video/mp4'
|
||
const extension = contentType.split('/')[1]?.split(';')[0] || 'mp4'
|
||
// 从URL路径中尝试提取扩展名
|
||
const pathMatch = this.videoUrl.match(/\.(mp4|webm|ogg|mov|avi|wmv|flv|mkv)/i)
|
||
if (pathMatch) {
|
||
fileName = `video_${new Date().getTime()}.${pathMatch[1]}`
|
||
} else {
|
||
fileName = `video_${new Date().getTime()}.${extension}`
|
||
}
|
||
}
|
||
|
||
// 创建下载链接
|
||
const url = window.URL.createObjectURL(blob)
|
||
const link = document.createElement('a')
|
||
link.href = url
|
||
link.download = fileName
|
||
link.style.display = 'none'
|
||
document.body.appendChild(link)
|
||
link.click()
|
||
document.body.removeChild(link)
|
||
window.URL.revokeObjectURL(url)
|
||
} catch (error) {
|
||
console.error('下载视频失败:', error)
|
||
this.$message.error(this.$t('common.downloadFailed') || '下载失败')
|
||
}
|
||
},
|
||
destroyInterval() {
|
||
if (this.interval) {
|
||
clearInterval(this.interval)
|
||
this.interval = null
|
||
}
|
||
},
|
||
viewVideo() {
|
||
if (!this.videoId) return
|
||
this.viewLoading = true
|
||
this.$axios({
|
||
url: `api/ai/${this.videoId}`,
|
||
method: 'GET'
|
||
})
|
||
.then((res) => {
|
||
this.viewLoading = false
|
||
if (res.code == 200) {
|
||
if (res.data.status == 'succeeded') {
|
||
this.$store.dispatch('main/setPrevent', false)
|
||
this.videoUrl = res.data.content.video_url
|
||
} else {
|
||
this.$message.warning(
|
||
this.$t('common.videoLoadingText')
|
||
)
|
||
}
|
||
}
|
||
})
|
||
.catch((_) => {
|
||
this.viewLoading = false
|
||
})
|
||
},
|
||
getVideo(videoId) {
|
||
if (!videoId) return
|
||
let pollAttempts = 0
|
||
this.interval = setInterval((_) => {
|
||
pollAttempts++
|
||
// 超时保护:超过最大轮询次数则停止
|
||
if (pollAttempts > this.maxPollAttempts) {
|
||
this.destroyInterval()
|
||
this.$message.warning(this.$t('common.videoTimeout'))
|
||
this.videoLoading = false
|
||
return
|
||
}
|
||
this.$axios({
|
||
url: `api/ai/${videoId}`,
|
||
method: 'GET'
|
||
}).then((res) => {
|
||
this.videoLoading = true
|
||
if (res.code == 200) {
|
||
if (res.data.status == 'succeeded') {
|
||
this.videoUrl = res.data.content.video_url
|
||
this.$store.dispatch('main/setPrevent', false)
|
||
this.videoLoading = false
|
||
this.destroyInterval()
|
||
}
|
||
}
|
||
})
|
||
}, 3000)
|
||
},
|
||
generateVideo() {
|
||
// 处理 firstUrl,可能是对象或字符串
|
||
const firstImageUrl = typeof this.firstUrl === 'object' ? this.firstUrl.url : this.firstUrl
|
||
if (!firstImageUrl) {
|
||
this.$message.error(this.$t('common.uploadFirstImageError'))
|
||
return
|
||
}
|
||
if (firstImageUrl.startsWith('blob:')) {
|
||
this.$message.error(this.$t('common.uploadWaitImageError'))
|
||
return
|
||
}
|
||
// if (!this.text) {
|
||
// this.$message.error(this.$t('common.textError'))
|
||
// return
|
||
// }
|
||
|
||
this.generateLoading = true
|
||
let params = {
|
||
text: this.text,
|
||
firstUrl: firstImageUrl,
|
||
functionType: '21'
|
||
}
|
||
if (this.lastUrl && this.lastUrl.url) {
|
||
params.lastUrl = this.lastUrl.url
|
||
}
|
||
let tags = []
|
||
Object.keys(this.selectedTags).map((key) => {
|
||
let tagValue = this.selectedTags[key]
|
||
let item = this.$datas.deepFind(
|
||
this.tags,
|
||
(d) => d.id == tagValue
|
||
)
|
||
let prompt = item.prompt
|
||
if (prompt) {
|
||
let arr = prompt.split(',')
|
||
const randomIndex = Math.floor(Math.random() * arr.length)
|
||
prompt = arr[randomIndex]
|
||
// tags.push(prompt)
|
||
} else {
|
||
// tags.push(item.title)
|
||
}
|
||
tags.push(item.id)
|
||
})
|
||
params.tags = tags.join(',')
|
||
this.$axios({
|
||
url: 'api/ai/imgToVideo',
|
||
method: 'POST',
|
||
data: params
|
||
})
|
||
.then((res) => {
|
||
this.generateLoading = false
|
||
if (res.code == 200) {
|
||
this.showResult = true
|
||
this.videoId = res.data.id
|
||
this.$store.dispatch('main/setPrevent', true)
|
||
this.getVideo(res.data.id)
|
||
} else if (res.code == -1) {
|
||
this.$confirm({
|
||
title: this.$t('common.notice'),
|
||
content: this.$t('common.balenceLow'),
|
||
okText: this.$t('common.confirm'),
|
||
cancelText: this.$t('common.cancel'),
|
||
onOk: (_) => {
|
||
this.$router.push('/recharge')
|
||
}
|
||
})
|
||
} else if (res.code == -2) {
|
||
this.$modal.error({
|
||
title: this.$t('common.notice'),
|
||
content: this.$t('common.createFailed'),
|
||
okText: this.$t('common.confirm')
|
||
})
|
||
} else if (res.code == -3) {
|
||
this.$modal.error({
|
||
title: this.$t('common.notice'),
|
||
content: this.$t('common.createTagFailed'),
|
||
okText: this.$t('common.confirm')
|
||
})
|
||
}
|
||
})
|
||
.catch((_) => {
|
||
this.generateLoading = false
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.fast-video {
|
||
display: flex;
|
||
height: 100%;
|
||
|
||
.left {
|
||
width: 400px;
|
||
padding: 20px 40px;
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
flex-shrink: 0;
|
||
|
||
.submit {
|
||
width: 100%;
|
||
margin-top: 20px;
|
||
border-radius: 10px;
|
||
|
||
&-tip {
|
||
font-size: 12px;
|
||
color: #5c5d68;
|
||
margin-top: 20px;
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
.arco-textarea-wrapper {
|
||
background: #1a1b20;
|
||
color: #fff;
|
||
height: 100px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.types {
|
||
display: flex;
|
||
padding-bottom: 20px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
.type {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 150px;
|
||
font-size: 14px;
|
||
color: #5c5d68;
|
||
height: 40px;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
|
||
&.active {
|
||
color: #ffffff;
|
||
background-color: rgb(var(--primary-6));
|
||
}
|
||
}
|
||
}
|
||
|
||
.upload {
|
||
margin-bottom: 20px;
|
||
&-title {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
justify-content: space-between;
|
||
&-left {
|
||
font-size: 14px;
|
||
color: #ffffff;
|
||
}
|
||
|
||
&-tip {
|
||
font-size: 12px;
|
||
color: #5c5d68;
|
||
}
|
||
}
|
||
|
||
.upload-type-select {
|
||
margin-bottom: 16px;
|
||
:deep(.arco-radio-group) {
|
||
.arco-radio {
|
||
margin-right: 24px;
|
||
&-label {
|
||
font-size: 14px;
|
||
color: #5c5d68;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.upload-content {
|
||
.mf-image-upload {
|
||
margin-bottom: 16px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.template-content {
|
||
.template-preview {
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
background: #1a1b20;
|
||
|
||
.preview-image {
|
||
width: 100%;
|
||
height: 200px;
|
||
display: block;
|
||
|
||
:deep(.arco-image-img) {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
}
|
||
}
|
||
|
||
.template-preview-actions {
|
||
padding: 12px;
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
.template-placeholder {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 200px;
|
||
border: 1px dashed rgba(255, 255, 255, 0.2);
|
||
border-radius: 8px;
|
||
background: #1a1b20;
|
||
}
|
||
}
|
||
}
|
||
|
||
.tags {
|
||
padding-top: 20px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||
&-title {
|
||
color: #fff;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
:deep(.arco-radio-group) {
|
||
.arco-radio {
|
||
margin-bottom: 12px;
|
||
&-label {
|
||
font-size: 14px;
|
||
color: #5c5d68;
|
||
}
|
||
|
||
&:hover {
|
||
.arco-radio-icon-hover::before {
|
||
background-color: transparent;
|
||
}
|
||
}
|
||
.arco-icon-hover:hover::before {
|
||
background-color: transparent;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.right {
|
||
flex: 1;
|
||
height: 100%;
|
||
overflow: auto;
|
||
background-color: #000000;
|
||
padding: 20px 100px 20px 100px;
|
||
|
||
:deep(.result-video) {
|
||
width: 100%;
|
||
min-width: 600px;
|
||
height: calc(100% - 60px);
|
||
|
||
.arco-spin {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.arco-image-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
.action {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 20px;
|
||
.mf-button {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 200px;
|
||
height: 40px;
|
||
background: #1a1b20;
|
||
border-radius: 10px;
|
||
color: #fff;
|
||
margin: 0 15px;
|
||
|
||
:deep(.arco-image) {
|
||
margin-right: 8px;
|
||
&-img {
|
||
vertical-align: unset;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 模版选择弹窗样式
|
||
.template-dialog-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: 70vh;
|
||
min-height: 350px;
|
||
}
|
||
|
||
.template-dialog-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
min-height: 0;
|
||
|
||
.template-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 20%);
|
||
gap: 16px;
|
||
padding: 10px 0;
|
||
|
||
.template-item {
|
||
cursor: pointer;
|
||
border: 2px solid transparent;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
transition: all 0.3s;
|
||
position: relative;
|
||
|
||
&:hover {
|
||
border-color: var(--color-primary-light-1);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
&.selected {
|
||
border-color: rgb(var(--primary-6));
|
||
box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.2);
|
||
}
|
||
|
||
:deep(.arco-image) {
|
||
width: 100%;
|
||
height: 300px;
|
||
display: block;
|
||
|
||
.arco-image-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
}
|
||
|
||
.template-name {
|
||
padding: 8px;
|
||
font-size: 12px;
|
||
color: var(--color-text-1);
|
||
text-align: center;
|
||
background: var(--color-fill-1);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-template {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: var(--color-text-3);
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.template-dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
padding: 16px 0 0 0;
|
||
border-top: 1px solid var(--color-border-2);
|
||
flex-shrink: 0;
|
||
background: var(--color-bg-1);
|
||
}
|
||
|
||
@media (max-width: 576px) {
|
||
.fast-video {
|
||
.left {
|
||
width: 100%;
|
||
}
|
||
|
||
.right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: 100px 40px;
|
||
background: linear-gradient(
|
||
0deg,
|
||
rgba(#050b15, 1) 0%,
|
||
rgba(#0f1011, 1) 49%,
|
||
rgba(#111215, 1) 100%
|
||
);
|
||
|
||
&-close {
|
||
right: 20px;
|
||
top: 80px;
|
||
position: fixed;
|
||
display: block;
|
||
cursor: pointer;
|
||
color: #fff;
|
||
|
||
.mf-icon {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.result-video {
|
||
width: 100%;
|
||
min-width: auto;
|
||
}
|
||
|
||
.action {
|
||
width: 100%;
|
||
.mf-button {
|
||
width: calc(50% - 8px);
|
||
margin: 0 10px;
|
||
background-color: rgb(var(--primary-6));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|