chore: 删除旧架构代码(config/models、runninghub、dialogBox 旧组件)
- 删除 dialogBox 下 8 个子组件目录(model/proportion/dimension/quantity/pattern/Time/imageUploader/videoImageUploader) - 删除 src/config/models/(模型配置已迁移至 src/platforms/painting/models/) - 删除 src/config/runninghub/ 及 config/index.js(Video 不再使用旧适配器) - 更新 Painting 平台的 getModelConfig 导入路径为 ./models/index.js - 移除 modelSelector.vue 中未使用的 getModelConfig 导入 - 保留 src/utils/modelConfig.js(Video 描述符仍需要 fetchModelConfig 加载远程配置)
This commit is contained in:
parent
bcd83fc0a8
commit
73f7bd888e
17
components.d.ts
vendored
17
components.d.ts
vendored
@ -11,19 +11,10 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
2: typeof import('./src/components/virtual-scroller/VirtualScroller copy 2.vue')['default']
|
|
||||||
3: typeof import('./src/components/virtual-scroller/VirtualScroller copy 3.vue')['default']
|
|
||||||
Canvas: typeof import('./src/components/canvas/index.vue')['default']
|
Canvas: typeof import('./src/components/canvas/index.vue')['default']
|
||||||
copy: typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default']
|
|
||||||
DialogBox: typeof import('./src/components/dialogBox/index.vue')['default']
|
DialogBox: typeof import('./src/components/dialogBox/index.vue')['default']
|
||||||
Dimension: typeof import('./src/components/dialogBox/dimension/index.vue')['default']
|
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
IEpCalendar: typeof import('~icons/ep/calendar')['default']
|
IEpCalendar: typeof import('~icons/ep/calendar')['default']
|
||||||
@ -32,19 +23,11 @@ declare module 'vue' {
|
|||||||
IEpLoading: typeof import('~icons/ep/loading')['default']
|
IEpLoading: typeof import('~icons/ep/loading')['default']
|
||||||
IEpPlus: typeof import('~icons/ep/plus')['default']
|
IEpPlus: typeof import('~icons/ep/plus')['default']
|
||||||
IEpStar: typeof import('~icons/ep/star')['default']
|
IEpStar: typeof import('~icons/ep/star')['default']
|
||||||
ImageUploader: typeof import('./src/components/dialogBox/imageUploader/index.vue')['default']
|
|
||||||
Img: typeof import('./src/components/Img/index.vue')['default']
|
Img: typeof import('./src/components/Img/index.vue')['default']
|
||||||
Painting: typeof import('./src/components/dialogBox/model/painting.vue')['default']
|
|
||||||
ParamControl: typeof import('./src/components/dialogBox/ParamControl.vue')['default']
|
|
||||||
Pattern: typeof import('./src/components/dialogBox/pattern/index.vue')['default']
|
|
||||||
Popover: typeof import('./src/components/Popover/index.vue')['default']
|
Popover: typeof import('./src/components/Popover/index.vue')['default']
|
||||||
Quantity: typeof import('./src/components/dialogBox/quantity/index.vue')['default']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
Select: typeof import('./src/components/Select/index.vue')['default']
|
Select: typeof import('./src/components/Select/index.vue')['default']
|
||||||
Time: typeof import('./src/components/dialogBox/Time/index.vue')['default']
|
|
||||||
Video: typeof import('./src/components/dialogBox/model/video.vue')['default']
|
|
||||||
VideoImageUploader: typeof import('./src/components/dialogBox/videoImageUploader/index.vue')['default']
|
|
||||||
VirtualScroller: typeof import('./src/components/virtual-scroller/VirtualScroller.vue')['default']
|
VirtualScroller: typeof import('./src/components/virtual-scroller/VirtualScroller.vue')['default']
|
||||||
'VirtualScroller copy': typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default']
|
'VirtualScroller copy': typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default']
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Select
|
|
||||||
v-model="quantity"
|
|
||||||
:options="quantityOptions"
|
|
||||||
class="quantity-select"
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<img src="@/assets/dialog/time.svg" alt="" style="width: 20px;">
|
|
||||||
</template>
|
|
||||||
<template #header>
|
|
||||||
<span class="header">选择视频生成时长</span>
|
|
||||||
</template>
|
|
||||||
</Select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Select from '@/components/Select/index.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [
|
|
||||||
{ value: 5, label: '5s' },
|
|
||||||
{ value: 6, label: '6s' },
|
|
||||||
{ value: 7, label: '7s' },
|
|
||||||
{ value: 8, label: '8s' },
|
|
||||||
{ value: 9, label: '9s' },
|
|
||||||
{ value: 10, label: '10s' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const quantity = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const quantityOptions = computed(() => props.options)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.quantity-select {
|
|
||||||
:deep(.select-header) {
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.select-text) {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
|
||||||
min-width: 136px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
|
||||||
min-width: 80px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.header{
|
|
||||||
margin-left: 10px;
|
|
||||||
color: #999;
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,247 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Popover placement="top">
|
|
||||||
<div class="dimension-container">
|
|
||||||
<div class="section">
|
|
||||||
<h3>尺寸 (px)</h3>
|
|
||||||
<div class="size-inputs">
|
|
||||||
<div class="input-group">
|
|
||||||
<label>W</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
v-model.number="localWidth"
|
|
||||||
:min="minW"
|
|
||||||
:max="maxW"
|
|
||||||
@input="onWidthChange"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="lock-icon" :class="{ locked: isLocked }" @click="toggleLock">
|
|
||||||
<img :src="isLocked ? lockIcon : lockNoIcon" alt="约束比例">
|
|
||||||
<span class="tooltip">{{ isLocked ? '解绑比例' : '约束比例' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label>H</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
v-model.number="localHeight"
|
|
||||||
:min="minH"
|
|
||||||
:max="maxH"
|
|
||||||
@input="onHeightChange"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #reference>
|
|
||||||
<div class="choice-btn">
|
|
||||||
<img src="@/assets/dialog/proportion.svg" alt="" style="width: 16px;">
|
|
||||||
<span>{{ displayText }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Popover from '@/components/Popover/index.vue'
|
|
||||||
import lockIcon from '@/assets/dialog/lock.svg'
|
|
||||||
import lockNoIcon from '@/assets/dialog/lockNo.svg'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
width: { type: Number, default: 1024 },
|
|
||||||
height: { type: Number, default: 1024 },
|
|
||||||
minW: { type: Number, default: 256 },
|
|
||||||
maxW: { type: Number, default: 6197 },
|
|
||||||
minH: { type: Number, default: 256 },
|
|
||||||
maxH: { type: Number, default: 4096 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:width', 'update:height'])
|
|
||||||
|
|
||||||
const localWidth = ref(props.width)
|
|
||||||
const localHeight = ref(props.height)
|
|
||||||
const isLocked = ref(true)
|
|
||||||
const lastRatio = ref(props.width / props.height)
|
|
||||||
|
|
||||||
const displayText = computed(() => `${localWidth.value} × ${localHeight.value}`)
|
|
||||||
|
|
||||||
watch(() => props.width, (val) => { localWidth.value = val })
|
|
||||||
watch(() => props.height, (val) => { localHeight.value = val })
|
|
||||||
|
|
||||||
const toggleLock = () => {
|
|
||||||
isLocked.value = !isLocked.value
|
|
||||||
if (isLocked.value) {
|
|
||||||
lastRatio.value = localWidth.value / localHeight.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const clamp = (val, min, max) => Math.max(min, Math.min(max, Math.round(val)))
|
|
||||||
|
|
||||||
const onWidthChange = () => {
|
|
||||||
localWidth.value = clamp(localWidth.value, props.minW, props.maxW)
|
|
||||||
if (isLocked.value) {
|
|
||||||
localHeight.value = clamp(Math.round(localWidth.value / lastRatio.value), props.minH, props.maxH)
|
|
||||||
}
|
|
||||||
emit('update:width', localWidth.value)
|
|
||||||
emit('update:height', localHeight.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onHeightChange = () => {
|
|
||||||
localHeight.value = clamp(localHeight.value, props.minH, props.maxH)
|
|
||||||
if (isLocked.value) {
|
|
||||||
localWidth.value = clamp(Math.round(localHeight.value * lastRatio.value), props.minW, props.maxW)
|
|
||||||
}
|
|
||||||
emit('update:width', localWidth.value)
|
|
||||||
emit('update:height', localHeight.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch([localWidth, localHeight], () => {
|
|
||||||
if (!isLocked.value) {
|
|
||||||
lastRatio.value = localWidth.value / localHeight.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.choice-btn {
|
|
||||||
display: flex;
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.choice-btn:hover {
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dimension-container {
|
|
||||||
padding: 20px;
|
|
||||||
min-width: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
border-radius: 20px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-inputs {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
label {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 12px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
height: 36px;
|
|
||||||
padding: 12px 12px 12px 30px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
background: #f5f6f7;
|
|
||||||
text-align: right;
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
|
|
||||||
&::-webkit-inner-spin-button,
|
|
||||||
&::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 10px;
|
|
||||||
position: relative;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
background: #333;
|
|
||||||
color: #fff;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border: 5px solid transparent;
|
|
||||||
border-top-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.locked {
|
|
||||||
background: #f5f6f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="image-uploader">
|
|
||||||
<div class="image-list">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in localPreviewList"
|
|
||||||
:key="item.uid"
|
|
||||||
class="image-item"
|
|
||||||
@click.stop="handleImageClick(index)"
|
|
||||||
>
|
|
||||||
<img :src="item.url" class="uploaded-image" alt="上传的图片" />
|
|
||||||
<div class="image-index">{{ index + 1 }}</div>
|
|
||||||
<div class="delete-icon" @click.stop="handleDelete(index)">
|
|
||||||
<i-ep-close />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="localPreviewList.length < limit" class="upload-trigger" @click="triggerUpload">
|
|
||||||
<i-ep-plus color="#333333" />
|
|
||||||
<div class="upload-text">参考内容</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-upload
|
|
||||||
ref="uploadRef"
|
|
||||||
v-show="false"
|
|
||||||
:action="uploadurl"
|
|
||||||
multiple
|
|
||||||
:limit="limit"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
:on-success="handleSuccess"
|
|
||||||
:on-error="handleError"
|
|
||||||
:on-exceed="handleExceed"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { genFileId } from 'element-plus'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
limit: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 图片列表,每个元素包含 url 和 uid 属性
|
|
||||||
*/
|
|
||||||
modelValue: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'open-canvas'])
|
|
||||||
|
|
||||||
const uploadurl = import.meta.env.VITE_API_WORKFLOW_UPLOAD
|
|
||||||
const uploadRef = ref(null)
|
|
||||||
const imageList = ref([...props.modelValue])
|
|
||||||
const localPreviewList = ref([...props.modelValue])
|
|
||||||
const isUploading = ref(false)
|
|
||||||
|
|
||||||
watch(() => props.modelValue, async (newVal) => {
|
|
||||||
if (isUploading.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
imageList.value = [...newVal]
|
|
||||||
|
|
||||||
const newPreviewList = []
|
|
||||||
for (const img of newVal) {
|
|
||||||
let previewImg = { ...img }
|
|
||||||
if (img.url && !img.url.startsWith('blob:')) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(img.url)
|
|
||||||
const blob = await response.blob()
|
|
||||||
previewImg = {
|
|
||||||
...img,
|
|
||||||
url: URL.createObjectURL(blob),
|
|
||||||
serverUrl: img.url
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create blob URL:', error)
|
|
||||||
previewImg = {
|
|
||||||
...img,
|
|
||||||
serverUrl: img.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newPreviewList.push(previewImg)
|
|
||||||
}
|
|
||||||
localPreviewList.value = newPreviewList
|
|
||||||
}, { deep: true, immediate: true })
|
|
||||||
|
|
||||||
const triggerUpload = () => {
|
|
||||||
uploadRef.value.$el.querySelector('input').click()
|
|
||||||
}
|
|
||||||
|
|
||||||
const beforeUpload = (rawFile) => {
|
|
||||||
const allowedTypes = ['image/jpeg', 'image/png']
|
|
||||||
|
|
||||||
if (!allowedTypes.includes(rawFile.type)) {
|
|
||||||
ElMessage.error('不支持的文件类型,请上传 JPG、PNG 格式的图片')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawFile.size / 1024 / 1024 > 10) {
|
|
||||||
ElMessage.error('图片大小不能超过 10MB')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSuccess = (response, uploadFile) => {
|
|
||||||
ElMessage.success('上传成功')
|
|
||||||
|
|
||||||
isUploading.value = true
|
|
||||||
|
|
||||||
const localUrl = URL.createObjectURL(uploadFile.raw)
|
|
||||||
|
|
||||||
const newImage = {
|
|
||||||
uid: uploadFile.uid,
|
|
||||||
url: response.url
|
|
||||||
}
|
|
||||||
imageList.value.push(newImage)
|
|
||||||
emit('update:modelValue', [...imageList.value])
|
|
||||||
|
|
||||||
const newPreview = {
|
|
||||||
uid: uploadFile.uid,
|
|
||||||
url: localUrl,
|
|
||||||
serverUrl: response.url
|
|
||||||
}
|
|
||||||
localPreviewList.value.push(newPreview)
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
isUploading.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleError = () => {
|
|
||||||
ElMessage.error('上传失败,请重新选择图片')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleExceed = () => {
|
|
||||||
ElMessage.warning(`最多只能上传 ${props.limit} 张图片`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = (index) => {
|
|
||||||
const previewItem = localPreviewList.value[index]
|
|
||||||
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
|
||||||
URL.revokeObjectURL(previewItem.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
localPreviewList.value.splice(index, 1)
|
|
||||||
imageList.value.splice(index, 1)
|
|
||||||
emit('update:modelValue', [...imageList.value])
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleImageClick = (clickedIndex) => {
|
|
||||||
const clickedImage = localPreviewList.value[clickedIndex]
|
|
||||||
if (!clickedImage) return
|
|
||||||
|
|
||||||
const otherImages = localPreviewList.value
|
|
||||||
.filter((_, index) => index !== clickedIndex)
|
|
||||||
.map((img, index) => ({
|
|
||||||
...img,
|
|
||||||
displayIndex: index + 2
|
|
||||||
}))
|
|
||||||
|
|
||||||
emit('open-canvas', {
|
|
||||||
mainImage: clickedImage,
|
|
||||||
referenceImages: otherImages
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelect = async (url) => {
|
|
||||||
const initialFile = await fetch(url)
|
|
||||||
const blob = await initialFile.blob()
|
|
||||||
const file = new File([blob], `selected_image_${Date.now()}.jpg`, { type: blob.type })
|
|
||||||
file.uid = genFileId()
|
|
||||||
|
|
||||||
if (props.limit === 1 && imageList.value.length === 1) {
|
|
||||||
imageList.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadRef.value.handleStart(file)
|
|
||||||
uploadRef.value.submit()
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
handleSelect,
|
|
||||||
triggerUpload
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.image-uploader {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-list {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-item {
|
|
||||||
position: relative;
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
.delete-icon {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploaded-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-index {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 4px;
|
|
||||||
right: 4px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background: rgba(0, 15, 51, 0.8);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
z-index: 5;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -6px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background: #ef4444;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
z-index: 20;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #dc2626;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-trigger {
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
// border: 2px dashed #d1d5db;
|
|
||||||
background: rgba(0, 15, 51, 0.04);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #9ca3af;
|
|
||||||
font-size: 15px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: #333;
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #F0F1F2;
|
|
||||||
border-color: #626aef;
|
|
||||||
// color: #626aef;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-text {
|
|
||||||
color: #666;
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Select
|
|
||||||
v-model="selectValue"
|
|
||||||
:grouped-options="modelGroups"
|
|
||||||
class="model-select"
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<img src="@/assets/dialog/model.svg" alt="" style="width: 16px;">
|
|
||||||
</template>
|
|
||||||
</Select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Select from '@/components/Select/index.vue'
|
|
||||||
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
|
||||||
import { getModelConfig } from '@/config/models/index.js'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: { type: String, default: 'Flux 2' },
|
|
||||||
typeValue: { type: String, default: 'text' },
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:typeValue'])
|
|
||||||
|
|
||||||
const platformModels = ref([])
|
|
||||||
|
|
||||||
const categoryMap = [
|
|
||||||
{ tag: 'text', label: '生成模型', inputType: 'text' },
|
|
||||||
{ tag: 'edit', label: '编辑模型', inputType: 'image' },
|
|
||||||
{ tag: 'vision', label: '视觉理解模型', inputType: 'vision' },
|
|
||||||
]
|
|
||||||
|
|
||||||
function parseValue(encoded) {
|
|
||||||
if (!encoded) return null
|
|
||||||
const idx = encoded.indexOf('::')
|
|
||||||
if (idx === -1) return null
|
|
||||||
return { tag: encoded.substring(0, idx), modelName: encoded.substring(idx + 2) }
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeValue(tag, modelName) {
|
|
||||||
return `${tag}::${modelName}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function findTagForModel(modelName) {
|
|
||||||
for (const cat of categoryMap) {
|
|
||||||
const model = platformModels.value.find(m => (m.display_name || m.name) === modelName && m.tags?.includes(cat.tag))
|
|
||||||
if (model) return cat.tag
|
|
||||||
}
|
|
||||||
return 'text'
|
|
||||||
}
|
|
||||||
|
|
||||||
function tagToInputType(tag) {
|
|
||||||
const cat = categoryMap.find(c => c.tag === tag)
|
|
||||||
return cat?.inputType || 'text'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select 双向绑定值(内部编码)
|
|
||||||
const selectValue = computed({
|
|
||||||
get: () => {
|
|
||||||
if (!props.modelValue) return ''
|
|
||||||
const tag = findTagForModel(props.modelValue)
|
|
||||||
return encodeValue(tag, props.modelValue)
|
|
||||||
},
|
|
||||||
set: (encoded) => {
|
|
||||||
const parsed = parseValue(encoded)
|
|
||||||
if (!parsed) return
|
|
||||||
emit('update:modelValue', parsed.modelName)
|
|
||||||
emit('update:typeValue', tagToInputType(parsed.tag))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// 从 API 加载模型列表
|
|
||||||
const loadModels = async () => {
|
|
||||||
try {
|
|
||||||
const code = getPlatformCode('Painting')
|
|
||||||
const models = await fetchPlatformModels(code)
|
|
||||||
platformModels.value = models || []
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载平台模型列表失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadModels()
|
|
||||||
|
|
||||||
// 按固定分类分组,value 编码为 tag::displayName
|
|
||||||
const modelGroups = computed(() => {
|
|
||||||
const models = platformModels.value
|
|
||||||
if (models.length === 0) return []
|
|
||||||
|
|
||||||
return categoryMap
|
|
||||||
.filter(cat => models.some(m => m.tags?.includes(cat.tag)))
|
|
||||||
.map(cat => ({
|
|
||||||
label: cat.label,
|
|
||||||
options: models
|
|
||||||
.filter(m => m.tags?.includes(cat.tag))
|
|
||||||
.map(m => ({
|
|
||||||
value: `${cat.tag}::${m.display_name || m.name}`,
|
|
||||||
label: m.display_name || m.name,
|
|
||||||
disabled: m.disabled || false,
|
|
||||||
}))
|
|
||||||
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })),
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 模型列表加载后自动纠正不可用模型
|
|
||||||
watch(platformModels, (models) => {
|
|
||||||
if (models.length === 0) return
|
|
||||||
const currentModel = models.find(m => (m.display_name || m.name) === props.modelValue || m.id === props.modelValue)
|
|
||||||
if (!currentModel || currentModel.disabled) {
|
|
||||||
const firstEnabled = models.find(m => !m.disabled)
|
|
||||||
if (firstEnabled) {
|
|
||||||
emit('update:modelValue', firstEnabled.display_name || firstEnabled.name)
|
|
||||||
emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
// 外部改变 modelValue 时校验是否可用
|
|
||||||
watch(() => props.modelValue, (newValue) => {
|
|
||||||
if (!newValue) return
|
|
||||||
const models = platformModels.value
|
|
||||||
if (models.length === 0) return
|
|
||||||
const currentModel = models.find(m => (m.display_name || m.name) === newValue)
|
|
||||||
if (currentModel && currentModel.disabled) {
|
|
||||||
const firstEnabled = models.find(m => !m.disabled)
|
|
||||||
if (firstEnabled) {
|
|
||||||
emit('update:modelValue', firstEnabled.display_name || firstEnabled.name)
|
|
||||||
emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.model-select {
|
|
||||||
:deep(.select-header) {
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.select-text) {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
|
||||||
max-height: 510px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
|
||||||
min-width: 120px;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: rgba(0, 15, 51, 0.10);
|
|
||||||
color: #000F33;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Select
|
|
||||||
v-model="model"
|
|
||||||
:options="modelGroups"
|
|
||||||
class="model-select"
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<img src="@/assets/dialog/model.svg" alt="" style="width: 16px;">
|
|
||||||
</template>
|
|
||||||
</Select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Select from '@/components/Select/index.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
typeValue: {
|
|
||||||
type: String,
|
|
||||||
default: 'text'
|
|
||||||
},
|
|
||||||
videoPattern: {
|
|
||||||
type: String,
|
|
||||||
default: '文生视频'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:typeValue'])
|
|
||||||
|
|
||||||
const videoConfig = ref({})
|
|
||||||
|
|
||||||
const fetchConfig = async () => {
|
|
||||||
try {
|
|
||||||
const url = `${import.meta.env.VITE_API_MODEL_RESOURCE}/static/public/Platform/AIGC_modelConfig/video.json`
|
|
||||||
const response = await fetch(url)
|
|
||||||
const data = await response.json()
|
|
||||||
videoConfig.value = data
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch video config:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fetchConfig()
|
|
||||||
|
|
||||||
watch(() => videoConfig.value, (newConfig) => {
|
|
||||||
const models = newConfig[props.videoPattern] || []
|
|
||||||
if (models.length > 0) {
|
|
||||||
const enabledModels = models.filter(m => !m.disabled)
|
|
||||||
if (enabledModels.length > 0) {
|
|
||||||
const currentModelExists = enabledModels.find(m => m.value === props.modelValue)
|
|
||||||
if (!currentModelExists) {
|
|
||||||
model.value = enabledModels[0].value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { deep: true })
|
|
||||||
|
|
||||||
|
|
||||||
const model = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => {
|
|
||||||
emit('update:modelValue', value)
|
|
||||||
const newType = getModelType(props.videoPattern)
|
|
||||||
emit('update:typeValue', newType)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const modelGroups = computed(() => {
|
|
||||||
const models = videoConfig.value[props.videoPattern] || []
|
|
||||||
return models
|
|
||||||
})
|
|
||||||
|
|
||||||
const getModelType = (value) => {
|
|
||||||
switch (value) {
|
|
||||||
case '文生视频':
|
|
||||||
return 'text'
|
|
||||||
case '首尾帧':
|
|
||||||
return 'image'
|
|
||||||
case '数字人':
|
|
||||||
return 'digitalHuman'
|
|
||||||
default:
|
|
||||||
return 'text'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.videoPattern, (newPattern) => {
|
|
||||||
const models = videoConfig.value[newPattern] || []
|
|
||||||
if (models.length > 0) {
|
|
||||||
const enabledModels = models.filter(m => !m.disabled)
|
|
||||||
if (enabledModels.length > 0) {
|
|
||||||
const currentModelExists = enabledModels.find(m => m.value === props.modelValue)
|
|
||||||
if (!currentModelExists) {
|
|
||||||
model.value = enabledModels[0].value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => props.modelValue, (newValue) => {
|
|
||||||
const models = videoConfig.value[props.videoPattern] || []
|
|
||||||
const currentModel = models.find(m => m.value === newValue)
|
|
||||||
if (currentModel && currentModel.disabled) {
|
|
||||||
const enabledModels = models.filter(m => !m.disabled)
|
|
||||||
if (enabledModels.length > 0) {
|
|
||||||
model.value = enabledModels[0].value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.model-select {
|
|
||||||
:deep(.select-header) {
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.select-text) {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
|
||||||
max-height: 510px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
|
||||||
min-width: 120px;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: rgba(0, 15, 51, 0.10);
|
|
||||||
color: #000F33;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Select
|
|
||||||
v-model="quantity"
|
|
||||||
:options="quantityOptions"
|
|
||||||
class="quantity-select"
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<img :src="selectedIcon" alt="" style="width: 20px;">
|
|
||||||
</template>
|
|
||||||
<template #option="{ option }">
|
|
||||||
<div class="option-content-custom">
|
|
||||||
<img :src="option.icon" alt="" style="width: 20px;">
|
|
||||||
<span v-if="option.labelText" class="option-label-text">{{ option.labelText }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Select from '@/components/Select/index.vue'
|
|
||||||
|
|
||||||
import videoPattern2 from '@/assets/dialog/videoPattern2.svg'
|
|
||||||
import videoPattern4 from '@/assets/dialog/videoPattern4.svg'
|
|
||||||
import videoPattern5 from '@/assets/dialog/videoPattern5.svg'
|
|
||||||
import videoPattern6 from '@/assets/dialog/videoPattern6.svg'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '全能参考'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const quantity = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const quantityOptions = [
|
|
||||||
{ value: '文生视频', label: '文生视频', labelText: '文生视频', icon: videoPattern2 },
|
|
||||||
{ value: '首尾帧', label: '首尾帧', labelText: '首尾帧', icon: videoPattern2 },
|
|
||||||
{ value: '数字人', label: '数字人', labelText: '数字人', icon: videoPattern2 },
|
|
||||||
{ value: '全能参考', label: '全能参考', labelText: '全能参考', icon: videoPattern4 },
|
|
||||||
{ value: '智能多帧', label: '智能多帧', labelText: '智能多帧', icon: videoPattern5 },
|
|
||||||
{ value: '主体参考', label: '主体参考', labelText: '主体参考', icon: videoPattern6 }
|
|
||||||
]
|
|
||||||
|
|
||||||
const selectedIcon = computed(() => {
|
|
||||||
const option = quantityOptions.find(opt => opt.value === quantity.value)
|
|
||||||
return option ? option.icon : videoPattern1
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.quantity-select {
|
|
||||||
:deep(.select-header) {
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.select-text) {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
|
||||||
min-width: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
|
||||||
min-width: 80px;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-content-custom {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: start;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-label-text {
|
|
||||||
font-size: 14px;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,465 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Popover placement="top">
|
|
||||||
<div class="proportion-container">
|
|
||||||
<div class="section">
|
|
||||||
<h3>选择比例</h3>
|
|
||||||
<div class="proportion-options">
|
|
||||||
<div
|
|
||||||
v-for="item in proportionOptions"
|
|
||||||
:key="item.value"
|
|
||||||
class="proportion-item"
|
|
||||||
:class="{ active: proportion === item.value }"
|
|
||||||
:style="getProportionStyle(item.value)"
|
|
||||||
@click="selectProportion(item.value)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="resolutionOptions.length > 0" class="section">
|
|
||||||
<h3>选择分辨率</h3>
|
|
||||||
<div class="resolution-options">
|
|
||||||
<div
|
|
||||||
v-for="item in resolutionOptions"
|
|
||||||
:key="item.value"
|
|
||||||
class="resolution-item"
|
|
||||||
:class="{ active: resolution === item.value }"
|
|
||||||
@click="selectResolution(item.value)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="allowCustom" class="section">
|
|
||||||
<h3>尺寸(px)</h3>
|
|
||||||
<div class="size-inputs">
|
|
||||||
<div class="input-group">
|
|
||||||
<label>W</label>
|
|
||||||
<input type="number" v-model.number="width" @input="updateWidth" :disabled="isLocked">
|
|
||||||
</div>
|
|
||||||
<div class="lock-icon" :class="{ locked: isLocked }" @click="toggleLock">
|
|
||||||
<img :src="isLocked ? lockIcon : lockNoIcon" alt="约束比例">
|
|
||||||
<span class="tooltip">{{ isLocked ? '解绑比例' : '约束比例' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label>H</label>
|
|
||||||
<input type="number" v-model.number="height" @input="updateHeight" :disabled="isLocked">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #reference>
|
|
||||||
<div class="choice-btn">
|
|
||||||
<img src="@/assets/dialog/proportion.svg" alt="" style="width: 16px;">
|
|
||||||
<span>{{ proportion }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Popover from '@/components/Popover/index.vue'
|
|
||||||
import lockIcon from '@/assets/dialog/lock.svg'
|
|
||||||
import lockNoIcon from '@/assets/dialog/lockNo.svg'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '1:1'
|
|
||||||
},
|
|
||||||
resolution: {
|
|
||||||
type: String,
|
|
||||||
default: '2k'
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 2048
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: Number,
|
|
||||||
default: 2048
|
|
||||||
},
|
|
||||||
proportionOptions: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [
|
|
||||||
{ value: '智能', label: '智能' },
|
|
||||||
{ value: '21:9', label: '21:9' },
|
|
||||||
{ value: '16:9', label: '16:9' },
|
|
||||||
{ value: '4:3', label: '4:3' },
|
|
||||||
{ value: '1:1', label: '1:1' },
|
|
||||||
{ value: '3:4', label: '3:4' },
|
|
||||||
{ value: '9:16', label: '9:16' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
allowCustom: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
resolutionOptions: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [
|
|
||||||
{ value: '1k', label: '标清 1K' },
|
|
||||||
{ value: '2k', label: '高清 2K' },
|
|
||||||
{ value: '4k', label: '超清 4K' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:resolution', 'update:width', 'update:height'])
|
|
||||||
|
|
||||||
const proportion = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const resolution = computed({
|
|
||||||
get: () => props.resolution,
|
|
||||||
set: (value) => emit('update:resolution', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const width = ref(props.width)
|
|
||||||
const height = ref(props.height)
|
|
||||||
const isLocked = ref(true)
|
|
||||||
|
|
||||||
const toggleLock = () => {
|
|
||||||
isLocked.value = !isLocked.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectProportion = (value) => {
|
|
||||||
proportion.value = value
|
|
||||||
updateDimensionsByProportion(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectResolution = (value) => {
|
|
||||||
resolution.value = value
|
|
||||||
updateDimensionsByResolution(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateDimensionsByProportion = (proportionValue) => {
|
|
||||||
if (proportionValue === '智能') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const [w, h] = proportionValue.split(':').map(Number)
|
|
||||||
const aspectRatio = w / h
|
|
||||||
if (width.value > height.value) {
|
|
||||||
height.value = Math.round(width.value / aspectRatio)
|
|
||||||
} else {
|
|
||||||
width.value = Math.round(height.value * aspectRatio)
|
|
||||||
}
|
|
||||||
emitUpdateDimensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateDimensionsByResolution = (resolutionValue) => {
|
|
||||||
let baseSize
|
|
||||||
switch (resolutionValue) {
|
|
||||||
case '1k':
|
|
||||||
baseSize = 1024
|
|
||||||
break
|
|
||||||
case '2k':
|
|
||||||
baseSize = 2048
|
|
||||||
break
|
|
||||||
case '4k':
|
|
||||||
baseSize = 4096
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
baseSize = 2048
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proportion.value === '智能') {
|
|
||||||
width.value = baseSize
|
|
||||||
height.value = baseSize
|
|
||||||
} else {
|
|
||||||
const [w, h] = proportion.value.split(':').map(Number)
|
|
||||||
const aspectRatio = w / h
|
|
||||||
if (aspectRatio > 1) {
|
|
||||||
width.value = baseSize
|
|
||||||
height.value = Math.round(baseSize / aspectRatio)
|
|
||||||
} else {
|
|
||||||
height.value = baseSize
|
|
||||||
width.value = Math.round(baseSize * aspectRatio)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emitUpdateDimensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateWidth = () => {
|
|
||||||
if (isLocked.value && proportion.value !== '智能') {
|
|
||||||
const [w, h] = proportion.value.split(':').map(Number)
|
|
||||||
const aspectRatio = w / h
|
|
||||||
height.value = Math.round(width.value / aspectRatio)
|
|
||||||
}
|
|
||||||
emitUpdateDimensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateHeight = () => {
|
|
||||||
if (isLocked.value && proportion.value !== '智能') {
|
|
||||||
const [w, h] = proportion.value.split(':').map(Number)
|
|
||||||
const aspectRatio = w / h
|
|
||||||
width.value = Math.round(height.value * aspectRatio)
|
|
||||||
}
|
|
||||||
emitUpdateDimensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const emitUpdateDimensions = () => {
|
|
||||||
emit('update:width', width.value)
|
|
||||||
emit('update:height', height.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getProportionStyle = (value) => {
|
|
||||||
if (value === '智能') {
|
|
||||||
return {
|
|
||||||
'--width': '20px',
|
|
||||||
'--height': '20px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const [w, h] = value.split(':').map(Number)
|
|
||||||
const aspectRatio = w / h
|
|
||||||
const baseSize = 20
|
|
||||||
|
|
||||||
if (aspectRatio > 1) {
|
|
||||||
return {
|
|
||||||
'--width': `${baseSize}px`,
|
|
||||||
'--height': `${Math.round(baseSize / aspectRatio)}px`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'--width': `${Math.round(baseSize * aspectRatio)}px`,
|
|
||||||
'--height': `${baseSize}px`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => [props.modelValue, props.resolution], () => {
|
|
||||||
updateDimensionsByResolution(resolution.value)
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.choice-btn{
|
|
||||||
display: flex;
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.choice-btn:hover{
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proportion-container{
|
|
||||||
padding: 20px;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section{
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 20px;
|
|
||||||
|
|
||||||
&:last-child{
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3{
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.proportion-options{
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 5px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
background-color: #F8F9FA;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proportion-item{
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: bottom;
|
|
||||||
color: #999;
|
|
||||||
|
|
||||||
&::before{
|
|
||||||
content: '';
|
|
||||||
width: var(--width, 20px);
|
|
||||||
height: var(--height, 20px);
|
|
||||||
background: #F5F6F7;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border: 2px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active{
|
|
||||||
color: #000F33;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
&.active::before{
|
|
||||||
border-color: #000F33;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resolution-options{
|
|
||||||
display: flex;
|
|
||||||
padding: 5px;
|
|
||||||
align-items: center;
|
|
||||||
align-self: stretch;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #F8F9FA;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resolution-item{
|
|
||||||
flex: 1;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
color: #666;
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active{
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-inputs{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group{
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
label{
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 12px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input{
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
height: 36px;
|
|
||||||
padding: 12px 12px 12px 30px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
background: #f5f6f7;
|
|
||||||
text-align: right;
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
|
|
||||||
&::-webkit-inner-spin-button,
|
|
||||||
&::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus{
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled{
|
|
||||||
color: #999;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-icon{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 10px;
|
|
||||||
position: relative;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
|
|
||||||
img{
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip{
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
background: #333;
|
|
||||||
color: #fff;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip::after{
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border: 5px solid transparent;
|
|
||||||
border-top-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
opacity: 0.8;
|
|
||||||
|
|
||||||
.tooltip{
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.locked{
|
|
||||||
background: #f5f6f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,242 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Popover placement="top">
|
|
||||||
<div class="proportion-container">
|
|
||||||
<div class="section">
|
|
||||||
<h3>选择比例</h3>
|
|
||||||
<div class="proportion-options" :style="{ marginBottom: props.type === 'Video' ? '0px' : '20px' }">
|
|
||||||
<div
|
|
||||||
v-for="item in proportionOptions"
|
|
||||||
:key="item.value"
|
|
||||||
class="proportion-item"
|
|
||||||
:class="{ active: proportion === item.value }"
|
|
||||||
:style="getProportionStyle(item.value)"
|
|
||||||
@click="selectProportion(item.value)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h3>选择分辨率</h3>
|
|
||||||
<div class="resolution-options">
|
|
||||||
<div
|
|
||||||
v-for="item in resolutionOptions"
|
|
||||||
:key="item.value"
|
|
||||||
class="resolution-item"
|
|
||||||
:class="{ active: resolution === item.value }"
|
|
||||||
@click="selectResolution(item.value)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #reference>
|
|
||||||
<div class="choice-btn">
|
|
||||||
<img src="@/assets/dialog/proportion.svg" alt="" style="width: 16px;">
|
|
||||||
<span>{{ proportion }}</span>
|
|
||||||
<div style="border: 0.5px solid #000; width: 1px; height:13px;margin: 0 5px;" />
|
|
||||||
<span>{{ resolution }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Popover from '@/components/Popover/index.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '1:1'
|
|
||||||
},
|
|
||||||
resolution: {
|
|
||||||
type: String,
|
|
||||||
default: '2k'
|
|
||||||
},
|
|
||||||
proportionOptions: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [
|
|
||||||
{ value: '21:9', label: '21:9' },
|
|
||||||
{ value: '16:9', label: '16:9' },
|
|
||||||
{ value: '4:3', label: '4:3' },
|
|
||||||
{ value: '1:1', label: '1:1' },
|
|
||||||
{ value: '3:4', label: '3:4' },
|
|
||||||
{ value: '9:16', label: '9:16' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
resolutionOptions: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [
|
|
||||||
{ value: '360', label: '流畅 360P' },
|
|
||||||
{ value: '540', label: '标清 540P' },
|
|
||||||
{ value: '720', label: '高清 720P' },
|
|
||||||
{ value: '1k', label: '超清 1K' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:resolution'])
|
|
||||||
|
|
||||||
const proportion = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const resolution = computed({
|
|
||||||
get: () => props.resolution,
|
|
||||||
set: (value) => emit('update:resolution', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectProportion = (value) => {
|
|
||||||
proportion.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectResolution = (value) => {
|
|
||||||
resolution.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const getProportionStyle = (value) => {
|
|
||||||
if (value === '智能') {
|
|
||||||
return {
|
|
||||||
'--width': '20px',
|
|
||||||
'--height': '20px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const [w, h] = value.split(':').map(Number)
|
|
||||||
const aspectRatio = w / h
|
|
||||||
const baseSize = 20
|
|
||||||
|
|
||||||
if (aspectRatio > 1) {
|
|
||||||
return {
|
|
||||||
'--width': `${baseSize}px`,
|
|
||||||
'--height': `${Math.round(baseSize / aspectRatio)}px`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'--width': `${Math.round(baseSize * aspectRatio)}px`,
|
|
||||||
'--height': `${baseSize}px`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.choice-btn{
|
|
||||||
display: flex;
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.choice-btn:hover{
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proportion-container{
|
|
||||||
padding: 20px;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section{
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 20px;
|
|
||||||
|
|
||||||
&:last-child{
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3{
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.proportion-options{
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 5px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
background-color: #F8F9FA;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proportion-item{
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: bottom;
|
|
||||||
color: #999;
|
|
||||||
|
|
||||||
&::before{
|
|
||||||
content: '';
|
|
||||||
width: var(--width, 20px);
|
|
||||||
height: var(--height, 20px);
|
|
||||||
background: #F5F6F7;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border: 2px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active{
|
|
||||||
color: #000F33;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
&.active::before{
|
|
||||||
border-color: #000F33;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resolution-options{
|
|
||||||
display: flex;
|
|
||||||
padding: 5px;
|
|
||||||
align-items: center;
|
|
||||||
align-self: stretch;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #F8F9FA;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resolution-item{
|
|
||||||
flex: 1;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
color: #666;
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active{
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Select
|
|
||||||
v-model="quantity"
|
|
||||||
:options="quantityOptions"
|
|
||||||
class="quantity-select"
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<img src="@/assets/dialog/quantity.svg" alt="" style="width: 16px;">
|
|
||||||
</template>
|
|
||||||
</Select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Select from '@/components/Select/index.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
max: {
|
|
||||||
type: Number,
|
|
||||||
default: 4
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const quantity = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const quantityOptions = computed(() =>
|
|
||||||
Array.from({ length: props.max }, (_, i) => ({ value: i + 1, label: `${i + 1} 张` }))
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.quantity-select {
|
|
||||||
:deep(.select-header) {
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #E8E9EB;
|
|
||||||
background: #f5f6f7;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e9eaeb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.select-text) {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
|
||||||
min-width: 80px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,330 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="video-image-uploader">
|
|
||||||
<div class="image-list">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in localPreviewList"
|
|
||||||
:key="item.uid"
|
|
||||||
class="image-item"
|
|
||||||
>
|
|
||||||
<img :src="item.url" class="uploaded-image" :alt="getFrameLabel(index)" />
|
|
||||||
<div class="frame-label">{{ getFrameLabel(index) }}</div>
|
|
||||||
<div class="delete-icon" @click.stop="handleDelete(index)">
|
|
||||||
<i-ep-close />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-for="i in maxImages" :key="i">
|
|
||||||
<div
|
|
||||||
v-if="localPreviewList.length < maxImages && !localPreviewList[i - 1]"
|
|
||||||
class="upload-trigger"
|
|
||||||
@click="triggerUpload(i - 1)"
|
|
||||||
>
|
|
||||||
<i-ep-plus color="#333333" />
|
|
||||||
<div class="upload-text">{{ getUploadText(i) }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<el-upload
|
|
||||||
v-for="i in maxImages"
|
|
||||||
:key="i"
|
|
||||||
:ref="el => setUploadRef(el, i - 1)"
|
|
||||||
v-show="false"
|
|
||||||
:action="uploadurl"
|
|
||||||
:limit="1"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
:on-success="(response, uploadFile) => handleSuccess(response, uploadFile, i - 1)"
|
|
||||||
:on-error="handleError"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { genFileId } from 'element-plus'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelType: {
|
|
||||||
type: String,
|
|
||||||
default: 'text'
|
|
||||||
},
|
|
||||||
imagesCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const uploadurl = import.meta.env.VITE_API_WORKFLOW_UPLOAD
|
|
||||||
const uploadRefs = ref([])
|
|
||||||
const imageList = ref([...props.modelValue])
|
|
||||||
const localPreviewList = ref([...props.modelValue])
|
|
||||||
const isUploading = ref(false)
|
|
||||||
|
|
||||||
const showEndFrame = computed(() => props.modelType === 'image' && props.imagesCount === 2)
|
|
||||||
const maxImages = computed(() => props.modelType === 'image' ? (showEndFrame.value ? 2 : 1) : 1)
|
|
||||||
|
|
||||||
const setUploadRef = (el, index) => {
|
|
||||||
if (el) {
|
|
||||||
uploadRefs.value[index] = el
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFrameLabel = (index) => {
|
|
||||||
if (props.modelType === 'digitalHuman') {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
return index === 0 ? '首帧' : '尾帧'
|
|
||||||
}
|
|
||||||
|
|
||||||
const getUploadText = (i) => {
|
|
||||||
if (props.modelType === 'digitalHuman') {
|
|
||||||
return '参考内容'
|
|
||||||
}
|
|
||||||
return i === 1 ? '首帧' : '尾帧'
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.modelValue, async (newVal) => {
|
|
||||||
if (isUploading.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
imageList.value = [...newVal]
|
|
||||||
|
|
||||||
const newPreviewList = []
|
|
||||||
for (const img of newVal) {
|
|
||||||
let previewImg = { ...img }
|
|
||||||
if (img.url && !img.url.startsWith('blob:')) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(img.url)
|
|
||||||
const blob = await response.blob()
|
|
||||||
previewImg = {
|
|
||||||
...img,
|
|
||||||
url: URL.createObjectURL(blob),
|
|
||||||
serverUrl: img.url
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create blob URL:', error)
|
|
||||||
previewImg = {
|
|
||||||
...img,
|
|
||||||
serverUrl: img.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newPreviewList.push(previewImg)
|
|
||||||
}
|
|
||||||
localPreviewList.value = newPreviewList
|
|
||||||
}, { deep: true, immediate: true })
|
|
||||||
|
|
||||||
const triggerUpload = (index) => {
|
|
||||||
if (uploadRefs.value[index]) {
|
|
||||||
uploadRefs.value[index].$el.querySelector('input').click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const beforeUpload = (rawFile) => {
|
|
||||||
const allowedTypes = ['image/jpeg', 'image/png']
|
|
||||||
|
|
||||||
if (!allowedTypes.includes(rawFile.type)) {
|
|
||||||
ElMessage.error('不支持的文件类型,请上传 JPG、PNG 格式的图片')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawFile.size / 1024 / 1024 > 10) {
|
|
||||||
ElMessage.error('图片大小不能超过 10MB')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSuccess = (response, uploadFile, index) => {
|
|
||||||
ElMessage.success('上传成功')
|
|
||||||
|
|
||||||
isUploading.value = true
|
|
||||||
|
|
||||||
const localUrl = URL.createObjectURL(uploadFile.raw)
|
|
||||||
|
|
||||||
const newImage = {
|
|
||||||
uid: uploadFile.uid,
|
|
||||||
url: response.url
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageList.value[index]) {
|
|
||||||
const previewItem = localPreviewList.value[index]
|
|
||||||
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
|
||||||
URL.revokeObjectURL(previewItem.url)
|
|
||||||
}
|
|
||||||
imageList.value[index] = newImage
|
|
||||||
localPreviewList.value[index] = {
|
|
||||||
uid: uploadFile.uid,
|
|
||||||
url: localUrl,
|
|
||||||
serverUrl: response.url
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
imageList.value[index] = newImage
|
|
||||||
localPreviewList.value[index] = {
|
|
||||||
uid: uploadFile.uid,
|
|
||||||
url: localUrl,
|
|
||||||
serverUrl: response.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit('update:modelValue', [...imageList.value])
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
isUploading.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleError = () => {
|
|
||||||
ElMessage.error('上传失败,请重新选择图片')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = (index) => {
|
|
||||||
const previewItem = localPreviewList.value[index]
|
|
||||||
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
|
||||||
URL.revokeObjectURL(previewItem.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
localPreviewList.value.splice(index, 1)
|
|
||||||
imageList.value.splice(index, 1)
|
|
||||||
emit('update:modelValue', [...imageList.value])
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelect = async (url, index = 0) => {
|
|
||||||
const initialFile = await fetch(url)
|
|
||||||
const blob = await initialFile.blob()
|
|
||||||
const file = new File([blob], `selected_image_${Date.now()}.jpg`, { type: blob.type })
|
|
||||||
file.uid = genFileId()
|
|
||||||
|
|
||||||
if (uploadRefs.value[index]) {
|
|
||||||
uploadRefs.value[index].handleStart(file)
|
|
||||||
uploadRefs.value[index].submit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
handleSelect
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.video-image-uploader {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-list {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-item {
|
|
||||||
position: relative;
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
.delete-icon {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploaded-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.frame-label {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 4px;
|
|
||||||
right: 4px;
|
|
||||||
width: 32px;
|
|
||||||
height: 20px;
|
|
||||||
background: rgba(0, 15, 51, 0.8);
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
z-index: 5;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -6px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background: #ef4444;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
z-index: 20;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #dc2626;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-trigger {
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
background: rgba(0, 15, 51, 0.04);
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #9ca3af;
|
|
||||||
font-size: 15px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: #333;
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #F0F1F2;
|
|
||||||
border-color: #626aef;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-text {
|
|
||||||
color: #666;
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import * as runninghub from './runninghub/index.js'
|
|
||||||
// import * as suno from './suno.js'
|
|
||||||
|
|
||||||
export default { runninghub }
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
import { fetchModelConfig } from '@/utils/modelConfig'
|
|
||||||
|
|
||||||
function getWidthHeight({ proportion, resolution }) {
|
|
||||||
let baseSize
|
|
||||||
switch (resolution) {
|
|
||||||
case '1k':
|
|
||||||
baseSize = 1024
|
|
||||||
break
|
|
||||||
case '2k':
|
|
||||||
baseSize = 2048
|
|
||||||
break
|
|
||||||
case '4k':
|
|
||||||
baseSize = 4096
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
baseSize = 2048
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proportion === '智能') {
|
|
||||||
return { width: baseSize, height: baseSize }
|
|
||||||
}
|
|
||||||
|
|
||||||
const [w, h] = String(proportion).split(':').map(Number)
|
|
||||||
const aspectRatio = w / h
|
|
||||||
|
|
||||||
if (aspectRatio > 1) {
|
|
||||||
return {
|
|
||||||
width: baseSize,
|
|
||||||
height: Math.round(baseSize / aspectRatio)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
height: baseSize,
|
|
||||||
width: Math.round(baseSize * aspectRatio)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function Playload(data) {
|
|
||||||
try {
|
|
||||||
const json = await fetchModelConfig(data.type, data.modelName, data.modelType)
|
|
||||||
|
|
||||||
const nodeInfoList = []
|
|
||||||
const proportionParam = data.params.find(param => param.name === 'proportion') || {data: 0}
|
|
||||||
const resolutionParam = data.params.find(param => param.name === 'resolution') || {data: 0}
|
|
||||||
|
|
||||||
if (Array.isArray(data.imgs) && data.imgs.length > 0 && (data.modelType === 'image' || data.modelType === 'edit')) {
|
|
||||||
for (const key of data.imgs) {
|
|
||||||
if (json.nodeInfoList[key.name]) {
|
|
||||||
console.log(key)
|
|
||||||
json.nodeInfoList[key.name].fieldValue = key.url
|
|
||||||
nodeInfoList.push(json.nodeInfoList[key.name])
|
|
||||||
}
|
|
||||||
if (json.imageIndex && json.imageIndex[key.name]) {
|
|
||||||
json.imageIndex[key.name].fieldValue = key.index
|
|
||||||
nodeInfoList.push(json.imageIndex[key.name])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (json.nodeInfoList.index) {
|
|
||||||
json.nodeInfoList.index.fieldValue = data.imgs.length - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(data.params)) {
|
|
||||||
for (const key of data.params) {
|
|
||||||
if (json.nodeInfoList[key.name]) {
|
|
||||||
console.log(key)
|
|
||||||
json.nodeInfoList[key.name].fieldValue = key.data
|
|
||||||
nodeInfoList.push(json.nodeInfoList[key.name])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((json.nodeInfoList.width || json.nodeInfoList.height) && (proportionParam.data && resolutionParam.data)) {
|
|
||||||
const { width, height } = getWidthHeight({
|
|
||||||
proportion: proportionParam.data,
|
|
||||||
resolution: resolutionParam.data
|
|
||||||
})
|
|
||||||
json.nodeInfoList.width.fieldValue = width
|
|
||||||
json.nodeInfoList.height.fieldValue = height
|
|
||||||
nodeInfoList.push(json.nodeInfoList.width, json.nodeInfoList.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(json.seed)) {
|
|
||||||
const min = Math.pow(10, 0)
|
|
||||||
const max = Math.pow(10, 9) - 1
|
|
||||||
const randomNum = Math.floor(Math.random() * (max - min + 1)) + min
|
|
||||||
json.seed.map((seedItem) => {
|
|
||||||
seedItem.fieldValue = randomNum
|
|
||||||
nodeInfoList.push(seedItem)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(json.must)) {
|
|
||||||
nodeInfoList.push(...json.must)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
workflowId: json.workflowId,
|
|
||||||
nodeInfoList
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取 JSON 文件失败:', error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function result(result) {
|
|
||||||
if (result.code === 0 && result.msg === 'success') {
|
|
||||||
return { type: true, url: result.data[0].fileUrl }
|
|
||||||
}
|
|
||||||
return { type: false, message: result.data.exception_message }
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { ref, reactive, markRaw } from 'vue'
|
import { ref, reactive, markRaw } from 'vue'
|
||||||
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
||||||
import { getModelConfig } from '@/config/models/index.js'
|
import { getModelConfig } from './models/index.js'
|
||||||
import PaintingModelSelector from './modelSelector.vue'
|
import PaintingModelSelector from './modelSelector.vue'
|
||||||
import PaintingProportion from './controls/proportion.vue'
|
import PaintingProportion from './controls/proportion.vue'
|
||||||
import DimensionInput from './controls/dimension.vue'
|
import DimensionInput from './controls/dimension.vue'
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Select from '@/components/Select/index.vue'
|
import Select from '@/components/Select/index.vue'
|
||||||
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
||||||
import { getModelConfig } from '@/config/models/index.js'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: 'Flux 2' },
|
modelValue: { type: String, default: 'Flux 2' },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user