阶段五完成
This commit is contained in:
1 parent
4e2e123269
commit
f2fe653c04
6 files changed
+1423
-60
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user