fix: 前端优化 支付相关,保存视频,模版内容
This commit is contained in:
parent
73888efd14
commit
4ee2c02daf
|
|
@ -1,14 +1,6 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="模版名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入模版名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
|
|
@ -64,7 +56,11 @@
|
|||
<el-table v-loading="loading" :data="templateList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
|
||||
<el-table-column label="模版名称" align="center" prop="name" />
|
||||
<el-table-column label="AI类型" align="center" prop="aiId">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getAiTypeLabel(scope.row.aiId) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模版中文" align="center" prop="chineseContent" />
|
||||
<el-table-column label="模版英文" align="center" prop="englishContent" />
|
||||
<el-table-column label="模版图片URL" align="center" prop="imageUrl">
|
||||
|
|
@ -79,7 +75,13 @@
|
|||
>保存</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.status === 0">停用</span>
|
||||
<span v-else-if="scope.row.status === 1">正常</span>
|
||||
<span v-else>{{ scope.row.status }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
|
|
@ -112,8 +114,15 @@
|
|||
<!-- 添加或修改AI模版对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="75%" :close-on-click-modal="false" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="模版名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入模版名称" />
|
||||
<el-form-item label="AI类型" prop="aiId">
|
||||
<el-select v-model="form.aiId" placeholder="请选择AI类型" clearable>
|
||||
<el-option
|
||||
v-for="item in aiTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="模版中文">
|
||||
<el-input v-model="form.chineseContent" placeholder="请输入模版中文"/>
|
||||
|
|
@ -188,15 +197,22 @@ export default {
|
|||
chineseContent: null,
|
||||
englishContent: null,
|
||||
imageUrl: null,
|
||||
aiId: null,
|
||||
status: null,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// AI类型选项
|
||||
aiTypeOptions: [
|
||||
{ value: 1, label: "快捷生图" },
|
||||
{ value: 11, label: "一键脱衣" },
|
||||
{ value: 12, label: "图生图2" },
|
||||
{ value: 13, label: "一键换脸" },
|
||||
{ value: 21, label: "快捷生视频" },
|
||||
{ value: 22, label: "视频换脸" }
|
||||
],
|
||||
// 表单校验
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: "模版名称不能为空", trigger: "blur" },
|
||||
],
|
||||
chineseContent: [
|
||||
{ required: true, message: "模版中文不能为空", trigger: "blur" },
|
||||
],
|
||||
|
|
@ -238,6 +254,7 @@ export default {
|
|||
chineseContent: null,
|
||||
englishContent: null,
|
||||
imageUrl: null,
|
||||
aiId: null,
|
||||
status: 1, // 默认状态为正常
|
||||
remark: null,
|
||||
createTime: null,
|
||||
|
|
@ -248,6 +265,14 @@ export default {
|
|||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 获取AI类型标签 */
|
||||
getAiTypeLabel(aiId) {
|
||||
if (aiId == null) return '-'
|
||||
// 处理数字或字符串类型的 aiId
|
||||
const value = typeof aiId === 'string' ? parseInt(aiId) : aiId
|
||||
const option = this.aiTypeOptions.find(item => item.value === value)
|
||||
return option ? option.label : aiId || '-'
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
|
|
|
|||
|
|
@ -73,47 +73,6 @@
|
|||
</mf-button>
|
||||
</div>
|
||||
</mf-dialog>
|
||||
<!-- 银行卡信息输入对话框 -->
|
||||
<mf-dialog
|
||||
:visible="cardInfoVisible"
|
||||
modal-class="card-info-dialog"
|
||||
class="card-info-dialog-wrapper"
|
||||
:title="$t('common.recharge')"
|
||||
hideTitle
|
||||
:footer="false"
|
||||
unmountOnClose
|
||||
@cancel="cancelCardInfo">
|
||||
<div class="card-info-close">
|
||||
<mf-icon
|
||||
value="icon-close"
|
||||
@click="cancelCardInfo" />
|
||||
</div>
|
||||
<div class="card-info-title">
|
||||
{{ $t('common.recharge') }}
|
||||
</div>
|
||||
<mf-input
|
||||
class="card-info-input"
|
||||
:placeholder="$t('common.cardNo')"
|
||||
v-model="cardno" />
|
||||
<mf-input
|
||||
class="card-info-input"
|
||||
:placeholder="$t('common.cardName')"
|
||||
v-model="cardname" />
|
||||
<div class="card-info-submit">
|
||||
<mf-button
|
||||
size="large"
|
||||
@click="cancelCardInfo">
|
||||
{{ $t('common.cancel') }}
|
||||
</mf-button>
|
||||
<mf-button
|
||||
size="large"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="submitCardInfo">
|
||||
{{ $t('common.ok') }}
|
||||
</mf-button>
|
||||
</div>
|
||||
</mf-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -131,11 +90,7 @@ export default {
|
|||
rechargeRemark: '',
|
||||
payVisible: false,
|
||||
orderNo: null,
|
||||
showPay: import.meta.env.VITE_SHOW_PAY == "SUCCESS",
|
||||
cardInfoVisible: false,
|
||||
cardno: '',
|
||||
cardname: '',
|
||||
currentGearId: null
|
||||
showPay: import.meta.env.VITE_SHOW_PAY == "SUCCESS"
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
|
@ -153,12 +108,6 @@ export default {
|
|||
this.orderNo = null
|
||||
this.payVisible = false
|
||||
},
|
||||
cancelCardInfo() {
|
||||
this.cardInfoVisible = false
|
||||
this.cardno = ''
|
||||
this.cardname = ''
|
||||
this.currentGearId = null
|
||||
},
|
||||
selectItem(item, idx) {
|
||||
this.selectedIndex = idx
|
||||
this.rechargeRemark = item.title
|
||||
|
|
@ -218,37 +167,13 @@ export default {
|
|||
},
|
||||
ok() {
|
||||
let selectedItem = this.dataList[this.selectedIndex]
|
||||
// 判断是否是 jinsha-pay 接口,需要先输入银行卡信息
|
||||
if (this.apiUrl && this.apiUrl.includes('jinsha-pay')) {
|
||||
this.currentGearId = selectedItem.id
|
||||
this.cardInfoVisible = true
|
||||
return
|
||||
}
|
||||
// 其他支付方式直接调用
|
||||
this.doRecharge(selectedItem.id)
|
||||
},
|
||||
submitCardInfo() {
|
||||
// 验证银行卡信息
|
||||
if (!this.cardno || !this.cardno.trim()) {
|
||||
this.$message.error(this.$t('common.cardNoRequired') || '请输入银行卡号')
|
||||
return
|
||||
}
|
||||
if (!this.cardname || !this.cardname.trim()) {
|
||||
this.$message.error(this.$t('common.cardNameRequired') || '请输入银行卡姓名')
|
||||
return
|
||||
}
|
||||
this.doRecharge(this.currentGearId, this.cardno.trim(), this.cardname.trim())
|
||||
},
|
||||
doRecharge(gearId, cardno, cardname) {
|
||||
doRecharge(gearId) {
|
||||
this.loading = true
|
||||
let params = {
|
||||
gearId: gearId
|
||||
}
|
||||
// 如果是 jinsha-pay,添加银行卡信息参数
|
||||
if (cardno && cardname) {
|
||||
params.cardno = cardno
|
||||
params.cardname = cardname
|
||||
}
|
||||
this.$axios({
|
||||
url: this.apiUrl,
|
||||
method: 'get',
|
||||
|
|
@ -262,21 +187,13 @@ export default {
|
|||
return;
|
||||
}
|
||||
if (res.code == 200) {
|
||||
// jinsha-pay 返回格式: {code: 0, msg: "成功"}
|
||||
// 其他支付方式返回: {orderNo, payUrl}
|
||||
if (this.apiUrl && this.apiUrl.includes('jinsha-pay')) {
|
||||
// jinsha支付成功,关闭对话框
|
||||
this.cardInfoVisible = false
|
||||
this.cardno = ''
|
||||
this.cardname = ''
|
||||
this.$message.success(this.$t('common.rechargeSuccessfully') || '充值成功,请等待处理')
|
||||
} else {
|
||||
// 其他支付方式,跳转支付页面
|
||||
// 获取支付URL并跳转
|
||||
this.orderNo = res.data?.orderNo;
|
||||
this.rechargeUrl = res.data?.payUrl;
|
||||
if (res.data?.payUrl) {
|
||||
window.open(res.data.payUrl)
|
||||
}
|
||||
} else {
|
||||
this.$message.error(this.$t('common.rechargeFailed') || '获取支付链接失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -394,96 +311,6 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-info-dialog {
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(39, 20, 51, 0.7) 0%,
|
||||
rgba(230, 33, 122, 0.7) 49%
|
||||
);
|
||||
border-radius: 20px;
|
||||
border: 2px solid #e6217a;
|
||||
width: 500px;
|
||||
top: 45% !important;
|
||||
transform: translateY(-45%) !important;
|
||||
&-wrapper {
|
||||
.arco-modal-mask {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.arco-modal-body {
|
||||
padding: 24px 30px 30px 30px;
|
||||
}
|
||||
|
||||
.card-info-close {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 6px;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
|
||||
.mf-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-info-title {
|
||||
font-size: 20px;
|
||||
color: #ffffff;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-info-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(#ffffff, 0.3);
|
||||
height: 40px;
|
||||
margin-top: 20px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
cursor: text;
|
||||
font-size: 14px;
|
||||
|
||||
::placeholder {
|
||||
color: rgba(#fff, 0.5);
|
||||
}
|
||||
|
||||
&.arco-input-wrapper:focus-within {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
border-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.card-info-submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
gap: 14px;
|
||||
.mf-button {
|
||||
width: 160px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.mf-recharge-pc {
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,15 @@
|
|||
{{ $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"
|
||||
|
|
@ -24,6 +33,27 @@
|
|||
: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">
|
||||
|
|
@ -59,6 +89,41 @@
|
|||
{{ $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 class="template-name">{{ getTemplateName(template) }}</div>
|
||||
</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">
|
||||
|
|
@ -117,7 +182,6 @@ export default {
|
|||
prefixCls: 'fast-video',
|
||||
firstUrl: '',
|
||||
lastUrl: '',
|
||||
current: 1,
|
||||
text: '',
|
||||
interval: null,
|
||||
videoUrl: null,
|
||||
|
|
@ -130,7 +194,16 @@ export default {
|
|||
tags: [],
|
||||
selectedTags: {},
|
||||
showResult: !this.$base.isMobile(),
|
||||
id: null
|
||||
id: null,
|
||||
// 上传方式选择
|
||||
uploadType: 'upload', // 'upload' 或 'template'
|
||||
// 模版选择相关
|
||||
templateDialogVisible: false,
|
||||
templateList: [],
|
||||
templateLoading: false,
|
||||
selectedTemplate: null,
|
||||
selectedTemplatePreview: '', // 选中的模版预览图URL
|
||||
maxPollAttempts: 40 // 最大轮询次数(3秒 * 40 = 120秒超时)
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
|
|
@ -138,9 +211,12 @@ export default {
|
|||
},
|
||||
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
|
||||
}
|
||||
|
|
@ -171,6 +247,100 @@ export default {
|
|||
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
|
||||
if (this.lang === 'zh_HK') {
|
||||
return template.chineseContent || template.name || ''
|
||||
}
|
||||
// 如果是英文,显示 englishContent
|
||||
else if (this.lang === 'en_US') {
|
||||
return template.englishContent || template.name || ''
|
||||
}
|
||||
// 默认返回 name
|
||||
return 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',
|
||||
|
|
@ -191,8 +361,51 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
saveVideo() {
|
||||
this.$file.downloadFile(this.videoUrl)
|
||||
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) {
|
||||
|
|
@ -226,7 +439,16 @@ export default {
|
|||
},
|
||||
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'
|
||||
|
|
@ -244,11 +466,13 @@ export default {
|
|||
}, 3000)
|
||||
},
|
||||
generateVideo() {
|
||||
if (!this.firstUrl || !this.firstUrl.url) {
|
||||
// 处理 firstUrl,可能是对象或字符串
|
||||
const firstImageUrl = typeof this.firstUrl === 'object' ? this.firstUrl.url : this.firstUrl
|
||||
if (!firstImageUrl) {
|
||||
this.$message.error(this.$t('common.uploadFirstImageError'))
|
||||
return
|
||||
}
|
||||
if (this.firstUrl.url.startsWith('blob:')) {
|
||||
if (firstImageUrl.startsWith('blob:')) {
|
||||
this.$message.error(this.$t('common.uploadWaitImageError'))
|
||||
return
|
||||
}
|
||||
|
|
@ -260,7 +484,7 @@ export default {
|
|||
this.generateLoading = true
|
||||
let params = {
|
||||
text: this.text,
|
||||
firstUrl: this.firstUrl.url,
|
||||
firstUrl: firstImageUrl,
|
||||
functionType: '21'
|
||||
}
|
||||
if (this.lastUrl && this.lastUrl.url) {
|
||||
|
|
@ -401,6 +625,20 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
|
|
@ -410,6 +648,43 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.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);
|
||||
|
|
@ -491,6 +766,87 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
// 模版选择弹窗样式
|
||||
.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 {
|
||||
|
|
|
|||
|
|
@ -266,11 +266,13 @@ export default {
|
|||
// 加载模板列表
|
||||
loadTemplateList() {
|
||||
this.templateLoading = true
|
||||
// 根据当前功能类型传递 ai_id 参数
|
||||
this.$axios({
|
||||
url: 'api/template/getTemplateList',
|
||||
method: 'GET',
|
||||
data: {
|
||||
position: 0
|
||||
position: 0,
|
||||
aiId: 11
|
||||
}
|
||||
}).then(res => {
|
||||
this.templateList = res.rows || []
|
||||
|
|
|
|||
Loading…
Reference in New Issue