Compare commits

..

No commits in common. "seedance" and "master" have entirely different histories.

46 changed files with 123 additions and 2551 deletions

27
.gitignore vendored
View File

@ -1,27 +0,0 @@
.DS_Store
node_modules/
unpackage
/dist/
/manager/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/test/unit/coverage/
/test/e2e/reports/
selenium-debug.log
nodejs/public/uploads
*.zip
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
package-lock.json
.history
/unpackage/resources/
/unpackage/dist/dev
/unpackage/dist/build/.automator
/unpackage/dist/build/app-plus
*.lock

View File

@ -5,8 +5,6 @@
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/python" />
<excludePattern pattern="*.zip" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@ -5,4 +5,4 @@ VUE_APP_TITLE = 管理系统
ENV = 'production'
# 若依管理系统/生产环境
VUE_APP_BASE_API = 'http://47.86.170.114:8011'
VUE_APP_BASE_API = 'https://admin-api.undressing.name'

1
admin-ui/.gitignore vendored
View File

@ -9,7 +9,6 @@ yarn-error.log*
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
*.zip
# Editor directories and files
.idea

View File

@ -4,4 +4,3 @@ VITE_AMAP_SAFE_KEY = 23d3fdbc6b4eaf65d4a82a16510938c9
VITE_REGISTER_SOURCE = ALL
#VITE_REGISTER_SOURCE = XM001
VITE_SHOW_PAY = SUCCESS
VITE_API_URL = http://47.86.170.114:8011/api

View File

@ -1,3 +1,3 @@
# 端口
VITE_PORT = 8887
VITE_API_URL = http://47.86.170.114:8011/api
VITE_API_URL = https://api.undressing.name/api

View File

@ -10,7 +10,7 @@ yarn-error.log*
/test/e2e/reports/
selenium-debug.log
nodejs/public/uploads
*.zip
# Editor directories and files
.idea
.vscode

File diff suppressed because one or more lines are too long

View File

@ -615,4 +615,4 @@
right: 15px
}
/* Quill core styles - source map removed to fix build error */
/*# sourceMappingURL=quill.core.css.map*/

View File

@ -1286,4 +1286,4 @@
border: 1px solid #ccc
}
/* Quill snow styles - source map removed to fix build error */
/*# sourceMappingURL=quill.snow.css.map*/

View File

@ -8,6 +8,10 @@
fullscreen
:class="`${prefixCls}-wrapper`"
:modal-class="`${prefixCls}-dialog`">
<mf-video
autoplay
:controls="false"
modelValue="https://images.iqyjsnwv.com/tmp/hello-BCSGJ8fP.mp4" />
<div :class="`${prefixCls}-shadow`">
<div :class="`${prefixCls}-title`">
{{ $t('common.fbTitle') }}

View File

@ -462,11 +462,12 @@ export default {
//
getCountryName(country) {
if (!country) return ''
//
if (this.lang === 'zh_HK') {
return country.name || country.nameEn || ''
//
if (this.lang === 'en_US') {
return country.nameEn || country.name
}
return country.nameEn || country.name || ''
//
return country.name
},
//
filterCountry(inputValue, option) {

View File

@ -1,412 +0,0 @@
<template>
<div class="rich-editor-root">
<!-- 工具栏 -->
<div class="toolbar">
<button class="tool-btn" type="button" @click="execCommand('bold')"><b>B</b></button>
<button class="tool-btn" type="button" @click="execCommand('italic')"><i>I</i></button>
<button class="tool-btn" type="button" @click="openImagePicker">{{ $t('common.insertImage') || '插入图片' }}</button>
<button class="tool-btn" type="button" @click="clear">清空</button>
</div>
<!-- 隐藏的文件输入 -->
<input
ref="fileInputRef"
class="hidden-input"
type="file"
accept="image/*"
multiple
@change="handleSelectFiles"
/>
<!-- 编辑器区域 -->
<div
ref="editorRef"
class="user-input rich-editor"
contenteditable="true"
:data-placeholder="placeholder || '请输入文本...'"
@input="handleInput"
@keyup="handleKeyup"
@click="handleEditorClick"
@keydown="handleKeydown"
></div>
<!-- @ 图片选择面板 -->
<div v-if="mentionVisible" class="mention-panel">
<div
v-for="(item, idx) in mentionImageList"
:key="item.url"
class="mention-item"
@mousedown.prevent="insertMentionImage(item)"
>
<img :src="item.url" class="mention-thumb" />
<span class="mention-label">@{{ idx + 1 }}</span>
</div>
<div v-if="mentionImageList.length === 0" class="mention-empty">
{{ $t('common.noImageToMention') || '暂无可引用图片' }}
</div>
</div>
</div>
</template>
<script>
import { nextTick, onMounted, ref, watch } from 'vue'
import { uploadFile } from '@/utils/file'
export default {
name: 'RichTextEditor',
props: {
modelValue: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入文本生成视频...'
},
uploadedImages: {
type: Array,
default: () => []
}
},
emits: ['update:modelValue', 'text-change', 'image-upload'],
setup(props, { emit }) {
const editorRef = ref(null)
const fileInputRef = ref(null)
const savedSelectionRange = ref(null)
const mentionVisible = ref(false)
const mentionImageList = ref([])
const MAX_IMAGE_COUNT = 4
//
const execCommand = (command) => {
if (!editorRef.value) return
editorRef.value.focus()
document.execCommand(command, false, null)
handleInput()
}
//
const openImagePicker = () => {
if (fileInputRef.value) {
fileInputRef.value.click()
}
}
// -
const handleSelectFiles = async (event) => {
const files = event.target.files
if (!files.length) return
for (let file of files) {
if (file.type.startsWith('image/')) {
try {
//
const loading = window.$message ? window.$message.loading('上传中...') : null
//
const res = await uploadFile({
url: '/api/cos/upload', // 使COS
file: file,
name: 'file'
})
if (res && res.code === 200 && res.data) {
const imageUrl = typeof res.data === 'string' ? res.data : (res.data.url || res.data)
insertImage(imageUrl, file.name)
// @
emit('image-upload', { url: imageUrl, name: file.name })
} else {
console.error('上传失败:', res)
alert('图片上传失败')
}
} catch (error) {
console.error('上传图片出错:', error)
alert('图片上传失败: ' + (error.message || '未知错误'))
}
}
}
// input
event.target.value = ''
}
//
const insertImage = (url, name = 'image') => {
if (!editorRef.value) return
editorRef.value.focus()
const selection = window.getSelection()
if (!selection) return
const img = document.createElement('img')
img.src = url
img.alt = name
img.style.maxWidth = '100%'
img.style.height = 'auto'
img.style.margin = '4px 0'
img.setAttribute('data-image-name', name)
const range = selection.getRangeAt(0)
range.deleteContents()
range.insertNode(img)
//
const space = document.createTextNode(' ')
range.setStartAfter(img)
range.setEndAfter(img)
range.insertNode(space)
handleInput()
}
//
const handleInput = () => {
if (!editorRef.value) return
const content = editorRef.value.innerHTML
emit('update:modelValue', content)
emit('text-change', content)
}
//
const handleKeyup = (e) => {
if (e.key === '@') {
showMentionPanel()
} else if (e.key === 'Escape' && mentionVisible.value) {
mentionVisible.value = false
}
}
const handleKeydown = (e) => {
if (e.key === '@') {
//
const selection = window.getSelection()
if (selection && selection.rangeCount > 0) {
savedSelectionRange.value = selection.getRangeAt(0).cloneRange()
}
}
}
// @
const showMentionPanel = () => {
if (props.uploadedImages && props.uploadedImages.length > 0) {
mentionImageList.value = props.uploadedImages.slice(0, MAX_IMAGE_COUNT)
mentionVisible.value = true
}
}
// @
const insertMentionImage = (imageItem) => {
if (!editorRef.value || !savedSelectionRange.value) {
mentionVisible.value = false
return
}
editorRef.value.focus()
const selection = window.getSelection()
if (selection) {
selection.removeAllRanges()
selection.addRange(savedSelectionRange.value)
}
const img = document.createElement('img')
img.src = imageItem.url
img.alt = '@图片'
img.style.maxWidth = '60px'
img.style.height = 'auto'
img.style.verticalAlign = 'middle'
img.setAttribute('data-mention', 'true')
const range = savedSelectionRange.value
range.deleteContents()
range.insertNode(img)
//
const space = document.createTextNode(' ')
range.setStartAfter(img)
range.setEndAfter(img)
range.insertNode(space)
mentionVisible.value = false
handleInput()
}
//
const clear = () => {
if (!editorRef.value) return
editorRef.value.innerHTML = ''
handleInput()
}
//
const handleEditorClick = () => {
if (mentionVisible.value) {
mentionVisible.value = false
}
}
// props
watch(() => props.modelValue, (newValue) => {
if (editorRef.value && editorRef.value.innerHTML !== newValue) {
editorRef.value.innerHTML = newValue || ''
}
})
//
watch(() => props.uploadedImages, (newImages) => {
mentionImageList.value = newImages || []
}, { immediate: true })
onMounted(() => {
if (editorRef.value) {
editorRef.value.innerHTML = props.modelValue || ''
//
editorRef.value.addEventListener('paste', (e) => {
const items = e.clipboardData.items
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
const blob = items[i].getAsFile()
const reader = new FileReader()
reader.onload = (event) => {
insertImage(event.target.result, 'pasted-image')
}
reader.readAsDataURL(blob)
e.preventDefault()
break
}
}
})
}
})
return {
editorRef,
fileInputRef,
mentionVisible,
mentionImageList,
execCommand,
openImagePicker,
handleSelectFiles,
handleInput,
handleKeyup,
handleKeydown,
handleEditorClick,
insertMentionImage,
clear
}
}
}
</script>
<style lang="less" scoped>
.rich-editor-root {
position: relative;
margin-bottom: 16px;
}
.toolbar {
display: flex;
gap: 8px;
margin-bottom: 8px;
padding: 8px;
background: #1a1b20;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.1);
.tool-btn {
padding: 6px 12px;
background: rgba(255,255,255,0.1);
border: none;
border-radius: 4px;
color: #fff;
cursor: pointer;
font-size: 14px;
&:hover {
background: rgba(255,255,255,0.2);
}
}
}
.rich-editor {
min-height: 120px;
padding: 12px;
background: #1a1b20;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
color: #fff;
line-height: 1.6;
font-size: 14px;
outline: none;
overflow-y: auto;
&:empty:before {
content: attr(data-placeholder);
color: #666;
pointer-events: none;
}
img {
max-width: 100%;
height: auto;
margin: 4px 0;
border-radius: 4px;
}
}
.mention-panel {
position: absolute;
top: 100%;
left: 0;
background: #1f2128;
border: 1px solid #3a3d47;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
max-height: 200px;
overflow-y: auto;
width: 280px;
.mention-item {
display: flex;
align-items: center;
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid rgba(255,255,255,0.1);
&:hover {
background: #2a2d38;
}
&:last-child {
border-bottom: none;
}
.mention-thumb {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: 4px;
margin-right: 12px;
}
.mention-label {
color: #a1a4b3;
font-size: 13px;
}
}
.mention-empty {
padding: 20px;
text-align: center;
color: #666;
font-size: 13px;
}
}
.hidden-input {
display: none;
}
</style>

View File

