阶段五完成

This commit is contained in:
tianmo committed 2026-08-28 17:50:23 +08:00
1 parent 4e2e123269
commit f2fe653c04
6 files changed
+1423 -60

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
<template>
<div class="image-card">
<div class="imgrow">
<button class="img-thumb" title="选择本地图片" @click="$emit('pick', resource.id)">
<img v-if="resource.previewUrl" :src="resource.previewUrl" :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>
</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>
</div>
</div>
<div class="thumb-note">{{ displayFileName }}</div>
<div class="resource-weight-row">
<NumSlider
class="resource-weight-control"
label="占比%"
:min="0"
:max="100"
:step="1"
:model-value="resource.weight"
:disabled="resource.locked || resourceCount === 1"
@update:model-value="$emit('updateWeight', resource.id, $event)"
/>
<button
class="weight-lock"
:class="{ on: resource.locked }"
:disabled="resourceCount === 1"
:title="resource.locked ? '解除占比锁定' : '锁定当前占比'"
@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>
<svg v-else viewBox="0 0 24 24" aria-hidden="true"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M16 10V7a4 4 0 0 0-7.5-2"/></svg>
</button>
</div>
<slot />
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import NumSlider from './NumSlider.vue'
type CommonImageResource = {
id: number
previewUrl: string
textureName: string
textureFolder?: string
weight: number
locked: boolean
}
const props = withDefaults(defineProps<{
resource: CommonImageResource
resourceCount: number
defaultFile: string
namePlaceholder?: string
showFolder?: boolean
}>(), {
namePlaceholder: 'particle',
showFolder: true,
})
defineEmits<{
pick: [id: number]
reset: [id: number]
remove: [id: number]
updateWeight: [id: number, value: number]
toggleLock: [id: number]
}>()
const displayFileName = computed(() => {
const name = props.resource.textureName || props.defaultFile.replace(/\.[^.]+$/, '')
return name.includes('.') ? name : `${name}.png`
})
</script>
<style scoped>
.image-card { margin: 8px 0; padding: 9px; border: 1px solid #2e3c55; border-radius: 7px; background: #171d2a; }
.imgrow { display: flex; align-items: flex-start; gap: 8px; }
.img-thumb { width: 48px; height: 48px; padding: 0; border: 1px solid #3a4963; border-radius: 6px; background: #1a2232; display: flex; align-items: center; justify-content: center; flex-shrink: 0; cursor: pointer; overflow: hidden; }
.img-thumb:hover { border-color: #7774ff; }
.img-thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.img-fields { flex: 1; min-width: 0; }
.img-actions { display: flex; flex-direction: column; gap: 4px; }
.row { display: flex; align-items: center; gap: 8px; margin: 6px 0; color: #aab; font-size: 12px; }
.row > span:first-child { width: 60px; flex-shrink: 0; color: #8a93bb; }
.inp { min-width: 0; flex: 1; padding: 3px 5px; border: 1px solid #2e2e44; border-radius: 5px; background: #1a1a28; color: #dde; font-size: 12px; }
.mini-btn2 { width: 26px; height: 24px; background: #1e1e2e; border: 1px solid #2e2e44; border-radius: 5px; color: #aab; font-size: 12px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.mini-btn2:hover { background: #2b3a5c; }
.mini-btn2:disabled { opacity: 0.35; cursor: not-allowed; }
.thumb-note { margin: 4px 0 2px; color: #667; font-size: 11px; }
.resource-weight-row { display: grid; grid-template-columns: minmax(0, 1fr) 28px; align-items: center; gap: 7px; }
.resource-weight-control { width: 100%; min-width: 0; }
.resource-weight-control :deep(.ns-range) { min-width: 0; }
.resource-weight-control :deep(.ns-num) { flex: 0 0 56px; box-sizing: border-box; }
.weight-lock { width: 28px; height: 26px; flex: 0 0 28px; padding: 5px; border: 1px solid #35435c; border-radius: 5px; background: #202a3b; color: #78859e; cursor: pointer; }
.weight-lock svg { display: block; width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.weight-lock:hover:not(:disabled) { border-color: #7774ff; color: #b8b6ff; }
.weight-lock.on { border-color: #7774ff; background: #514fd0; color: #fff; }
.weight-lock:disabled { opacity: 0.35; cursor: not-allowed; }
</style>