122 lines
4.9 KiB
Vue
122 lines
4.9 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { RouterLink } from 'vue-router'
|
|
import { NAlert, NButton, NInput, NTag } from 'naive-ui'
|
|
import { ArrowRight, RefreshCw, Search } from '@lucide/vue'
|
|
import { EmptyState } from '../../components/ui'
|
|
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
|
import { useQuery } from '../../composables/useQuery'
|
|
import { projectsApi } from '../projects/api'
|
|
|
|
/** 全局资产库只负责选择项目;素材读写仍严格使用后端的项目级接口。 */
|
|
const query = useQuery(ref('asset-projects'), (_, signal) => projectsApi.list(signal))
|
|
const search = ref('')
|
|
const projects = computed(() => {
|
|
const keyword = search.value.trim().toLowerCase()
|
|
return (query.data.value ?? []).filter(project =>
|
|
[project.title, project.topic, project.style].some(value => value?.toLowerCase().includes(keyword))
|
|
)
|
|
})
|
|
|
|
function projectName(title: string | null, topic: string) {
|
|
return title?.trim() || topic
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<WorkspacePage compact class="asset-library-page">
|
|
<template #header>
|
|
<div class="page-heading asset-library-heading">
|
|
<div>
|
|
<h1>资产库</h1>
|
|
<p class="page-description">按剧本管理人物、场景、道具和参考素材。</p>
|
|
</div>
|
|
<NButton
|
|
quaternary
|
|
class="icon-button"
|
|
:loading="query.loading.value"
|
|
aria-label="刷新资产项目"
|
|
title="刷新资产项目"
|
|
@click="query.refresh"
|
|
>
|
|
<template #icon><RefreshCw :size="16" /></template>
|
|
</NButton>
|
|
</div>
|
|
</template>
|
|
|
|
<NAlert v-if="query.error.value" type="error" :show-icon="false" class="mb-4">
|
|
{{ query.error.value }}<NButton text class="ml-3" @click="query.refresh">重新连接</NButton>
|
|
</NAlert>
|
|
|
|
<NInput
|
|
v-model:value="search"
|
|
clearable
|
|
class="asset-project-search"
|
|
placeholder="搜索剧本名称、主题或风格"
|
|
:input-props="{ 'aria-label': '搜索资产所属剧本' }"
|
|
>
|
|
<template #prefix><Search :size="15" /></template>
|
|
</NInput>
|
|
|
|
<div v-if="projects.length" class="asset-project-list">
|
|
<article v-for="project in projects" :key="project.id" class="asset-project-item">
|
|
<div class="min-w-0">
|
|
<div class="flex min-w-0 items-center gap-2">
|
|
<h2 class="truncate text-sm font-semibold" :title="projectName(project.title, project.topic)">
|
|
{{ projectName(project.title, project.topic) }}
|
|
</h2>
|
|
<NTag v-if="project.status !== 'completed'" size="small" :bordered="false">剧本未完成</NTag>
|
|
</div>
|
|
<p class="mt-1 truncate text-xs text-muted">{{ project.topic }}</p>
|
|
</div>
|
|
<RouterLink
|
|
v-if="project.status === 'completed'"
|
|
v-slot="{ href, navigate }"
|
|
:to="`/projects/${encodeURIComponent(project.id)}/assets`"
|
|
custom
|
|
>
|
|
<NButton tag="a" :href="href" text type="primary" @click="navigate">
|
|
管理素材<template #icon><ArrowRight :size="15" /></template>
|
|
</NButton>
|
|
</RouterLink>
|
|
<NButton v-else text disabled
|
|
>管理素材<template #icon><ArrowRight :size="15" /></template
|
|
></NButton>
|
|
</article>
|
|
</div>
|
|
|
|
<EmptyState
|
|
v-else-if="query.data.value"
|
|
:title="search ? '没有匹配的剧本' : '还没有可管理的剧本'"
|
|
:description="search ? '请调整搜索关键词。' : '创建剧本后,可在这里进入对应的项目资产库。'"
|
|
>
|
|
<NButton v-if="search" @click="search = ''">清除搜索</NButton>
|
|
<RouterLink v-else v-slot="{ href, navigate }" to="/projects" custom>
|
|
<NButton tag="a" :href="href" type="primary" @click="navigate">前往我的剧本</NButton>
|
|
</RouterLink>
|
|
</EmptyState>
|
|
<p v-else class="py-10 text-center text-sm text-muted" role="status">正在读取剧本列表…</p>
|
|
</WorkspacePage>
|
|
</template>
|
|
|
|
<style scoped>
|
|
@reference "../../styles/styles.css";
|
|
.asset-library-heading {
|
|
@apply flex items-start justify-between gap-4;
|
|
}
|
|
.asset-project-search {
|
|
@apply max-w-[420px] mb-4;
|
|
}
|
|
.asset-project-list {
|
|
@apply grid gap-2;
|
|
}
|
|
.asset-project-item {
|
|
@apply flex min-w-0 items-center justify-between gap-5 px-4 py-3 bg-(--app-surface);
|
|
}
|
|
@media (max-width: 640px) {
|
|
.asset-project-item {
|
|
@apply items-start gap-3;
|
|
}
|
|
}
|
|
</style>
|