feat: 同步视觉风格与主体身份母版工作区
对齐后端 dev 059e597,新增项目风格编辑锁定、身份文本生成、身份参考图与母版切换。 整理六个工作区入口,接通人物及场景母版继承说明、形态图片来源追溯。 补充草稿保护、正式 ID 校验、费用确认及部分失败回执,67 项测试和静态检查、构建通过。 未修改后端,未调用真实模型;本环境未完成浏览器视觉验收。
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { AssetImage } from '../../../components/ui'
|
||||
import { referenceImageUrl } from '../../../lib/assets'
|
||||
import type { AddStyleImageInput, StyleCategory, VisualStyleImage } from '../types'
|
||||
|
||||
/** 风格图只登记已有地址;不伪造文件上传或风格生图功能。 */
|
||||
const props = defineProps<{ images: VisualStyleImage[]; disabled: boolean }>()
|
||||
const emit = defineEmits<{
|
||||
add: [input: AddStyleImageInput]
|
||||
toggle: [image: VisualStyleImage]
|
||||
remove: [image: VisualStyleImage]
|
||||
}>()
|
||||
const form = reactive({ imageUrl: '', category: 'overall' as StyleCategory, sortOrder: 0, enabled: true })
|
||||
const confirmingId = ref('')
|
||||
const categories = { overall: '整体', character: '人物', scene: '场景', prop: '道具' } as const
|
||||
const valid = computed(() => !!referenceImageUrl(form.imageUrl) && Number.isSafeInteger(form.sortOrder))
|
||||
|
||||
/** 仅通过校验后发出新增请求,输入框在失败时保留供修正。 */
|
||||
function add() {
|
||||
if (props.disabled || !valid.value) return
|
||||
emit('add', { ...form, imageUrl: form.imageUrl.trim(), source: 'upload' })
|
||||
}
|
||||
|
||||
/** 删除只移除记录,需确认且不声称删除远程文件。 */
|
||||
function remove(image: VisualStyleImage) {
|
||||
if (props.disabled || confirmingId.value !== image.id) return
|
||||
confirmingId.value = ''
|
||||
emit('remove', image)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="panel mt-5 p-5 sm:p-6" aria-label="风格参考图">
|
||||
<h3 class="font-medium">
|
||||
风格参考图 <span class="ml-2 text-xs text-muted">{{ images.length }} 张</span>
|
||||
</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
登记已有 HTTP(S) 或 /storage/
|
||||
图片地址。后端尚无文件上传接口;这些图片目前仅管理记录,现有身份/形态生图不会自动将风格图片传给模型。
|
||||
</p>
|
||||
<form class="mt-4" @submit.prevent="add">
|
||||
<fieldset :disabled="disabled" class="flex flex-wrap items-end gap-3">
|
||||
<label class="min-w-48 flex-1"
|
||||
><span class="field-label">图片地址</span
|
||||
><input
|
||||
v-model="form.imageUrl"
|
||||
class="input"
|
||||
placeholder="https://… 或 /storage/…"
|
||||
aria-label="风格图片地址"
|
||||
/></label>
|
||||
<label
|
||||
><span class="field-label">分类</span
|
||||
><select v-model="form.category" class="input">
|
||||
<option v-for="(label, value) in categories" :key="value" :value="value">{{ label }}</option>
|
||||
</select></label
|
||||
>
|
||||
<label class="w-24"
|
||||
><span class="field-label">排序</span
|
||||
><input
|
||||
v-model.number="form.sortOrder"
|
||||
class="input"
|
||||
type="number"
|
||||
step="1"
|
||||
aria-label="风格图片排序"
|
||||
/></label>
|
||||
<label class="flex gap-2 pb-2 text-xs"><input v-model="form.enabled" type="checkbox" />启用</label>
|
||||
<button class="button button-secondary" type="submit" :disabled="disabled || !valid">登记参考图</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
<p v-if="form.imageUrl && !valid" class="mt-2 text-xs text-danger" role="alert">
|
||||
请填写有效图片地址和整数排序。
|
||||
</p>
|
||||
<div v-if="images.length" class="mt-5 grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<article v-for="image in images" :key="image.id" class="min-w-0 rounded border border-line p-3">
|
||||
<AssetImage
|
||||
:src="image.imageUrl"
|
||||
:alt="`${categories[image.category]}风格参考图`"
|
||||
class="aspect-square"
|
||||
/>
|
||||
<p class="mt-3 text-xs">
|
||||
{{ categories[image.category] }} · {{ image.enabled ? '已启用' : '已停用' }} · 排序
|
||||
{{ image.sortOrder }}
|
||||
</p>
|
||||
<p v-if="image.provider || image.model" class="mt-2 break-words text-[11px] text-muted">
|
||||
{{ image.provider }} {{ image.model }}
|
||||
</p>
|
||||
<details v-if="image.prompt" class="mt-2 text-xs">
|
||||
<summary>实际提示词</summary>
|
||||
<p class="mt-2 whitespace-pre-wrap">{{ image.prompt }}</p>
|
||||
</details>
|
||||
<div class="mt-3 flex flex-wrap gap-3">
|
||||
<a
|
||||
v-if="referenceImageUrl(image.imageUrl)"
|
||||
:href="referenceImageUrl(image.imageUrl)!"
|
||||
class="text-button"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>原图</a
|
||||
>
|
||||
<button class="text-button" :disabled="disabled" @click="emit('toggle', image)">
|
||||
{{ image.enabled ? '停用' : '启用' }}
|
||||
</button>
|
||||
<button class="text-button text-danger" :disabled="disabled" @click="confirmingId = image.id">
|
||||
移除
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="confirmingId === image.id" class="mt-3 text-xs">
|
||||
<p>仅移除参考图记录,不删除远程文件。</p>
|
||||
<div class="mt-2 flex gap-3">
|
||||
<button class="text-button text-danger" :disabled="disabled" @click="remove(image)">
|
||||
确认移除</button
|
||||
><button class="text-button" @click="confirmingId = ''">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<p v-else class="mt-5 text-sm text-muted">尚无风格参考图。先保存视觉风格,再登记图片。</p>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user