@ -237,18 +237,11 @@ export default {
{content}
</div></div>
}
{/* 图片预览区域 - 点击框体可替换 */}
{/* 图片预览区域 */}
{!this.$datas.isEmpty(mValue) && (
<div class="mf-image-upload-draggable-preview">
{mValue.map(item => {
return <div
class="mf-image-upload-draggable-preview-item mf-image-upload-draggable-replace"
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
if (!readonly && !this.disabled) this.triggerUpload()
}}
>
return <div class="mf-image-upload-draggable-preview-item">
<a-image
width="100%"
height="100%"
@ -257,16 +250,6 @@ export default {
preview={true}
/>
<div class="mf-image-upload-draggable-preview-mask">
{!readonly && (
<mf-icon
value="icon-edit"
onClick={(e) => {
e.stopPropagation()
this.triggerUpload()
}}
title={this.$t('common.replaceImage') || '替换'}
/>
)}
{item.type !== '.pdf' && (
<mf-icon
value="icon-eye"
@ -314,52 +297,6 @@ export default {
</div>
}
},
// 触发文件选择(用于替换已上传图片)
triggerUpload() {
if (this.readonly || this.disabled) return
const mValue = this.getValue()
// draggable 且已有图片时,用专用替换 input避免 a-upload 在 limit 满时不渲染 input
if (this.listType === 'draggable' && mValue.length > 0 && this.$refs.replaceInput) {
this.$refs.replaceInput.value = ''
this.$refs.replaceInput.click()
return
}
const el = this.$refs.mfUpload?.$el
const input = el?.querySelector?.('input[type="file"]')
if (input) {
input.value = ''
input.click()
}
},
// 替换时选择文件后手动上传
handleReplaceFileChange(e) {
const file = e.target.files[0]
if (!file) return
if (file.size / 1024 / 1024 > 10) {
this.$message.error('文件大小不能超过10M')
e.target.value = ''
return
}
this.$emit('beforeUpload', file)
const formData = new FormData()
formData.append('file', file)
const action = import.meta.env.MODE === 'production' ? 'api/file/upload' : 'dev-api/api/file/upload'
const headers = { ...this.headers }
delete headers['Content-Type']
fetch(action, { method: 'POST', body: formData, headers })
.then((res) => res.json())
.then((data) => {
if (data.code === 200 && data.url) {
const url = this.getImageUrl(data.url)
this.handleChange({ url, name: file.name })
this.$emit('success', data.url, file)
} else {
this.$message.error(data.msg || data.url || '上传失败')
}
})
.catch(() => this.$message.error('上传失败'))
.finally(() => { e.target.value = '' })
},
handlePreview() {
let images = this.getValue().map((i) => i.url)
this.$viewerApi({
@ -549,16 +486,6 @@ export default {
}
return (
<div class={`${prefixCls}-wrap`}>
{listType === 'draggable' && (
<input
ref="replaceInput"
type="file"
accept={accept}
style={{ position: 'absolute', opacity: 0, width: 0, height: 0, pointerEvents: 'none' }}
onChange={this.handleReplaceFileChange}
/>
)}
<a-upload
ref="mfUpload"
class={wrapCls}
@ -566,9 +493,7 @@ export default {
name="file"
headers={headers}
{...imageUploadProps}
v-slots={slots}
/>
</div>
v-slots={slots}></a-upload>
)
}
}

View File

@ -112,9 +112,6 @@
&-item {
height: 100%;
position: relative;
&.mf-image-upload-draggable-replace {
cursor: pointer;
}
}
&-mask {
position: absolute;

View File

@ -1,179 +0,0 @@
export default {
image1: 'خلع بضغطة واحدة',
image2: 'صورة إلى صورة 2',
uploadImage: 'رفع صورة',
uploadImageTip: 'PNG/JPG، بحد أقصى 10 ميجا',
uploadPlaceholder: 'انقر لرفع صورة',
selectImageSource: 'اختر مصدر الصورة',
selectTemplate: 'اختر القالب',
reselectTemplate: 'إعادة اختيار القالب',
noTemplates: 'لا توجد قوالب',
tag1: 'نوع الوسم 1',
tag2: 'نوع الوسم 2',
tag3: 'نوع الوسم 3',
generateImage: 'إنشاء الآن (يخصم {score} من الرصيد)',
generateImageNow: 'إنشاء الآن',
generateTip: 'بعد الإرسال يمكنك المشاهدة في "أعمالي"',
generateVideo: 'إنشاء فيديو',
imageFace: 'تبديل الوجه في صورة',
videoFace: 'تبديل الوجه في فيديو',
uploadImageFace: 'انقر لرفع صورة الوجه',
uploadTemplate: 'انقر لرفع قالب مخصص',
textPlaceholder: 'صف الصورة التي تريد إنشاءها',
uploadImageError: 'يرجى رفع صورة',
replaceImage: 'استبدال الصورة',
uploadFaceImageError: 'يرجى رفع صورة الوجه',
uploadTemplateError: 'يرجى رفع قالب مخصص',
textError: 'يرجى إدخال النص التوجيهي',
textVideoPlaceholder: 'صف الفيديو الذي تريد إنشاءه',
uploadFirstPlaceholder: 'انقر لرفع الإطار الأول',
uploadLastPlaceholder: 'انقر لرفع الإطار الأخير',
uploadFirstImageError: 'يرجى رفع صورة الإطار الأول',
uploadWaitImageError: 'يرجى انتظار انتهاء الرفع',
saveVideo: 'تحميل الفيديو',
videoLoadingText: 'جاري إنشاء الفيديو...',
viewVideo: 'عرض الفيديو',
changeFacePrompt: 'أخذ الوجه من الصورة الثانية واستبداله بالوجه في الأولى',
rechartTip1: 'عند الشحن via المحفظة تأكد من شبكة المحفظة.',
rechartTip2: 'قد يتأخر الشحن؛ انتظر 35 دقائق ثم حدّث.',
walletAddr: 'عنوان المحفظة:',
fbTitle: 'تحذير! هذا الموقع للبالغين فقط!',
fbContent: 'بالدخول أؤكد أن عمري 18 سنة أو أكثر',
fbCancel: 'أقل من 18',
fbOK: 'عمري 18+',
sorry: 'عذراً!',
useLess: 'لا يمكنك استخدام هذا الموقع...',
loginAccount: 'تسجيل الدخول',
logout: 'تسجيل الخروج',
userEmailPlaceholder: 'أدخل البريد أو اسم المستخدم',
passwordPlaceholder: 'أدخل كلمة المرور',
forgetPassword: 'نسيت كلمة المرور؟',
register: 'التسجيل',
login: 'دخول',
forgotPassword: 'نسيت كلمة المرور',
registerAccount: 'إنشاء حساب',
usernamePlaceholder: 'أدخل اسم المستخدم',
codePlaceholder: 'أدخل رمز التحقق',
confirmPasswordPlaceholder: 'تأكيد كلمة المرور',
backToLogin: 'العودة لتسجيل الدخول',
send: 'إرسال',
emailValidPlaceholder: 'أدخل بريداً إلكترونياً صحيحاً',
confirmPwdValidMsg: 'كلمات المرور غير متطابقة',
editPassword: 'تغيير كلمة المرور',
recharge: 'شحن',
myAccount: 'حسابي',
moneyInvite: 'دعوة مكافآت',
rechargeRecord: 'سجل الشحن',
resumeRecord: 'سجل الاستهلاك',
inviteRecord: 'سجل الدعوات',
username: 'اسم المستخدم',
userId: 'معرف المستخدم',
accountAmount: 'الرصيد',
editUserInfo: 'تعديل البيانات',
contact: 'تواصل معنا',
backToUser: 'رجوع',
moneyTips: 'عند دعوة صديق للتسجيل تحصل على {rate} من مبلغ شحنه في كل مرة.',
inviteCode: 'رمز الدعوة',
inviteLink: 'رابط الدعوة',
saveImage: 'حفظ الصورة',
totalAmount: 'إجمالي الشحن',
amount: 'المبلغ',
sendAmount: 'مبلغ الهدية',
rechargeType: 'طريقة الشحن',
rechargeTime: 'وقت الشحن',
emptyText: 'لا توجد بيانات',
product: 'عمل',
resumeAmount: 'المبلغ المستهلك',
productType: 'نوع العمل',
productTime: 'وقت الإنشاء',
totalReward: 'رصيد المكافآت',
rewardAmount: 'مبلغ المكافأة',
rewardTime: 'وقت المكافأة',
reSend: 'إعادة الإرسال',
registerSuccessfully: 'تم التسجيل بنجاح',
loginSuccessfully: 'تم تسجيل الدخول',
passwordResetSuccessfully: 'تم إعادة تعيين كلمة المرور',
rechargeSuccessfully: 'تم الشحن بنجاح',
avatar: 'الصورة الرمزية',
input: 'أدخل',
save: 'حفظ',
editEmail: 'تغيير البريد',
editEmailSuccessfully: 'تم تحديث البريد',
updateAvatarSuccessfully: 'تم تحديث الصورة الرمزية',
balenceLow: 'رصيدك غير كافٍ، يرجى الشحن',
confirm: 'تأكيد',
createFailed: 'فشل الإنشاء، تمت إعادة الرصيد',
notice: 'تنبيه',
oldPasswordPlaceholder: 'أدخل كلمة المرور القديمة',
newpasswordPlaceholder: 'أدخل كلمة المرور الجديدة',
switchPageTip: 'الفيديو قيد الإنشاء؛ اخرج وتحقق من سجل الاستهلاك.',
loginless: 'انتهت الجلسة! يرجى تسجيل الدخول مجدداً.',
createVideo: 'إنشاء فيديو (يخصم {price} من الرصيد)',
ok: 'تأكيد',
rechartNotice: 'تعليمات الشحن',
rechargeNotice1: '1. يُضاف الشحن فوراً. إن لم يتغير الرصيد حدّث الصفحة.',
rechargeNotice2: '2. الباقات المحدودة عروض؛ يمكنك الشحن أكثر من مرة.',
rechargeNotice3: '3. الرصيد المشحون لا ينتهي صلاحيته.',
rechargeNotice4: 'راجع الباقة قبل الشراء؛ لا استرداد.',
rechargeLeft: 'الرصيد',
dollor: 'USD',
isDevelop: 'قيد التطوير.',
copySuccessfully: 'تم النسخ',
copyLink: 'نسخ الرابط',
goPay: 'الذهاب للدفع',
filePreview: 'معاينة',
doSame: 'عمل نسخة مشابهة',
saveQRCode: 'حفظ رمز QR',
invitationCodePlaceholder: 'أدخل رمز الدعوة',
testRecharge: 'شحن تجريبي',
orderNo: 'رقم الطلب',
orderNoP: 'رقم الطلب مطلوب',
emailNotExists: 'هذا البريد غير مسجّل',
gearNotExists: 'خطة الشحن المختارة غير موجودة؛ اختر غيرها.',
myProduct: 'أعمالي',
permissionError: 'إجمالي شحنك {total}، لا يكفي للمعاينة.',
createTagFailed: 'فشل الإنشاء؛ حدّث وجرب مرة أخرى.',
loadingText: 'جاري التحميل...',
hasMore: 'اسحب لأعلى للمزيد',
noMore: 'لا مزيد',
giftAmount: 'المبلغ المُستَوفى',
cardNo: 'رقم البطاقة',
cardName: 'اسم صاحب البطاقة',
cardNoRequired: 'أدخل رقم البطاقة',
cardNameRequired: 'أدخل اسم صاحب البطاقة',
rechargeFailed: 'فشل الشحن',
vmCardInfo: 'بيانات البطاقة',
cardNumber: 'رقم البطاقة',
cardNumberPlaceholder: 'أدخل رقم البطاقة',
cardNumberRequired: 'أدخل رقم البطاقة',
cardNumberInvalid: 'رقم البطاقة يجب أن يكون 1319 رقماً',
cvc: 'CVC',
cvcPlaceholder: 'أدخل CVC',
cvcRequired: 'أدخل CVC',
cvcInvalid: 'CVC يجب أن يكون 3 أرقام',
expYear: 'سنة انتهاء الصلاحية',
expYearPlaceholder: 'أدخل السنة (مثل 2027)',
expYearRequired: 'أدخل سنة انتهاء الصلاحية',
expYearInvalid: 'السنة يجب أن تكون 4 أرقام',
expMonth: 'شهر انتهاء الصلاحية',
expMonthPlaceholder: 'اختر الشهر',
expMonthRequired: 'اختر الشهر',
email: 'البريد',
emailPlaceholder: 'أدخل البريد',
emailRequired: 'أدخل البريد',
emailInvalid: 'صيغة البريد غير صحيحة',
emailMaxLength: 'البريد لا يتجاوز 30 حرفاً',
firstName: 'الاسم الأول',
firstNamePlaceholder: 'أدخل الاسم الأول',
firstNameRequired: 'أدخل الاسم الأول',
firstNameMaxLength: 'الاسم لا يتجاوز 30 حرفاً',
lastName: 'اسم العائلة',
lastNamePlaceholder: 'أدخل اسم العائلة',
lastNameRequired: 'أدخل اسم العائلة',
lastNameMaxLength: 'اسم العائلة لا يتجاوز 30 حرفاً',
country: 'الدولة',
countryPlaceholder: 'اختر الدولة',
countryRequired: 'اختر الدولة',
submit: 'إرسال',
cancel: 'إلغاء'
}

View File

@ -1,7 +0,0 @@
import route from './route'
import common from './common.js'
export default {
route,
common
}

View File

@ -1,12 +0,0 @@
export default {
index: 'الرئيسية',
imageToImage: 'خلع بضغطة واحدة',
imageToImage2: 'صورة إلى صورة 2',
changeFace: 'تبديل الوجه',
changeFaceVideo: 'تبديل الوجه في الفيديو',
fastImage: 'إنشاء صورة',
fastVideo: 'إنشاء فيديو',
recharge: 'شحن سريع',
help: 'مركز المساعدة',
moneyInvite: 'دعوة مكافآت'
}

View File

@ -23,7 +23,6 @@ export default {
uploadTemplate: 'Click to upload custom template',
textPlaceholder: 'Describe the image you want to generate',
uploadImageError: 'Please upload an image',
replaceImage: 'Replace image',
textError: 'Please enter a prompt',
textVideoPlaceholder: "Describe the video you want to generate",
uploadFirstPlaceholder: 'Click to upload first frame',
@ -143,14 +142,6 @@ export default {
cardNoRequired: 'Please enter card number',
cardNameRequired: 'Please enter cardholder name',
rechargeFailed: 'Recharge failed',
// Model selection
selectModel: 'Select Model',
seedance20: 'Seedance 2.0',
seedance20Fast: 'Seedance 2.0 Fast',
// Rich text editor
insertImage: 'Insert Image',
mentionImage: 'Mention Image',
noImageToMention: 'No images to mention',
// VM支付相关
vmCardInfo: 'Credit Card Information',
cardNumber: 'Card Number',
@ -183,9 +174,5 @@ export default {
country: 'Country',
countryPlaceholder: 'Select country',
countryRequired: 'Please select country',
videoGen: 'Video Generation',
uploadFirstImage: 'Upload Reference Image',
insertImage: 'Insert Image',
submit: 'Submit',
cancel: 'Cancel'
submit: 'Submit'
}

View File

@ -1,12 +1,11 @@
export default {
index: 'Home',
imageToImage: 'Image-to-Image 1',
imageToImage: 'One-click undressing',
imageToImage2: 'Image-to-Image2',
changeFace: 'Swap Face',
changeFaceVideo: 'Swap Video Face',
fastImage: 'Gen Image',
fastVideo: 'Gen Video',
videoGen: 'Video Generation',
recharge: 'Quick Recharge',
help: 'Help Center',
moneyInvite: 'Reward Invitation'

View File

@ -1,179 +0,0 @@
export default {
image1: 'Desvestir en un clic',
image2: 'Imagen a Imagen 2',
uploadImage: 'Subir imagen',
uploadImageTip: 'Soporta PNG/JPG, máx. 10MB',
uploadPlaceholder: 'Clic para subir imagen',
selectImageSource: 'Seleccionar fuente de imagen',
selectTemplate: 'Seleccionar plantilla',
reselectTemplate: 'Reseleccionar plantilla',
noTemplates: 'Sin plantillas',
tag1: 'Tipo de etiqueta 1',
tag2: 'Tipo de etiqueta 2',
tag3: 'Tipo de etiqueta 3',
generateImage: 'Generar ahora (consume {score} de saldo)',
generateImageNow: 'Generar ahora',
generateTip: 'Después de enviar, puede ver en "Mis obras"',
generateVideo: 'Generar video',
imageFace: 'Cambiar rostro en imagen',
videoFace: 'Cambiar rostro en video',
uploadImageFace: 'Clic para subir imagen de rostro',
uploadTemplate: 'Clic para subir plantilla personalizada',
textPlaceholder: 'Describa la imagen que desea generar',
uploadImageError: 'Por favor suba una imagen',
replaceImage: 'Reemplazar imagen',
uploadFaceImageError: 'Por favor suba una imagen de rostro',
uploadTemplateError: 'Por favor suba una plantilla personalizada',
textError: 'Por favor escriba el texto de indicación',
textVideoPlaceholder: 'Describa el video que desea generar',
uploadFirstPlaceholder: 'Clic para subir imagen del primer frame',
uploadLastPlaceholder: 'Clic para subir imagen del último frame',
uploadFirstImageError: 'Por favor suba la imagen del primer frame',
uploadWaitImageError: 'Espere a que termine la subida',
saveVideo: 'Descargar video',
videoLoadingText: 'Generando video...',
viewVideo: 'Ver video',
changeFacePrompt: 'Extraer el rostro de la segunda imagen y reemplazar en la primera',
rechartTip1: 'Al recargar con transferencia de billetera, confirme la red del monedero.',
rechartTip2: 'La recarga puede tardar; espere 3-5 minutos antes de actualizar.',
walletAddr: 'Dirección del monedero',
fbTitle: 'Advertencia: este sitio es solo para adultos.',
fbContent: 'Al entrar confirmo que tengo 18 años o más',
fbCancel: 'Menor de 18',
fbOK: 'Tengo 18 o más',
sorry: '¡Lo sentimos!',
useLess: 'No puede usar este sitio...',
loginAccount: 'Iniciar sesión',
logout: 'Cerrar sesión',
userEmailPlaceholder: 'Introduzca correo o usuario',
passwordPlaceholder: 'Introduzca contraseña',
forgetPassword: '¿Olvidó la contraseña?',
register: 'Registrarse',
login: 'Iniciar sesión',
forgotPassword: 'Olvidé la contraseña',
registerAccount: 'Crear cuenta',
usernamePlaceholder: 'Introduzca usuario',
codePlaceholder: 'Introduzca código de verificación',
confirmPasswordPlaceholder: 'Confirme la contraseña',
backToLogin: 'Volver al inicio de sesión',
send: 'Enviar',
emailValidPlaceholder: 'Introduzca un correo válido',
confirmPwdValidMsg: 'Las contraseñas no coinciden',
editPassword: 'Cambiar contraseña',
recharge: 'Recargar',
myAccount: 'Mi cuenta',
moneyInvite: 'Invitación con recompensa',
rechargeRecord: 'Historial de recargas',
resumeRecord: 'Historial de consumo',
inviteRecord: 'Historial de invitaciones',
username: 'Usuario',
userId: 'ID de usuario',
accountAmount: 'Saldo',
editUserInfo: 'Editar datos',
contact: 'Contacto',
backToUser: 'Volver',
moneyTips: 'Al invitar a un amigo a registrarse, obtiene {rate} de su recarga cada vez.',
inviteCode: 'Código de invitación',
inviteLink: 'Enlace de invitación',
saveImage: 'Guardar imagen',
totalAmount: 'Total recargado',
amount: 'Monto',
sendAmount: 'Monto de regalo',
rechargeType: 'Método de recarga',
rechargeTime: 'Fecha de recarga',
emptyText: 'Sin datos',
product: 'Obra',
resumeAmount: 'Monto consumido',
productType: 'Tipo de obra',
productTime: 'Fecha de creación',
totalReward: 'Saldo de recompensas',
rewardAmount: 'Monto de recompensa',
rewardTime: 'Fecha de recompensa',
reSend: 'Reenviar',
registerSuccessfully: 'Registro exitoso',
loginSuccessfully: 'Sesión iniciada',
passwordResetSuccessfully: 'Contraseña restablecida',
rechargeSuccessfully: 'Recarga exitosa',
avatar: 'Avatar',
input: 'Introduzca',
save: 'Guardar',
editEmail: 'Cambiar correo',
editEmailSuccessfully: 'Correo actualizado',
updateAvatarSuccessfully: 'Avatar actualizado',
balenceLow: 'Saldo insuficiente, recargue',
confirm: 'Aceptar',
createFailed: 'Error al generar, saldo devuelto',
notice: 'Aviso',
oldPasswordPlaceholder: 'Introduzca la contraseña anterior',
newpasswordPlaceholder: 'Introduzca la nueva contraseña',
switchPageTip: 'El video se está generando; salga y revise el historial de consumo.',
loginless: 'Sesión expirada; inicie sesión de nuevo.',
createVideo: 'Generar video (consume {price} de saldo)',
ok: 'Aceptar',
rechartNotice: 'Instrucciones de recarga',
rechargeNotice1: '1. La recarga se acredita al instante. Si no cambia, actualice la página.',
rechargeNotice2: '2. Los paquetes por tiempo limitado son promociones; puede recargar varias veces.',
rechargeNotice3: '3. El saldo recargado no tiene límite de tiempo.',
rechargeNotice4: 'Revise el paquete antes de comprar; no hay devoluciones.',
rechargeLeft: 'Saldo',
dollor: 'USD',
isDevelop: 'En desarrollo.',
copySuccessfully: 'Copiado',
copyLink: 'Copiar enlace',
goPay: 'Ir a pagar',
filePreview: 'Vista previa',
doSame: 'Hacer similar',
saveQRCode: 'Guardar código QR',
invitationCodePlaceholder: 'Introduzca código de invitación',
testRecharge: 'Recarga de prueba',
orderNo: 'Número de pedido',
orderNoP: 'El número de pedido es obligatorio',
emailNotExists: 'El correo no está registrado',
gearNotExists: 'El plan de recarga no existe; elija otro.',
myProduct: 'Mis obras',
permissionError: 'Su recarga acumulada es {total}, no cumple para previsualizar.',
createTagFailed: 'Error al generar; actualice e intente de nuevo.',
loadingText: 'Cargando...',
hasMore: 'Deslizar para más',
noMore: 'No hay más',
giftAmount: 'Monto acreditado',
cardNo: 'Número de tarjeta',
cardName: 'Nombre en tarjeta',
cardNoRequired: 'Introduzca número de tarjeta',
cardNameRequired: 'Introduzca nombre en tarjeta',
rechargeFailed: 'Recarga fallida',
vmCardInfo: 'Datos de tarjeta',
cardNumber: 'Número de tarjeta',
cardNumberPlaceholder: 'Introduzca número de tarjeta',
cardNumberRequired: 'Introduzca número de tarjeta',
cardNumberInvalid: 'El número debe tener 13-19 dígitos',
cvc: 'CVC',
cvcPlaceholder: 'Introduzca CVC',
cvcRequired: 'Introduzca CVC',
cvcInvalid: 'CVC debe ser 3 dígitos',
expYear: 'Año de vencimiento',
expYearPlaceholder: 'Introduzca año (ej.: 2027)',
expYearRequired: 'Introduzca año de vencimiento',
expYearInvalid: 'El año debe ser 4 dígitos',
expMonth: 'Mes de vencimiento',
expMonthPlaceholder: 'Seleccione mes',
expMonthRequired: 'Seleccione mes',
email: 'Correo',
emailPlaceholder: 'Introduzca correo',
emailRequired: 'Introduzca correo',
emailInvalid: 'Formato de correo no válido',
emailMaxLength: 'Máx. 30 caracteres',
firstName: 'Nombre',
firstNamePlaceholder: 'Introduzca nombre',
firstNameRequired: 'Introduzca nombre',
firstNameMaxLength: 'Máx. 30 caracteres',
lastName: 'Apellido',
lastNamePlaceholder: 'Introduzca apellido',
lastNameRequired: 'Introduzca apellido',
lastNameMaxLength: 'Máx. 30 caracteres',
country: 'País',
countryPlaceholder: 'Seleccione país',
countryRequired: 'Seleccione país',
submit: 'Enviar',
cancel: 'Cancelar'
}

View File

@ -1,7 +0,0 @@
import route from './route'
import common from './common.js'
export default {
route,
common
}

View File

@ -1,12 +0,0 @@
export default {
index: 'Inicio',
imageToImage: 'Desvestir en un clic',
imageToImage2: 'Imagen a Imagen 2',
changeFace: 'Cambiar rostro',
changeFaceVideo: 'Cambiar rostro en video',
fastImage: 'Generar imagen',
fastVideo: 'Generar video',
recharge: 'Recarga rápida',
help: 'Centro de ayuda',
moneyInvite: 'Invitación con recompensa'
}

View File

@ -1,179 +0,0 @@
export default {
image1: 'Déshabiller en un clic',
image2: 'Image vers Image 2',
uploadImage: 'Télécharger une image',
uploadImageTip: 'PNG/JPG, max. 10 Mo',
uploadPlaceholder: 'Cliquez pour télécharger',
selectImageSource: 'Choisir la source de l\'image',
selectTemplate: 'Choisir un modèle',
reselectTemplate: 'Resélectionner un modèle',
noTemplates: 'Aucun modèle',
tag1: 'Type d\'étiquette 1',
tag2: 'Type d\'étiquette 2',
tag3: 'Type d\'étiquette 3',
generateImage: 'Générer maintenant (coûte {score} de solde)',
generateImageNow: 'Générer maintenant',
generateTip: 'Après envoi, voir dans « Mes œuvres »',
generateVideo: 'Générer une vidéo',
imageFace: 'Changer le visage sur image',
videoFace: 'Changer le visage en vidéo',
uploadImageFace: 'Cliquez pour télécharger une photo de visage',
uploadTemplate: 'Cliquez pour télécharger un modèle personnalisé',
textPlaceholder: 'Décrivez l\'image que vous voulez générer',
uploadImageError: 'Veuillez télécharger une image',
replaceImage: 'Remplacer l\'image',
uploadFaceImageError: 'Veuillez télécharger une photo de visage',
uploadTemplateError: 'Veuillez télécharger un modèle personnalisé',
textError: 'Veuillez saisir le texte d\'invite',
textVideoPlaceholder: 'Décrivez la vidéo que vous voulez générer',
uploadFirstPlaceholder: 'Cliquez pour télécharger la première image',
uploadLastPlaceholder: 'Cliquez pour télécharger la dernière image',
uploadFirstImageError: 'Veuillez télécharger l\'image du premier plan',
uploadWaitImageError: 'Veuillez attendre la fin du téléchargement',
saveVideo: 'Télécharger la vidéo',
videoLoadingText: 'Génération de la vidéo...',
viewVideo: 'Voir la vidéo',
changeFacePrompt: 'Extraire le visage de la 2e image et remplacer celui de la 1re',
rechartTip1: 'Lors d\'une recharge par portefeuille, vérifiez le réseau du portefeuille.',
rechartTip2: 'La recharge peut être retardée ; attendez 35 min avant dactualiser.',
walletAddr: 'Adresse du portefeuille : ',
fbTitle: 'Attention ! Ce site est réservé aux adultes.',
fbContent: 'En entrant je confirme avoir 18 ans ou plus',
fbCancel: 'Moins de 18 ans',
fbOK: 'J\'ai 18 ans ou plus',
sorry: 'Désolé !',
useLess: 'Vous ne pouvez pas utiliser ce site...',
loginAccount: 'Connexion',
logout: 'Déconnexion',
userEmailPlaceholder: 'Saisissez l\'email ou lidentifiant',
passwordPlaceholder: 'Saisissez le mot de passe',
forgetPassword: 'Mot de passe oublié ?',
register: 'S\'inscrire',
login: 'Connexion',
forgotPassword: 'Mot de passe oublié',
registerAccount: 'Créer un compte',
usernamePlaceholder: 'Saisissez lidentifiant',
codePlaceholder: 'Saisissez le code de vérification',
confirmPasswordPlaceholder: 'Confirmez le mot de passe',
backToLogin: 'Retour à la connexion',
send: 'Envoyer',
emailValidPlaceholder: 'Saisissez un email valide',
confirmPwdValidMsg: 'Les mots de passe ne correspondent pas',
editPassword: 'Modifier le mot de passe',
recharge: 'Recharger',
myAccount: 'Mon compte',
moneyInvite: 'Invitation avec récompense',
rechargeRecord: 'Historique des recharges',
resumeRecord: 'Historique des consommations',
inviteRecord: 'Historique des invitations',
username: 'Identifiant',
userId: 'ID utilisateur',
accountAmount: 'Solde',
editUserInfo: 'Modifier les informations',
contact: 'Nous contacter',
backToUser: 'Retour',
moneyTips: 'Quand vous parrainez un ami, vous gagnez {rate} de chaque recharge quil fait.',
inviteCode: 'Code de parrainage',
inviteLink: 'Lien d\'invitation',
saveImage: 'Enregistrer l\'image',
totalAmount: 'Total rechargé',
amount: 'Montant',
sendAmount: 'Montant offert',
rechargeType: 'Méthode de recharge',
rechargeTime: 'Date de recharge',
emptyText: 'Aucune donnée',
product: 'Œuvre',
resumeAmount: 'Montant consommé',
productType: 'Type d\'œuvre',
productTime: 'Date de création',
totalReward: 'Solde de récompenses',
rewardAmount: 'Montant de la récompense',
rewardTime: 'Date de la récompense',
reSend: 'Renvoyer',
registerSuccessfully: 'Inscription réussie',
loginSuccessfully: 'Connexion réussie',
passwordResetSuccessfully: 'Mot de passe réinitialisé',
rechargeSuccessfully: 'Recharge réussie',
avatar: 'Avatar',
input: 'Saisir',
save: 'Enregistrer',
editEmail: 'Modifier l\'email',
editEmailSuccessfully: 'Email mis à jour',
updateAvatarSuccessfully: 'Avatar mis à jour',
balenceLow: 'Solde insuffisant, veuillez recharger',
confirm: 'Confirmer',
createFailed: 'Échec de génération, solde remboursé',
notice: 'Avis',
oldPasswordPlaceholder: 'Saisissez lancien mot de passe',
newpasswordPlaceholder: 'Saisissez le nouveau mot de passe',
switchPageTip: 'Une vidéo est en cours de génération ; consultez lhistorique des consommations.',
loginless: 'Session expirée ! Veuillez vous reconnecter.',
createVideo: 'Générer une vidéo (coûte {price} de solde)',
ok: 'Confirmer',
rechartNotice: 'Instructions de recharge',
rechargeNotice1: '1. La recharge est créditée tout de suite. Si rien ne change, actualisez la page.',
rechargeNotice2: '2. Les offres limitées sont des promos ; vous pouvez recharger plusieurs fois.',
rechargeNotice3: '3. Le solde rechargé na pas de limite de durée.',
rechargeNotice4: 'Vérifiez loffre avant dacheter ; pas de remboursement.',
rechargeLeft: 'Solde',
dollor: 'USD',
isDevelop: 'En cours de développement.',
copySuccessfully: 'Copié',
copyLink: 'Copier le lien',
goPay: 'Aller au paiement',
filePreview: 'Aperçu',
doSame: 'Créer un équivalent',
saveQRCode: 'Enregistrer le QR code',
invitationCodePlaceholder: 'Saisissez le code d\'invitation',
testRecharge: 'Recharge test',
orderNo: 'Numéro de commande',
orderNoP: 'Le numéro de commande est obligatoire',
emailNotExists: 'Cet email nest pas inscrit',
gearNotExists: 'Loffre choisie nexiste pas ; choisissez une autre.',
myProduct: 'Mes œuvres',
permissionError: 'Votre total de recharges est {total}, insuffisant pour la prévisualisation.',
createTagFailed: 'Échec de génération ; actualisez et réessayez.',
loadingText: 'Chargement...',
hasMore: 'Tirez pour charger plus',
noMore: 'Plus rien',
giftAmount: 'Montant crédité',
cardNo: 'Numéro de carte',
cardName: 'Nom sur la carte',
cardNoRequired: 'Saisissez le numéro de carte',
cardNameRequired: 'Saisissez le nom sur la carte',
rechargeFailed: 'Recharge échouée',
vmCardInfo: 'Informations de carte',
cardNumber: 'Numéro de carte',
cardNumberPlaceholder: 'Saisissez le numéro de carte',
cardNumberRequired: 'Saisissez le numéro de carte',
cardNumberInvalid: 'Le numéro doit contenir 13 à 19 chiffres',
cvc: 'CVC',
cvcPlaceholder: 'Saisissez le CVC',
cvcRequired: 'Saisissez le CVC',
cvcInvalid: 'Le CVC doit faire 3 chiffres',
expYear: 'Année d\'expiration',
expYearPlaceholder: 'Saisissez lannée (ex. 2027)',
expYearRequired: 'Saisissez lannée d\'expiration',
expYearInvalid: 'Lannée doit faire 4 chiffres',
expMonth: 'Mois d\'expiration',
expMonthPlaceholder: 'Choisissez le mois',
expMonthRequired: 'Choisissez le mois',
email: 'Email',
emailPlaceholder: 'Saisissez lemail',
emailRequired: 'Saisissez lemail',
emailInvalid: 'Format demail invalide',
emailMaxLength: 'Lemail ne doit pas dépasser 30 caractères',
firstName: 'Prénom',
firstNamePlaceholder: 'Saisissez le prénom',
firstNameRequired: 'Saisissez le prénom',
firstNameMaxLength: 'Le prénom ne doit pas dépasser 30 caractères',
lastName: 'Nom de famille',
lastNamePlaceholder: 'Saisissez le nom de famille',
lastNameRequired: 'Saisissez le nom de famille',
lastNameMaxLength: 'Le nom ne doit pas dépasser 30 caractères',
country: 'Pays',
countryPlaceholder: 'Choisissez le pays',
countryRequired: 'Choisissez le pays',
submit: 'Envoyer',
cancel: 'Annuler'
}

View File

@ -1,7 +0,0 @@
import route from './route'
import common from './common.js'
export default {
route,
common
}

View File

@ -1,12 +0,0 @@
export default {
index: 'Accueil',
imageToImage: 'Déshabiller en un clic',
imageToImage2: 'Image vers Image 2',
changeFace: 'Changer le visage',
changeFaceVideo: 'Changer le visage en vidéo',
fastImage: 'Générer une image',
fastVideo: 'Générer une vidéo',
recharge: 'Recharge rapide',
help: 'Centre d\'aide',
moneyInvite: 'Invitation avec récompense'
}

View File

@ -1,179 +0,0 @@
export default {
image1: 'एक क्लिक में कपड़े उतारें',
image2: 'इमेज टू इमेज 2',
uploadImage: 'छवि अपलोड करें',
uploadImageTip: 'PNG/JPG, अधिकतम 10MB',
uploadPlaceholder: 'अपलोड करने के लिए क्लिक करें',
selectImageSource: 'छवि स्रोत चुनें',
selectTemplate: 'टेम्पलेट चुनें',
reselectTemplate: 'टेम्पलेट दोबारा चुनें',
noTemplates: 'कोई टेम्पलेट नहीं',
tag1: 'टैग प्रकार 1',
tag2: 'टैग प्रकार 2',
tag3: 'टैग प्रकार 3',
generateImage: 'अभी जनरेट करें ({score} बैलेंस खर्च)',
generateImageNow: 'अभी जनरेट करें',
generateTip: 'सबमिट के बाद "मेरी रचनाएं" में देखें',
generateVideo: 'वीडियो जनरेट करें',
imageFace: 'छवि में चेहरा बदलें',
videoFace: 'वीडियो में चेहरा बदलें',
uploadImageFace: 'चेहरे की छवि अपलोड करने के लिए क्लिक करें',
uploadTemplate: 'कस्टम टेम्पलेट अपलोड करने के लिए क्लिक करें',
textPlaceholder: 'वह छवि बताएं जो आप बनाना चाहते हैं',
uploadImageError: 'कृपया एक छवि अपलोड करें',
replaceImage: 'छवि बदलें',
uploadFaceImageError: 'कृपया चेहरे की छवि अपलोड करें',
uploadTemplateError: 'कृपया कस्टम टेम्पलेट अपलोड करें',
textError: 'कृपया प्रॉम्प्ट दर्ज करें',
textVideoPlaceholder: 'वह वीडियो बताएं जो आप बनाना चाहते हैं',
uploadFirstPlaceholder: 'पहला फ्रेम अपलोड करने के लिए क्लिक करें',
uploadLastPlaceholder: 'आखिरी फ्रेम अपलोड करने के लिए क्लिक करें',
uploadFirstImageError: 'कृपया पहले फ्रेम की छवि अपलोड करें',
uploadWaitImageError: 'अपलोड पूरा होने तक प्रतीक्षा करें',
saveVideo: 'वीडियो डाउनलोड करें',
videoLoadingText: 'वीडियो बन रहा है...',
viewVideo: 'वीडियो देखें',
changeFacePrompt: 'दूसरी छवि का चेहरा लेकर पहली छवि के चेहरे से बदलें',
rechartTip1: 'वॉलेट ट्रांसफर से रिचार्ज करते समय वॉलेट का नेटवर्क जांचें।',
rechartTip2: 'रिचार्ज में देरी हो सकती है; 3-5 मिनट बाद रिफ्रेश करें।',
walletAddr: 'वॉलेट पता:',
fbTitle: 'चेतावनी! यह साइट केवल वयस्कों के लिए है।',
fbContent: 'प्रवेश करके मैं पुष्टि करता/करती हूं कि मैं 18 वर्ष या उससे अधिक का हूं',
fbCancel: '18 से कम',
fbOK: 'मैं 18+ हूं',
sorry: 'क्षमा करें!',
useLess: 'आप इस साइट का उपयोग नहीं कर सकते...',
loginAccount: 'लॉगिन',
logout: 'लॉगआउट',
userEmailPlaceholder: 'ईमेल या यूजरनेम दर्ज करें',
passwordPlaceholder: 'पासवर्ड दर्ज करें',
forgetPassword: 'पासवर्ड भूल गए?',
register: 'रजिस्टर',
login: 'लॉगिन',
forgotPassword: 'पासवर्ड भूल गए',
registerAccount: 'अकाउंट बनाएं',
usernamePlaceholder: 'यूजरनेम दर्ज करें',
codePlaceholder: 'वेरिफिकेशन कोड दर्ज करें',
confirmPasswordPlaceholder: 'पासवर्ड की पुष्टि करें',
backToLogin: 'लॉगिन पर वापस',
send: 'भेजें',
emailValidPlaceholder: 'वैध ईमेल दर्ज करें',
confirmPwdValidMsg: 'पासवर्ड मेल नहीं खाते',
editPassword: 'पासवर्ड बदलें',
recharge: 'रिचार्ज',
myAccount: 'मेरा अकाउंट',
moneyInvite: 'इनाम निमंत्रण',
rechargeRecord: 'रिचार्ज रिकॉर्ड',
resumeRecord: 'खर्च रिकॉर्ड',
inviteRecord: 'निमंत्रण रिकॉर्ड',
username: 'यूजरनेम',
userId: 'यूजर आईडी',
accountAmount: 'बैलेंस',
editUserInfo: 'जानकारी संपादित करें',
contact: 'संपर्क करें',
backToUser: 'वापस',
moneyTips: 'जब आप दोस्त को रजिस्टर करवाते हैं, उसके रिचार्ज का {rate} हर बार मिलता है।',
inviteCode: 'इनवाइट कोड',
inviteLink: 'इनवाइट लिंक',
saveImage: 'छवि सहेजें',
totalAmount: 'कुल रिचार्ज',
amount: 'राशि',
sendAmount: 'उपहार राशि',
rechargeType: 'रिचार्ज तरीका',
rechargeTime: 'रिचार्ज समय',
emptyText: 'कोई डेटा नहीं',
product: 'रचना',
resumeAmount: 'खर्च राशि',
productType: 'रचना प्रकार',
productTime: 'बनाने का समय',
totalReward: 'कुल इनाम बैलेंस',
rewardAmount: 'इनाम राशि',
rewardTime: 'इनाम समय',
reSend: 'दोबारा भेजें',
registerSuccessfully: 'रजिस्ट्रेशन सफल',
loginSuccessfully: 'लॉगिन सफल',
passwordResetSuccessfully: 'पासवर्ड रीसेट सफल',
rechargeSuccessfully: 'रिचार्ज सफल',
avatar: 'अवतार',
input: 'दर्ज करें',
save: 'सहेजें',
editEmail: 'ईमेल बदलें',
editEmailSuccessfully: 'ईमेल अपडेट हो गया',
updateAvatarSuccessfully: 'अवतार अपडेट हो गया',
balenceLow: 'बैलेंस कम है, रिचार्ज करें',
confirm: 'पुष्टि करें',
createFailed: 'जनरेशन विफल, बैलेंस वापस',
notice: 'सूचना',
oldPasswordPlaceholder: 'पुराना पासवर्ड दर्ज करें',
newpasswordPlaceholder: 'नया पासवर्ड दर्ज करें',
switchPageTip: 'वीडियो बन रहा है; निकलकर खर्च रिकॉर्ड देखें।',
loginless: 'लॉगिन समाप्त! दोबारा लॉगिन करें।',
createVideo: 'वीडियो जनरेट करें ({price} बैलेंस खर्च)',
ok: 'पुष्टि करें',
rechartNotice: 'रिचार्ज निर्देश',
rechargeNotice1: '1. रिचार्ज तुरंत जमा होता है। बदलाव न दिखे तो रिफ्रेश करें।',
rechargeNotice2: '2. लिमिटेड पैक प्रचार हैं; आप कई बार रिचार्ज कर सकते हैं।',
rechargeNotice3: '3. रिचार्ज की गई राशि की कोई समय सीमा नहीं।',
rechargeNotice4: 'खरीदने से पहले पैक देखें; वापसी नहीं होती।',
rechargeLeft: 'बैलेंस',
dollor: 'USD',
isDevelop: 'विकास में है।',
copySuccessfully: 'कॉपी हुआ',
copyLink: 'लिंक कॉपी करें',
goPay: 'भुगतान पर जाएं',
filePreview: 'पूर्वावलोकन',
doSame: 'इसी तरह बनाएं',
saveQRCode: 'QR कोड सहेजें',
invitationCodePlaceholder: 'इनवाइट कोड दर्ज करें',
testRecharge: 'टेस्ट रिचार्ज',
orderNo: 'ऑर्डर नंबर',
orderNoP: 'ऑर्डर नंबर खाली नहीं हो सकता',
emailNotExists: 'यह ईमेल रजिस्टर नहीं है',
gearNotExists: 'चुना गया रिचार्ज प्लान मौजूद नहीं; दूसरा चुनें।',
myProduct: 'मेरी रचनाएं',
permissionError: 'आपका कुल रिचार्ज {total} है, प्रीव्यू के लिए पर्याप्त नहीं।',
createTagFailed: 'जनरेशन विफल; रिफ्रेश करके दोबारा कोशिश करें।',
loadingText: 'लोड हो रहा है...',
hasMore: 'ऊपर खींचें और अधिक लोड करें',
noMore: 'और नहीं',
giftAmount: 'जमा राशि',
cardNo: 'कार्ड नंबर',
cardName: 'कार्डधारक का नाम',
cardNoRequired: 'कार्ड नंबर दर्ज करें',
cardNameRequired: 'कार्डधारक का नाम दर्ज करें',
rechargeFailed: 'रिचार्ज विफल',
vmCardInfo: 'कार्ड जानकारी',
cardNumber: 'कार्ड नंबर',
cardNumberPlaceholder: 'कार्ड नंबर दर्ज करें',
cardNumberRequired: 'कार्ड नंबर दर्ज करें',
cardNumberInvalid: 'कार्ड नंबर 13-19 अंकों का होना चाहिए',
cvc: 'CVC',
cvcPlaceholder: 'CVC दर्ज करें',
cvcRequired: 'CVC दर्ज करें',
cvcInvalid: 'CVC 3 अंकों का होना चाहिए',
expYear: 'समाप्ति वर्ष',
expYearPlaceholder: 'वर्ष दर्ज करें (जैसे 2027)',
expYearRequired: 'समाप्ति वर्ष दर्ज करें',
expYearInvalid: 'वर्ष 4 अंकों का होना चाहिए',
expMonth: 'समाप्ति माह',
expMonthPlaceholder: 'माह चुनें',
expMonthRequired: 'माह चुनें',
email: 'ईमेल',
emailPlaceholder: 'ईमेल दर्ज करें',
emailRequired: 'ईमेल दर्ज करें',
emailInvalid: 'ईमेल फॉर्मेट गलत',
emailMaxLength: 'ईमेल 30 अक्षर से अधिक नहीं',
firstName: 'पहला नाम',
firstNamePlaceholder: 'पहला नाम दर्ज करें',
firstNameRequired: 'पहला नाम दर्ज करें',
firstNameMaxLength: 'पहला नाम 30 अक्षर से अधिक नहीं',
lastName: 'उपनाम',
lastNamePlaceholder: 'उपनाम दर्ज करें',
lastNameRequired: 'उपनाम दर्ज करें',
lastNameMaxLength: 'उपनाम 30 अक्षर से अधिक नहीं',
country: 'देश',
countryPlaceholder: 'देश चुनें',
countryRequired: 'देश चुनें',
submit: 'भेजें',
cancel: 'रद्द करें'
}

View File

@ -1,7 +0,0 @@
import route from './route'
import common from './common.js'
export default {
route,
common
}

View File

@ -1,12 +0,0 @@
export default {
index: 'होम',
imageToImage: 'एक क्लिक में कपड़े उतारें',
imageToImage2: 'इमेज टू इमेज 2',
changeFace: 'चेहरा बदलें',
changeFaceVideo: 'वीडियो में चेहरा बदलें',
fastImage: 'छवि जनरेट करें',
fastVideo: 'वीडियो जनरेट करें',
recharge: 'त्वरित रिचार्ज',
help: 'सहायता केंद्र',
moneyInvite: 'इनाम निमंत्रण'
}

View File

@ -2,39 +2,15 @@ import { createI18n } from 'vue-i18n'
import Cookies from 'js-cookie'
import zh_HK from '@/lang/zh_HK/index.js'
import en_US from '@/lang/en_US/index.js'
import es_ES from '@/lang/es_ES/index.js'
import pt_BR from '@/lang/pt_BR/index.js'
import hi_IN from '@/lang/hi_IN/index.js'
import ru_RU from '@/lang/ru_RU/index.js'
import ar_SA from '@/lang/ar_SA/index.js'
import fr_FR from '@/lang/fr_FR/index.js'
let locale = Cookies.get('language') || 'en_US'
/** 各语言在界面上的显示名称 */
export const LOCALE_NAMES = {
zh_HK: '繁体中文',
en_US: 'English',
es_ES: 'Español',
pt_BR: 'Português',
hi_IN: 'हिन्दी',
ru_RU: 'Русский',
ar_SA: 'العربية',
fr_FR: 'Français'
}
const i18n = createI18n({
globalInjection: true,
locale,
messages: {
zh_HK,
en_US,
es_ES,
pt_BR,
hi_IN,
ru_RU,
ar_SA,
fr_FR
en_US
}
})

View File

@ -1,179 +0,0 @@
export default {
image1: 'Despir em um clique',
image2: 'Imagem para Imagem 2',
uploadImage: 'Enviar imagem',
uploadImageTip: 'PNG/JPG, máx. 10MB',
uploadPlaceholder: 'Clique para enviar imagem',
selectImageSource: 'Selecionar origem da imagem',
selectTemplate: 'Selecionar modelo',
reselectTemplate: 'Selecionar outro modelo',
noTemplates: 'Sem modelos',
tag1: 'Tipo de tag 1',
tag2: 'Tipo de tag 2',
tag3: 'Tipo de tag 3',
generateImage: 'Gerar agora (usa {score} de saldo)',
generateImageNow: 'Gerar agora',
generateTip: 'Após enviar, veja em "Minhas obras"',
generateVideo: 'Gerar vídeo',
imageFace: 'Trocar rosto em imagem',
videoFace: 'Trocar rosto em vídeo',
uploadImageFace: 'Clique para enviar imagem do rosto',
uploadTemplate: 'Clique para enviar modelo personalizado',
textPlaceholder: 'Descreva a imagem que deseja gerar',
uploadImageError: 'Envie uma imagem',
replaceImage: 'Substituir imagem',
uploadFaceImageError: 'Envie uma imagem do rosto',
uploadTemplateError: 'Envie um modelo personalizado',
textError: 'Digite o texto de indicação',
textVideoPlaceholder: 'Descreva o vídeo que deseja gerar',
uploadFirstPlaceholder: 'Clique para enviar primeiro frame',
uploadLastPlaceholder: 'Clique para enviar último frame',
uploadFirstImageError: 'Envie a imagem do primeiro frame',
uploadWaitImageError: 'Aguarde o envio terminar',
saveVideo: 'Baixar vídeo',
videoLoadingText: 'Gerando vídeo...',
viewVideo: 'Ver vídeo',
changeFacePrompt: 'Extrair o rosto da segunda imagem e substituir na primeira',
rechartTip1: 'Ao recarregar por carteira, confirme a rede do carteira.',
rechartTip2: 'A recarga pode atrasar; aguarde 3-5 minutos antes de atualizar.',
walletAddr: 'Endereço da carteira',
fbTitle: 'Aviso: este site é apenas para maiores de 18 anos.',
fbContent: 'Ao entrar confirmo que tenho 18 anos ou mais',
fbCancel: 'Menor de 18',
fbOK: 'Tenho 18 ou mais',
sorry: 'Desculpe!',
useLess: 'Você não pode usar este site...',
loginAccount: 'Entrar',
logout: 'Sair',
userEmailPlaceholder: 'Digite e-mail ou usuário',
passwordPlaceholder: 'Digite a senha',
forgetPassword: 'Esqueceu a senha?',
register: 'Cadastrar',
login: 'Entrar',
forgotPassword: 'Esqueci a senha',
registerAccount: 'Criar conta',
usernamePlaceholder: 'Digite o usuário',
codePlaceholder: 'Digite o código de verificação',
confirmPasswordPlaceholder: 'Confirme a senha',
backToLogin: 'Voltar ao login',
send: 'Enviar',
emailValidPlaceholder: 'Digite um e-mail válido',
confirmPwdValidMsg: 'As senhas não coincidem',
editPassword: 'Alterar senha',
recharge: 'Recarregar',
myAccount: 'Minha conta',
moneyInvite: 'Convite com recompensa',
rechargeRecord: 'Histórico de recargas',
resumeRecord: 'Histórico de consumo',
inviteRecord: 'Histórico de convites',
username: 'Usuário',
userId: 'ID do usuário',
accountAmount: 'Saldo',
editUserInfo: 'Editar dados',
contact: 'Contato',
backToUser: 'Voltar',
moneyTips: 'Ao convidar um amigo, você ganha {rate} do valor que ele recarregar.',
inviteCode: 'Código de convite',
inviteLink: 'Link de convite',
saveImage: 'Salvar imagem',
totalAmount: 'Total recarregado',
amount: 'Valor',
sendAmount: 'Valor de brinde',
rechargeType: 'Forma de recarga',
rechargeTime: 'Data da recarga',
emptyText: 'Sem dados',
product: 'Obra',
resumeAmount: 'Valor consumido',
productType: 'Tipo de obra',
productTime: 'Data de criação',
totalReward: 'Saldo de recompensas',
rewardAmount: 'Valor da recompensa',
rewardTime: 'Data da recompensa',
reSend: 'Reenviar',
registerSuccessfully: 'Cadastro concluído',
loginSuccessfully: 'Login realizado',
passwordResetSuccessfully: 'Senha redefinida',
rechargeSuccessfully: 'Recarga concluída',
avatar: 'Avatar',
input: 'Digite',
save: 'Salvar',
editEmail: 'Alterar e-mail',
editEmailSuccessfully: 'E-mail atualizado',
updateAvatarSuccessfully: 'Avatar atualizado',
balenceLow: 'Saldo insuficiente, recarregue',
confirm: 'Confirmar',
createFailed: 'Falha na geração, saldo devolvido',
notice: 'Aviso',
oldPasswordPlaceholder: 'Digite a senha antiga',
newpasswordPlaceholder: 'Digite a nova senha',
switchPageTip: 'O vídeo está sendo gerado; saia e veja o histórico de consumo.',
loginless: 'Sessão expirada; faça login novamente.',
createVideo: 'Gerar vídeo (usa {price} de saldo)',
ok: 'Confirmar',
rechartNotice: 'Instruções de recarga',
rechargeNotice1: '1. A recarga é creditada na hora. Se não mudar, atualize a página.',
rechargeNotice2: '2. Pacotes por tempo limitado são promoções; você pode recarregar várias vezes.',
rechargeNotice3: '3. O saldo recarregado não tem prazo.',
rechargeNotice4: 'Confira o pacote antes de comprar; não há reembolso.',
rechargeLeft: 'Saldo',
dollor: 'USD',
isDevelop: 'Em desenvolvimento.',
copySuccessfully: 'Copiado',
copyLink: 'Copiar link',
goPay: 'Ir para pagamento',
filePreview: 'Visualizar',
doSame: 'Fazer igual',
saveQRCode: 'Salvar código QR',
invitationCodePlaceholder: 'Digite o código de convite',
testRecharge: 'Recarga de teste',
orderNo: 'Número do pedido',
orderNoP: 'O número do pedido é obrigatório',
emailNotExists: 'O e-mail não está cadastrado',
gearNotExists: 'O plano de recarga não existe; escolha outro.',
myProduct: 'Minhas obras',
permissionError: 'Sua recarga acumulada é {total}, não atende para visualizar.',
createTagFailed: 'Falha na geração; atualize e tente de novo.',
loadingText: 'Carregando...',
hasMore: 'Deslize para mais',
noMore: 'Não há mais',
giftAmount: 'Valor creditado',
cardNo: 'Número do cartão',
cardName: 'Nome no cartão',
cardNoRequired: 'Digite o número do cartão',
cardNameRequired: 'Digite o nome no cartão',
rechargeFailed: 'Recarga falhou',
vmCardInfo: 'Dados do cartão',
cardNumber: 'Número do cartão',
cardNumberPlaceholder: 'Digite o número do cartão',
cardNumberRequired: 'Digite o número do cartão',
cardNumberInvalid: 'O número deve ter 13-19 dígitos',
cvc: 'CVC',
cvcPlaceholder: 'Digite o CVC',
cvcRequired: 'Digite o CVC',
cvcInvalid: 'CVC deve ter 3 dígitos',
expYear: 'Ano de validade',
expYearPlaceholder: 'Digite o ano (ex.: 2027)',
expYearRequired: 'Digite o ano de validade',
expYearInvalid: 'O ano deve ter 4 dígitos',
expMonth: 'Mês de validade',
expMonthPlaceholder: 'Selecione o mês',
expMonthRequired: 'Selecione o mês',
email: 'E-mail',
emailPlaceholder: 'Digite o e-mail',
emailRequired: 'Digite o e-mail',
emailInvalid: 'Formato de e-mail inválido',
emailMaxLength: 'Máx. 30 caracteres',
firstName: 'Nome',
firstNamePlaceholder: 'Digite o nome',
firstNameRequired: 'Digite o nome',
firstNameMaxLength: 'Máx. 30 caracteres',
lastName: 'Sobrenome',
lastNamePlaceholder: 'Digite o sobrenome',
lastNameRequired: 'Digite o sobrenome',
lastNameMaxLength: 'Máx. 30 caracteres',
country: 'País',
countryPlaceholder: 'Selecione o país',
countryRequired: 'Selecione o país',
submit: 'Enviar',
cancel: 'Cancelar'
}

View File

@ -1,7 +0,0 @@
import route from './route'
import common from './common.js'
export default {
route,
common
}

View File

@ -1,12 +0,0 @@
export default {
index: 'Início',
imageToImage: 'Despir em um clique',
imageToImage2: 'Imagem para Imagem 2',
changeFace: 'Trocar rosto',
changeFaceVideo: 'Trocar rosto em vídeo',
fastImage: 'Gerar imagem',
fastVideo: 'Gerar vídeo',
recharge: 'Recarga rápida',
help: 'Central de ajuda',
moneyInvite: 'Convite com recompensa'
}

View File

@ -1,179 +0,0 @@
export default {
image1: 'Раздеть в один клик',
image2: 'Изображение в изображение 2',
uploadImage: 'Загрузить изображение',
uploadImageTip: 'PNG/JPG, макс. 10 МБ',
uploadPlaceholder: 'Нажмите, чтобы загрузить',
selectImageSource: 'Выберите источник изображения',
selectTemplate: 'Выберите шаблон',
reselectTemplate: 'Выбрать шаблон заново',
noTemplates: 'Нет шаблонов',
tag1: 'Тип метки 1',
tag2: 'Тип метки 2',
tag3: 'Тип метки 3',
generateImage: 'Создать сейчас (списание {score} с баланса)',
generateImageNow: 'Создать сейчас',
generateTip: 'После отправки смотрите в «Мои работы»',
generateVideo: 'Создать видео',
imageFace: 'Заменить лицо на изображении',
videoFace: 'Заменить лицо в видео',
uploadImageFace: 'Нажмите, чтобы загрузить фото лица',
uploadTemplate: 'Нажмите, чтобы загрузить свой шаблон',
textPlaceholder: 'Опишите изображение, которое хотите получить',
uploadImageError: 'Загрузите изображение',
replaceImage: 'Заменить изображение',
uploadFaceImageError: 'Загрузите фото лица',
uploadTemplateError: 'Загрузите свой шаблон',
textError: 'Введите текст подсказки',
textVideoPlaceholder: 'Опишите видео, которое хотите получить',
uploadFirstPlaceholder: 'Нажмите, чтобы загрузить первый кадр',
uploadLastPlaceholder: 'Нажмите, чтобы загрузить последний кадр',
uploadFirstImageError: 'Загрузите изображение первого кадра',
uploadWaitImageError: 'Дождитесь окончания загрузки',
saveVideo: 'Скачать видео',
videoLoadingText: 'Создание видео...',
viewVideo: 'Смотреть видео',
changeFacePrompt: 'Взять лицо со второй картинки и заменить им лицо на первой',
rechartTip1: 'При пополнении через кошелёк проверьте сеть кошелька.',
rechartTip2: 'Зачисление может задерживаться; подождите 35 минут и обновите.',
walletAddr: 'Адрес кошелька:',
fbTitle: 'Внимание! Сайт только для взрослых!',
fbContent: 'Входя, я подтверждаю, что мне 18 лет или больше',
fbCancel: 'Мне нет 18',
fbOK: 'Мне есть 18',
sorry: 'Извините!',
useLess: 'Вам запрещено пользоваться этим сайтом...',
loginAccount: 'Вход',
logout: 'Выйти',
userEmailPlaceholder: 'Введите email или логин',
passwordPlaceholder: 'Введите пароль',
forgetPassword: 'Забыли пароль?',
register: 'Регистрация',
login: 'Войти',
forgotPassword: 'Забыл пароль',
registerAccount: 'Создать аккаунт',
usernamePlaceholder: 'Введите имя пользователя',
codePlaceholder: 'Введите код подтверждения',
confirmPasswordPlaceholder: 'Подтвердите пароль',
backToLogin: 'Вернуться к входу',
send: 'Отправить',
emailValidPlaceholder: 'Введите корректный email',
confirmPwdValidMsg: 'Пароли не совпадают',
editPassword: 'Изменить пароль',
recharge: 'Пополнить',
myAccount: 'Мой аккаунт',
moneyInvite: 'Приглашение с наградой',
rechargeRecord: 'История пополнений',
resumeRecord: 'История списаний',
inviteRecord: 'История приглашений',
username: 'Имя пользователя',
userId: 'ID пользователя',
accountAmount: 'Баланс',
editUserInfo: 'Редактировать данные',
contact: 'Связаться с нами',
backToUser: 'Назад',
moneyTips: 'При регистрации по вашей ссылке вы получаете {rate} от каждой его пополнения.',
inviteCode: 'Код приглашения',
inviteLink: 'Ссылка приглашения',
saveImage: 'Сохранить изображение',
totalAmount: 'Всего пополнено',
amount: 'Сумма',
sendAmount: 'Сумма бонуса',
rechargeType: 'Способ пополнения',
rechargeTime: 'Время пополнения',
emptyText: 'Нет данных',
product: 'Работа',
resumeAmount: 'Списано',
productType: 'Тип работы',
productTime: 'Дата создания',
totalReward: 'Баланс наград',
rewardAmount: 'Сумма награды',
rewardTime: 'Время награды',
reSend: 'Отправить снова',
registerSuccessfully: 'Регистрация выполнена',
loginSuccessfully: 'Вход выполнен',
passwordResetSuccessfully: 'Пароль сброшен',
rechargeSuccessfully: 'Пополнение выполнено',
avatar: 'Аватар',
input: 'Введите',
save: 'Сохранить',
editEmail: 'Изменить email',
editEmailSuccessfully: 'Email обновлён',
updateAvatarSuccessfully: 'Аватар обновлён',
balenceLow: 'Недостаточно средств, пополните баланс',
confirm: 'Подтвердить',
createFailed: 'Ошибка создания, средства возвращены',
notice: 'Уведомление',
oldPasswordPlaceholder: 'Введите старый пароль',
newpasswordPlaceholder: 'Введите новый пароль',
switchPageTip: 'Видео создаётся; выйдите и проверьте историю списаний.',
loginless: 'Сессия истекла. Войдите снова.',
createVideo: 'Создать видео (списание {price} с баланса)',
ok: 'Подтвердить',
rechartNotice: 'Правила пополнения',
rechargeNotice1: '1. Средства зачисляются сразу. Если не изменилось — обновите страницу.',
rechargeNotice2: '2. Акционные пакеты проводятся периодически; можно пополнять несколько раз.',
rechargeNotice3: '3. Пополненный баланс не имеет срока действия.',
rechargeNotice4: 'Перед покупкой ознакомьтесь с пакетом; возврат не предусмотрен.',
rechargeLeft: 'Баланс',
dollor: 'USD',
isDevelop: 'В разработке.',
copySuccessfully: 'Скопировано',
copyLink: 'Копировать ссылку',
goPay: 'Перейти к оплате',
filePreview: 'Предпросмотр',
doSame: 'Сделать похожее',
saveQRCode: 'Сохранить QR-код',
invitationCodePlaceholder: 'Введите код приглашения',
testRecharge: 'Тестовое пополнение',
orderNo: 'Номер заказа',
orderNoP: 'Номер заказа обязателен',
emailNotExists: 'Этот email не зарегистрирован',
gearNotExists: 'Выбранный тариф отсутствует; выберите другой.',
myProduct: 'Мои работы',
permissionError: 'Ваше суммарное пополнение {total}, недостаточно для просмотра.',
createTagFailed: 'Ошибка создания; обновите страницу и повторите.',
loadingText: 'Загрузка...',
hasMore: 'Потяните вверх для подгрузки',
noMore: 'Больше нет',
giftAmount: 'Зачисленная сумма',
cardNo: 'Номер карты',
cardName: 'Имя владельца',
cardNoRequired: 'Введите номер карты',
cardNameRequired: 'Введите имя владельца',
rechargeFailed: 'Ошибка пополнения',
vmCardInfo: 'Данные карты',
cardNumber: 'Номер карты',
cardNumberPlaceholder: 'Введите номер карты',
cardNumberRequired: 'Введите номер карты',
cardNumberInvalid: 'Номер карты должен быть из 1319 цифр',
cvc: 'CVC',
cvcPlaceholder: 'Введите CVC',
cvcRequired: 'Введите CVC',
cvcInvalid: 'CVC должен быть из 3 цифр',
expYear: 'Год срока действия',
expYearPlaceholder: 'Введите год (например 2027)',
expYearRequired: 'Введите год',
expYearInvalid: 'Год должен быть из 4 цифр',
expMonth: 'Месяц срока действия',
expMonthPlaceholder: 'Выберите месяц',
expMonthRequired: 'Выберите месяц',
email: 'Email',
emailPlaceholder: 'Введите email',
emailRequired: 'Введите email',
emailInvalid: 'Некорректный email',
emailMaxLength: 'Email не более 30 символов',
firstName: 'Имя',
firstNamePlaceholder: 'Введите имя',
firstNameRequired: 'Введите имя',
firstNameMaxLength: 'Имя не более 30 символов',
lastName: 'Фамилия',
lastNamePlaceholder: 'Введите фамилию',
lastNameRequired: 'Введите фамилию',
lastNameMaxLength: 'Фамилия не более 30 символов',
country: 'Страна',
countryPlaceholder: 'Выберите страну',
countryRequired: 'Выберите страну',
submit: 'Отправить',
cancel: 'Отмена'
}

View File

@ -1,7 +0,0 @@
import route from './route'
import common from './common.js'
export default {
route,
common
}

View File

@ -1,12 +0,0 @@
export default {
index: 'Главная',
imageToImage: 'Раздеть в один клик',
imageToImage2: 'Изображение в изображение 2',
changeFace: 'Заменить лицо',
changeFaceVideo: 'Заменить лицо в видео',
fastImage: 'Создать изображение',
fastVideo: 'Создать видео',
recharge: 'Быстрая пополнение',
help: 'Центр помощи',
moneyInvite: 'Приглашение с наградой'
}

View File

@ -21,7 +21,6 @@ export default {
uploadTemplate: '點擊上傳自定義模板',
textPlaceholder: '請描述你想生成的圖片',
uploadImageError: '請上傳圖片',
replaceImage: '替換圖片',
uploadFaceImageError: '請上傳人臉圖片',
uploadTemplateError: '請上傳自定義模板',
textError: '請輸入提示詞',
@ -145,14 +144,6 @@ export default {
cardNoRequired: '請輸入銀行卡號',
cardNameRequired: '請輸入銀行卡姓名',
rechargeFailed: '充值失敗',
// 模型选择
selectModel: '選擇模型',
seedance20: 'Seedance 2.0',
seedance20Fast: 'Seedance 2.0 Fast',
// 富文本编辑器
insertImage: '插入圖片',
mentionImage: '引用圖片',
noImageToMention: '暫無可引用的圖片',
// VM支付相關
vmCardInfo: '信用卡信息',
cardNumber: '信用卡卡號',
@ -186,9 +177,6 @@ export default {
country: '國家',
countryPlaceholder: '請選擇國家',
countryRequired: '請選擇國家',
videoGen: '視頻生成',
uploadFirstImage: '上傳首圖',
insertImage: '插入圖片',
submit: '提交',
cancel: '取消'
}

View File

@ -6,7 +6,6 @@ export default {
changeFaceVideo: '視頻換臉',
fastImage: '快捷生圖',
fastVideo: '快捷生視頻',
videoGen: '視頻生成',
recharge: '快速充值',
help: '幫助中心',
moneyInvite: '有獎邀請'

View File

@ -29,12 +29,11 @@
:title="$t('common.moneyInvite')">
<Money />
</a-tab-pane>
<!-- 隐藏快速充值入口 -->
<!-- <a-tab-pane
<a-tab-pane
key="recharge"
:title="$t('common.rechargeRecord')">
<RechargeRecord />
</a-tab-pane> -->
</a-tab-pane>
<a-tab-pane
key="resume"
:title="$t('common.myProduct')">
@ -68,6 +67,7 @@ import { mapGetters } from 'vuex'
import i18n from '@/lang/i18n'
import Money from './Money.vue'
import UserAccount from './UserAccount.vue'
import RechargeRecord from './RechargeRecord.vue'
import ResumeRecord from './ResumeRecord.vue'
import RewardRecord from './RewardRecord.vue'
@ -93,6 +93,7 @@ export default {
Register,
Money,
UserAccount,
RechargeRecord,
ResumeRecord,
RewardRecord
},

View File

@ -33,16 +33,12 @@
<div class="right-menu-item language">
<a-dropdown @select="handleSelect">
<mf-button type="text">
{{ localeName }}
{{ lang == 'zh_HK' ? '繁体中文' : 'English' }}
<icon-down />
</mf-button>
<template #content>
<a-doption
v-for="(name, code) in localeNames"
:key="code"
:value="code">
{{ name }}
</a-doption>
<a-doption value="zh_HK">繁体中文</a-doption>
<a-doption value="en_US">English</a-doption>
</template>
</a-dropdown>
</div>
@ -97,7 +93,7 @@ import { mapGetters, mapState } from 'vuex'
import cloneDeep from 'lodash-es/cloneDeep'
import { constantRoutes } from '@/router/index.js'
import Login from './Login.vue'
import i18n, { LOCALE_NAMES } from '@/lang/i18n'
import i18n from '@/lang/i18n'
import User from './User.vue'
export default {
@ -126,12 +122,6 @@ export default {
demoEnv() {
return import.meta.env.MODE === 'demo'
},
localeNames() {
return LOCALE_NAMES
},
localeName() {
return LOCALE_NAMES[this.lang] || 'English'
},
...mapGetters([
'theme',
'permission_routes',

View File

@ -9,11 +9,9 @@ import router from './router'
import '@/assets/styles/base.less'
// Quill 样式 - 临时注释避免 source map 文件缺失导致的构建错误
// 如果后续需要使用 Quill 富文本编辑器,可取消注释
// import '@/assets/styles/quill.bubble.css'
// import '@/assets/styles/quill.core.css'
// import '@/assets/styles/quill.snow.css'
import '@/assets/styles/quill.bubble.css'
import '@/assets/styles/quill.core.css'
import '@/assets/styles/quill.snow.css'
const app = createApp(App)
app.use(router)

View File

@ -69,47 +69,49 @@ export const constantRoutes = [{
permission: "pass",
icon: 'btn_tst'
}
}, {
path: 'image-to-image?type=2',
name: 'image-to-image2',
component: () => import('@/views/Image.vue'),
meta: {
title: 'imageToImage2',
menuItem: true,
permission: "pass",
icon: 'btn_tst'
}
}, {
path: 'change-face',
name: 'change-face',
component: () => import('@/views/ChangeFace.vue'),
meta: {
title: 'changeFace',
menuItem: true,
permission: "pass",
icon: 'btn_yjhl'
}
}, {
path: 'change-face-video',
name: 'change-face-video',
component: () => import('@/views/ChangeFace.vue'),
meta: {
title: 'changeFaceVideo',
menuItem: true,
permission: "pass",
icon: 'btn_yjhl'
}
}, {
path: 'fast-image',
name: 'fast-image',
component: () => import('@/views/FastImage.vue'),
meta: {
title: 'fastImage',
menuItem: true,
permission: "pass",
icon: 'btn_kjst'
}
}, {
},
// {
// path: 'image-to-image?type=2',
// name: 'image-to-image2',
// component: () => import('@/views/Image.vue'),
// meta: {
// title: 'imageToImage2',
// menuItem: true,
// permission: "pass",
// icon: 'btn_tst'
// }
// }, {
// path: 'change-face',
// name: 'change-face',
// component: () => import('@/views/ChangeFace.vue'),
// meta: {
// title: 'changeFace',
// menuItem: true,
// permission: "pass",
// icon: 'btn_yjhl'
// }
// }, {
// path: 'change-face-video',
// name: 'change-face-video',
// component: () => import('@/views/ChangeFace.vue'),
// meta: {
// title: 'changeFaceVideo',
// menuItem: true,
// permission: "pass",
// icon: 'btn_yjhl'
// }
// }, {
// path: 'fast-image',
// name: 'fast-image',
// component: () => import('@/views/FastImage.vue'),
// meta: {
// title: 'fastImage',
// menuItem: true,
// permission: "pass",
// icon: 'btn_kjst'
// }
// },
{
path: 'fast-video',
name: 'fast-video',
component: () => import('@/views/FastVideo.vue'),
@ -119,16 +121,6 @@ export const constantRoutes = [{
permission: "pass",
icon: 'btn_kjsp'
}
}, {
path: 'video-gen',
name: 'video-gen',
component: () => import('@/views/VideoGen.vue'),
meta: {
title: 'videoGen',
menuItem: true,
permission: "pass",
icon: 'btn_video'
}
}, {
path: 'recharge',
name: 'recharge',

View File

@ -21,7 +21,7 @@ const messages = i18n.global.messages[lang];
* @desc 创建axios实例
*/
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
// baseURL: process.env.VUE_APP_BASE_API,
withCredentials: import.meta.env.MODE === 'development',
timeout: 600 * 1000
})

View File

@ -68,28 +68,11 @@
</a-radio>
</a-radio-group>
</div>
<!-- 模型选择 -->
<div class="model-select">
<div class="model-title">{{ $t('common.selectModel') || '选择模型' }}</div>
<a-select v-model="selectedModel" style="width: 100%;">
<a-option
v-for="option in modelOptions"
:key="option.value"
:value="option.value">
{{ option.label }}
</a-option>
</a-select>
</div>
<!-- 富文本编辑器 -->
<RichTextEditor
v-model="editorContent"
:placeholder="$t('common.textVideoPlaceholder') || '请输入文本生成视频...'"
:uploaded-images="uploadedImages"
@text-change="handleTextChange"
@image-upload="handleImageUpload"
/>
<!-- <div class="text">
<a-textarea
v-model="text"
:placeholder="$t('common.textVideoPlaceholder')" />
</div> -->
<mf-button
class="submit"
type="primary"
@ -192,8 +175,6 @@
<script>
import { mapGetters } from 'vuex'
import RichTextEditor from '@/components/RichTextEditor.vue'
export default {
data() {
return {
@ -201,7 +182,6 @@ export default {
firstUrl: '',
lastUrl: '',
text: '',
editorContent: '',
interval: null,
videoUrl: null,
videoLoading: false,
@ -222,20 +202,9 @@ export default {
templateLoading: false,
selectedTemplate: null,
selectedTemplatePreview: '', // URL
maxPollAttempts: 40, // 3 * 40 = 120
//
modelOptions: [
{ label: 'Seedance 2.0', value: 'ep-20260326165811-dlkth' },
{ label: 'Seedance 2.0 Fast', value: 'ep-20260326170056-dkj9m' }
],
selectedModel: 'ep-20260326165811-dlkth',
// @
uploadedImages: []
maxPollAttempts: 40 // 3 * 40 = 120
}
},
components: {
RichTextEditor
},
beforeUnmount() {
this.destroyInterval()
},
@ -344,28 +313,16 @@ export default {
//
getTemplateName(template) {
if (!template) return ''
// chineseContent englishContent
// chineseContent
if (this.lang === 'zh_HK') {
return template.chineseContent || template.name || ''
}else{
}
// englishContent
else if (this.lang === 'en_US') {
return template.englishContent || template.name || ''
}
},
//
handleTextChange(content) {
this.editorContent = content
// text
this.text = content
},
// @
handleImageUpload(imageInfo) {
if (imageInfo && imageInfo.url) {
this.uploadedImages.push({
url: imageInfo.url,
name: imageInfo.name || 'image'
})
}
// name
return template.name || ''
},
//
handleConfirmTemplate() {
@ -525,10 +482,9 @@ export default {
this.generateLoading = true
let params = {
text: this.editorContent || this.text,
text: this.text,
firstUrl: firstImageUrl,
functionType: '21',
model: this.selectedModel //
functionType: '21'
}
if (this.lastUrl && this.lastUrl.url) {
params.lastUrl = this.lastUrl.url
@ -652,74 +608,6 @@ export default {
.upload {
margin-bottom: 20px;
.model-select {
margin-bottom: 20px;
.model-title {
color: #fff;
margin-bottom: 8px;
font-size: 14px;
}
:deep(.arco-select) {
background-color: #1a1b20;
border-color: rgba(255,255,255,0.1);
}
}
/* 富文本编辑器样式 */
.rich-editor-root {
margin-bottom: 20px;
.toolbar {
display: flex;
gap: 8px;
margin-bottom: 12px;
padding: 8px;
background-color: #1a1b20;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.1);
.tool-btn {
padding: 6px 12px;
background-color: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
color: #fff;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
&:hover {
background-color: rgba(255,255,255,0.1);
border-color: rgba(255,255,255,0.2);
}
}
}
.rich-editor {
min-height: 160px;
padding: 16px;
background-color: #1a1b20;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
color: #fff;
font-size: 14px;
line-height: 1.6;
outline: none;
resize: none;
&:focus {
border-color: rgb(var(--primary-6));
}
&[data-placeholder]:empty::before {
content: attr(data-placeholder);
color: #5c5d68;
pointer-events: none;
}
}
}
&-title {
display: flex;
align-items: center;

View File

@ -61,28 +61,13 @@
</a-radio>
</a-radio-group>
</div>
<!-- 模型选择 -->
<div class="model-select">
<div class="model-title">{{ $t('common.selectModel') || '选择模型' }}</div>
<a-select v-model="selectedModel" style="width: 100%;">
<a-option
v-for="option in modelOptions"
:key="option.value"
:value="option.value">
{{ option.label }}
</a-option>
</a-select>
</div>
<!-- 富文本编辑器 -->
<RichTextEditor
v-model="editorContent"
:placeholder="$t('common.textPlaceholder') || '请输入文本生成图片...'"
:uploaded-images="uploadedImages"
@text-change="handleTextChange"
@image-upload="handleImageUpload"
/>
<!-- <div
class="text"
v-if="current == 2">
<a-textarea
v-model="text"
:placeholder="$t('common.textPlaceholder')" />
</div> -->
<mf-button
class="submit"
type="primary"
@ -172,7 +157,6 @@
<script>
import { mapGetters } from 'vuex'
import RichTextEditor from '@/components/RichTextEditor.vue'
export default {
data() {
@ -195,20 +179,9 @@ export default {
templateList: [],
templateLoading: false,
selectedTemplate: null,
selectedTemplatePreview: '', // URL
// - Seedance 2.0
modelOptions: [
{ label: 'Seedance 2.0', value: 'ep-20260326165811-dlkth' },
{ label: 'Seedance 2.0 Fast', value: 'ep-20260326170056-dkj9m' }
],
selectedModel: 'ep-20260326165811-dlkth',
// @
uploadedImages: []
selectedTemplatePreview: '' // URL
}
},
components: {
RichTextEditor
},
computed: {
...mapGetters(['lang']),
},
@ -338,11 +311,16 @@ export default {
//
getTemplateName(template) {
if (!template) return ''
// chineseContent englishContent
// 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() {
@ -360,21 +338,6 @@ export default {
this.templateDialogVisible = false
// selectedTemplate便
},
//
handleTextChange(content) {
this.editorContent = content
// text
this.text = content
},
// @
handleImageUpload(imageInfo) {
if (imageInfo && imageInfo.url) {
this.uploadedImages.push({
url: imageInfo.url,
name: imageInfo.name || 'image'
})
}
},
close() {
this.showResult = false
},
@ -438,11 +401,10 @@ export default {
url: 'api/ai/imgToImg',
method: 'POST',
data: {
text: this.editorContent || this.text,
text: this.text,
firstUrl: this.firstUrl.url,
functionType: this.current == 1 ? '11' : '12',
tags: tags.join(','),
model: this.selectedModel //
tags: tags.join(',')
}
})
.then((res) => {
@ -539,74 +501,6 @@ export default {
.upload {
// margin-top: 20px;
margin-bottom: 20px;
.model-select {
margin-bottom: 20px;
.model-title {
color: #fff;
margin-bottom: 8px;
font-size: 14px;
}
:deep(.arco-select) {
background-color: #1a1b20;
border-color: rgba(255,255,255,0.1);
}
}
/* 富文本编辑器样式 */
.rich-editor-root {
margin-bottom: 20px;
.toolbar {
display: flex;
gap: 8px;
margin-bottom: 12px;
padding: 8px;
background-color: #1a1b20;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.1);
.tool-btn {
padding: 6px 12px;
background-color: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
color: #fff;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
&:hover {
background-color: rgba(255,255,255,0.1);
border-color: rgba(255,255,255,0.2);
}
}
}
.rich-editor {
min-height: 160px;
padding: 16px;
background-color: #1a1b20;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
color: #fff;
font-size: 14px;
line-height: 1.6;
outline: none;
resize: none;
&:focus {
border-color: rgb(var(--primary-6));
}
&[data-placeholder]:empty::before {
content: attr(data-placeholder);
color: #5c5d68;
pointer-events: none;
}
}
}
&-title {
display: flex;
align-items: center;

View File

@ -1,436 +0,0 @@
<template>
<div :class="prefixCls">
<div class="left">
<!-- 模式切换 -->
<div class="mode-tabs">
<button
:class="['mode-tab', { active: mode === 'text-to-video' }]"
@click="switchMode('text-to-video')">
文生视频
</button>
<button
:class="['mode-tab', { active: mode === 'image-to-video' }]"
@click="switchMode('image-to-video')">
图生视频
</button>
</div>
<!-- 上传区域 - 根据模式显示 -->
<div class="upload" v-if="mode === 'image-to-video'">
<div class="upload-title">
<div class="upload-title-left">
{{ $t('common.uploadFirstImage') || '上传参考图' }}
</div>
<div class="upload-title-tip">
{{ $t('common.uploadImageTip') || '支持PNG/JPG最大10MB' }}
</div>
</div>
<mf-image-upload
listType="picture-card"
uploadHeight="180px"
:show-file-list="false"
:content="$t('common.uploadFirstPlaceholder') || '点击上传参考图片'"
v-model="firstUrl" />
<!-- 可选上传尾帧 -->
<div class="last-frame" v-if="showLastFrame">
<div class="upload-title-left">
{{ $t('common.uploadLastPlaceholder') || '上传尾帧(可选)' }}
</div>
<mf-image-upload
listType="picture-card"
uploadHeight="120px"
:show-file-list="false"
:content="$t('common.uploadLastPlaceholder') || '点击上传尾帧'"
v-model="lastUrl" />
</div>
</div>
<!-- 模型选择 -->
<div class="model-select">
<div class="model-title">{{ $t('common.selectModel') || '选择模型' }}</div>
<a-select v-model="selectedModel" style="width: 100%;">
<a-option
v-for="option in modelOptions"
:key="option.value"
:value="option.value">
{{ option.label }}
</a-option>
</a-select>
</div>
<!-- 富文本编辑器 - 加大输入框 -->
<div class="rich-editor-container">
<RichTextEditor
v-model="editorContent"
:placeholder="$t('common.textVideoPlaceholder') || '描述视频内容,例如:一个女孩在海边跳舞...'"
:uploaded-images="uploadedImages"
@text-change="handleTextChange"
@image-upload="handleImageUpload"
/>
</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>
<!-- 右侧结果区 -->
<div class="right" :class="{ 'has-result': showResult }">
<div v-if="!showResult" class="empty-state">
<img src="/images/empty-video.png" alt="视频生成" />
<p>生成的视频将在这里显示</p>
</div>
<div v-else class="result-container">
<div class="result-video-wrapper">
<video
v-if="videoUrl"
:src="videoUrl"
controls
autoplay
class="result-video"
:poster="firstUrl?.url || ''">
您的浏览器不支持视频播放
</video>
</div>
<div class="result-actions">
<mf-button v-if="canCancel" @click="cancelTask" type="danger">
取消任务
</mf-button>
<mf-button @click="saveVideo" :disabled="!videoUrl">
<a-image src="/images/btn_save.png" /> 保存视频
</mf-button>
<mf-button @click="closeResult">
<a-image src="/images/btn_close.png" /> 关闭
</mf-button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import RichTextEditor from '@/components/RichTextEditor.vue'
export default {
name: 'VideoGen',
data() {
return {
prefixCls: 'video-gen',
mode: 'text-to-video', // 'text-to-video' 'image-to-video'
firstUrl: '',
lastUrl: '',
editorContent: '',
interval: null,
videoUrl: null,
videoLoading: false,
generateLoading: false,
videoId: null,
showResult: false,
price: null,
id: null,
modelOptions: [
{ label: 'Seedance 2.0', value: 'ep-20260326165811-dlkth' },
{ label: 'Seedance 2.0 Fast', value: 'ep-20260326170056-dkj9m' }
],
selectedModel: 'ep-20260326165811-dlkth',
uploadedImages: [],
showLastFrame: false,
maxPollAttempts: 40
}
},
components: {
RichTextEditor
},
computed: {
...mapGetters(['lang'])
},
mounted() {
this.loadPriceInfo()
},
methods: {
async loadPriceInfo() {
this.$axios({
url: 'api/manager/selectInfo',
method: 'GET',
data: { aiType: '21' }
}).then((res) => {
this.price = res.data?.price
this.id = res.data?.id
})
},
handleTextChange(content) {
this.editorContent = content
},
handleImageUpload(imageInfo) {
if (imageInfo && imageInfo.url) {
this.uploadedImages.push({
url: imageInfo.url,
name: imageInfo.name || 'image'
})
}
},
generateVideo() {
if (this.mode === 'image-to-video') {
const firstImageUrl = typeof this.firstUrl === 'object' ? this.firstUrl.url : this.firstUrl
if (!firstImageUrl) {
this.$message.error(this.$t('common.uploadFirstImageError') || '请上传参考图')
return
}
} else if (!this.editorContent.trim()) {
this.$message.error('请输入视频描述文本')
return
}
this.generateLoading = true
const params = {
text: this.editorContent || '一个优雅的女孩在阳光下跳舞',
functionType: '21',
model: this.selectedModel
}
//
if (this.mode === 'image-to-video') {
const firstImageUrl = typeof this.firstUrl === 'object' ? this.firstUrl.url : this.firstUrl
params.firstUrl = firstImageUrl
if (this.lastUrl) {
params.lastUrl = typeof this.lastUrl === 'object' ? this.lastUrl.url : this.lastUrl
}
}
this.$axios({
url: 'api/ai/imgToVideo',
method: 'POST',
data: params
}).then((res) => {
this.generateLoading = false
if (res.code == 200) {
this.videoId = res.data.id
this.showResult = true
this.getVideo(res.data.id)
} else if (res.code == -1) {
this.$confirm({
title: this.$t('common.notice'),
content: this.$t('common.balenceLow'),
onOk: () => this.$router.push('/recharge')
})
}
}).catch(() => {
this.generateLoading = false
})
},
getVideo(videoId) {
let attempts = 0
this.interval = setInterval(() => {
attempts++
if (attempts > this.maxPollAttempts) {
this.$message.warning('视频生成超时')
this.destroyInterval()
return
}
this.$axios({
url: `api/ai/${videoId}`,
method: 'GET'
}).then((res) => {
if (res.code == 200 && res.data.status === 'succeeded') {
this.videoUrl = res.data.content?.video_url || res.data.video_url
this.destroyInterval()
}
})
}, 3000)
},
destroyInterval() {
if (this.interval) {
clearInterval(this.interval)
this.interval = null
}
},
saveVideo() {
if (!this.videoUrl) return
const link = document.createElement('a')
link.href = this.videoUrl
link.download = `video_${Date.now()}.mp4`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
},
closeResult() {
this.showResult = false
this.videoUrl = null
this.videoId = null
this.taskStatus = null
},
//
async cancelTask() {
if (!this.videoId) return
this.$confirm({
title: '取消任务',
content: '确定要取消当前视频生成任务吗?取消后余额将退回。',
okText: '确定取消',
cancelText: '关闭',
onOk: async () => {
try {
const res = await this.$axios({
url: `api/ai/${this.videoId}/cancel`,
method: 'POST'
})
if (res.code === 200) {
this.$message.success('任务已取消,余额已退回')
this.destroyInterval()
this.showResult = false
this.videoUrl = null
this.videoId = null
} else {
this.$message.error(res.msg || '取消失败')
}
} catch (error) {
this.$message.error('取消请求失败')
}
}
})
},
//
canCancel() {
return this.videoId && !this.videoUrl
}
},
beforeUnmount() {
this.destroyInterval()
}
}
</script>
<style lang="less" scoped>
.video-gen {
display: flex;
height: 100%;
gap: 20px;
padding: 20px;
background: #0f1014;
.left {
width: 480px;
background: #1a1b20;
border-radius: 12px;
padding: 24px;
display: flex;
flex-direction: column;
}
.page-title {
font-size: 22px;
color: #fff;
margin-bottom: 24px;
font-weight: 600;
.subtitle {
font-size: 14px;
color: #666;
margin-left: 12px;
font-weight: normal;
}
}
.model-select {
margin: 16px 0;
.model-title {
color: #ddd;
margin-bottom: 8px;
font-size: 14px;
}
}
.rich-editor-container {
flex: 1;
min-height: 320px;
margin: 16px 0;
display: flex;
flex-direction: column;
:deep(.rich-editor-root) {
flex: 1;
display: flex;
flex-direction: column;
}
:deep(.rich-editor) {
flex: 1;
min-height: 280px !important;
font-size: 16px;
line-height: 1.75;
padding: 20px;
}
}
.submit {
margin-top: 16px;
}
.right {
flex: 1;
background: #000;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
&.has-result {
align-items: flex-start;
padding: 20px;
}
}
.empty-state {
text-align: center;
color: #666;
img {
width: 120px;
opacity: 0.6;
}
}
.result-video-wrapper {
width: 100%;
max-width: 720px;
}
.result-video {
width: 100%;
border-radius: 8px;
}
}
</style>