中英双语

This commit is contained in:
tianmo committed 2026-10-22 18:13:43 +08:00
1 parent ea26af26ee
commit b98c0333e6
50 files changed
+4082 -2049

No files matched your search

+40 -36
View File
@@ -1,43 +1,43 @@
<template>
<div class="image-card">
<div class="imgrow">
<button class="img-thumb" :title="supportsSequence && resource.imageMode === 'sequence' ? '选择序列帧图片' : '选择本地图片'" @click="pickImage">
<button class="img-thumb" :title="supportsSequence && resource.imageMode === 'sequence' ? $t('image.chooseSequence') : $t('image.chooseLocal')" @click="pickImage">
<img v-if="displayPreviewUrl" :src="displayPreviewUrl" :alt="resource.textureName" />
<svg v-else viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="#8a93bb" stroke-width="1.6"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.6"/><path d="M21 15l-5-5-9 9"/></svg>
</button>
<div class="img-fields">
<label v-if="showFolder" class="row"><span>子文件夹</span><input v-model="resource.textureFolder" type="text" class="inp" placeholder="subfolder/" /></label>
<label class="row"><span>名称</span><input v-model="resource.textureName" type="text" class="inp" :placeholder="namePlaceholder" /></label>
<label v-if="showFolder" class="row"><span>{{ $t('image.subfolder') }}</span><input v-model="resource.textureFolder" type="text" class="inp" placeholder="subfolder/" /></label>
<label class="row"><span>{{ $t('image.name') }}</span><input v-model="resource.textureName" type="text" class="inp" :placeholder="namePlaceholder" /></label>
</div>
<div class="img-actions">
<button class="mini-btn2" :title="`恢复 ${defaultFile}`" @click="$emit('reset', resource.id)">⟳</button>
<button class="mini-btn2" title="删除图片资源" :disabled="resourceCount === 1" @click="$emit('remove', resource.id)">🗑</button>
<button class="mini-btn2" :title="$t('image.restore', { name: defaultFile })" @click="$emit('reset', resource.id)">⟳</button>
<button class="mini-btn2" :title="$t('image.deleteResource')" :disabled="resourceCount === 1" @click="$emit('remove', resource.id)">🗑</button>
</div>
</div>
<div class="thumb-note">{{ displayFileName }}</div>
<label v-if="supportsSequence" class="row"><span>图片模式</span>
<label v-if="supportsSequence" class="row"><span>{{ $t('image.imageMode') }}</span>
<select v-model="resource.imageMode" class="inp">
<option value="fixed">固定</option>
<option value="sequence">序列帧</option>
<option value="fixed">{{ $t('image.fixed') }}</option>
<option value="sequence">{{ $t('image.sequence') }}</option>
</select>
</label>
<template v-if="supportsSequence && resource.imageMode === 'sequence'">
<label class="row"><span>播放模式</span>
<label class="row"><span>{{ $t('image.playbackMode') }}</span>
<select v-model="resource.sequencePlayback" class="inp">
<option value="once-hide">单次-尾帧消失</option>
<option value="once-hold">单次-尾帧固定</option>
<option value="loop-forward">正向循环</option>
<option value="loop-reverse">反向循环</option>
<option value="once-hide">{{ $t('image.onceHide') }}</option>
<option value="once-hold">{{ $t('image.onceHold') }}</option>
<option value="loop-forward">{{ $t('image.loopForward') }}</option>
<option value="loop-reverse">{{ $t('image.loopReverse') }}</option>
</select>
</label>
<label class="row"><span>播放帧率</span><input v-model.number="resource.sequenceFps" type="number" min="1" max="120" step="1" class="inp" /></label>
<button class="sequence-pick" @click="$emit('pickSequence', resource.id)">选择序列帧(多选)</button>
<label class="row"><span>{{ $t('image.playbackFps') }}</span><input v-model.number="resource.sequenceFps" type="number" min="1" max="120" step="1" class="inp" /></label>
<button class="sequence-pick" @click="$emit('pickSequence', resource.id)">{{ $t('image.chooseSequenceMulti') }}</button>
<div class="sequence-note">{{ sequenceOrderText }}</div>
</template>
<div class="resource-weight-row">
<NumSlider
class="resource-weight-control"
label="占比%"
:label="$t('image.weight')"
:min="0"
:max="100"
:step="1"
@@ -49,7 +49,7 @@
class="weight-lock"
:class="{ on: resource.locked }"
:disabled="resourceCount === 1"
:title="resource.locked ? '解除占比锁定' : '锁定当前占比'"
:title="resource.locked ? $t('image.unlockWeight') : $t('image.lockWeight')"
@click="$emit('toggleLock', resource.id)"
>
<svg v-if="resource.locked" viewBox="0 0 24 24" aria-hidden="true"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg>
@@ -60,10 +60,10 @@
<div v-if="supportsPivot" class="pivot-toggle-row">
<div class="pivot-toggle-icon">◎</div>
<div class="pivot-toggle-text">
<div class="pivot-toggle-title">贴图轴点</div>
<div class="pivot-toggle-title">{{ $t('image.pivot') }}</div>
<div class="pivot-toggle-sub" :title="pivotSummary">{{ pivotSummary }}</div>
</div>
<label class="pivot-switch" :title="pivotOpen ? '收起轴点调整' : '展开轴点调整'">
<label class="pivot-switch" :title="pivotOpen ? $t('image.collapsePivot') : $t('image.expandPivot')">
<input v-model="pivotOpen" type="checkbox" @change="onPivotToggle" />
<span class="pivot-switch-ui"></span>
</label>
@@ -81,19 +81,19 @@
<Teleport to="body">
<div v-if="pivotFullscreen" class="pivot-modal-backdrop" @pointerdown.self="closePivotFullscreen(false)">
<div class="pivot-modal" role="dialog" aria-modal="true" aria-label="贴图旋转轴点大图精修">
<div class="pivot-modal" role="dialog" aria-modal="true" :aria-label="$t('image.pivotModal')">
<div class="pivot-modal-head">
<div class="pivot-modal-icon">◎</div>
<div class="pivot-modal-info">
<div class="pivot-modal-title">
贴图旋转轴点大图精修
{{ $t('image.pivotModal') }}
<span class="pivot-modal-badge">{{ resource.textureName || defaultFile }}</span>
</div>
<div class="pivot-modal-desc">在下方贴图上任意点击或拖拽,精确设置粒子的旋转/摆动中心</div>
<div class="pivot-modal-desc">{{ $t('image.pivotModalDesc') }}</div>
</div>
<button type="button" class="pivot-modal-close" title="关闭" @click="closePivotFullscreen(false)">✕</button>
<button type="button" class="pivot-modal-close" :title="$t('common.close')" @click="closePivotFullscreen(false)">✕</button>
</div>
<div class="pivot-modal-hint">支持 100% ~ 400% 放大,精确对准尖端或根部:</div>
<div class="pivot-modal-hint">{{ $t('image.pivotZoomHint') }}</div>
<PivotEditor
large
:preview-url="displayPreviewUrl"
@@ -103,8 +103,8 @@
@update:anchorY="draftY = $event"
/>
<div class="pivot-modal-actions">
<button type="button" class="pivot-modal-cancel" @click="closePivotFullscreen(false)">取消</button>
<button type="button" class="pivot-modal-confirm" @click="closePivotFullscreen(true)">确定并保存轴点</button>
<button type="button" class="pivot-modal-cancel" @click="closePivotFullscreen(false)">{{ $t('common.cancel') }}</button>
<button type="button" class="pivot-modal-confirm" @click="closePivotFullscreen(true)">{{ $t('image.confirmPivot') }}</button>
</div>
</div>
</div>
@@ -113,6 +113,7 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import NumSlider from './NumSlider.vue'
import PivotEditor from './PivotEditor.vue'
@@ -154,14 +155,17 @@ const emit = defineEmits<{
updateWeight: [id: number, value: number]
toggleLock: [id: number]
}>()
const { t } = useI18n()
const displayFileName = computed(() => {
if (props.resource.imageMode === 'sequence') {
const frames = props.resource.sequenceFrames || []
if (!frames.length) return '未选择序列帧'
const first = frames[0]?.name || '第 1 帧'
if (!frames.length) return t('image.noSequence')
const first = frames[0]?.name || t('image.firstFrame')
const last = frames[frames.length - 1]?.name || first
return frames.length === 1 ? `1 帧 · ${first}` : `${frames.length} 帧 · ${first} → ${last}`
return frames.length === 1
? t('image.frameCountOne', { name: first })
: t('image.frameCountMany', { count: frames.length, first, last })
}
const name = props.resource.textureName || props.defaultFile.replace(/\.[^.]+$/, '')
return name.includes('.') ? name : `${name}.png`
@@ -207,9 +211,9 @@ const draftY = ref(0.5)
const pivotSummary = computed(() => {
const x = ((props.resource.anchorX ?? 0.5) * 100).toFixed(1)
const y = ((props.resource.anchorY ?? 0.5) * 100).toFixed(1)
if (pivotOpen.value) return `已开启:X ${x}% | Y ${y}%`
if (pivotCentered.value) return '未开启,默认围绕图片正中心 (50%, 50%)'
return `轴点已自定义:X ${x}% | Y ${y}%`
if (pivotOpen.value) return t('image.pivotOn', { x, y })
if (pivotCentered.value) return t('image.pivotOff')
return t('image.pivotCustom', { x, y })
})
function openPivotFullscreen() {
@@ -243,10 +247,10 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
const sequenceOrderText = computed(() => {
const frames = props.resource.sequenceFrames || []
if (!frames.length) return '未选择序列帧'
const first = frames[0]?.name || '第一张图片'
const last = frames[frames.length - 1]?.name || '最后一张图片'
return `已选择 ${frames.length} 帧,按 ${first}-${last} 顺序播放`
if (!frames.length) return t('image.noSequence')
const first = frames[0]?.name || t('image.firstImage')
const last = frames[frames.length - 1]?.name || t('image.lastImage')
return t('image.sequenceSelected', { count: frames.length, first, last })
})
function pickImage() {