feat: 实现剧本创作与拆解前端工作台
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { EmptyState } from './ui'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page-container">
|
||||
<EmptyState title="这个页面不存在" description="请检查地址,或回到项目列表继续创作。"
|
||||
><RouterLink to="/projects" class="button button-primary">返回我的剧本</RouterLink></EmptyState
|
||||
>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DialogRoot,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogClose
|
||||
} from 'reka-ui'
|
||||
import { X } from '@lucide/vue'
|
||||
|
||||
/** 统一弹窗容器,Reka UI 负责焦点锁定、Escape 和无障碍语义。 */
|
||||
defineProps<{ title: string; description: string; busy?: boolean }>()
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot v-model:open="open">
|
||||
<slot name="trigger"></slot>
|
||||
<DialogPortal>
|
||||
<DialogOverlay class="dialog-overlay" />
|
||||
<DialogContent
|
||||
class="dialog-content"
|
||||
@interact-outside="busy && $event.preventDefault()"
|
||||
@escape-key-down="busy && $event.preventDefault()"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<DialogTitle class="text-xl font-semibold">{{ title }}</DialogTitle
|
||||
><DialogDescription class="mt-2 text-sm leading-6 text-muted">{{
|
||||
description
|
||||
}}</DialogDescription>
|
||||
</div>
|
||||
<DialogClose class="icon-button shrink-0" aria-label="关闭弹窗" :disabled="busy"
|
||||
><X :size="18"
|
||||
/></DialogClose>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { FileText } from '@lucide/vue'
|
||||
|
||||
/** 空数据与等待生成共用的轻量占位,不放入演示数据。 */
|
||||
defineProps<{ title: string; description: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="empty-state">
|
||||
<FileText :size="28" :stroke-width="1.4" class="mb-4 text-muted" />
|
||||
<h3 class="font-medium">{{ title }}</h3>
|
||||
<p class="mt-2 max-w-md text-sm leading-6 text-muted">{{ description }}</p>
|
||||
<div class="mt-5"><slot></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { statusLabels } from '../../lib/format'
|
||||
import type { ProjectStatus } from '../../features/projects/types'
|
||||
|
||||
/** 状态同时提供文字和颜色,避免只依赖色觉。 */
|
||||
defineProps<{ status: string; label?: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="status-badge" :data-status="status">
|
||||
<span class="status-dot" aria-hidden="true"></span
|
||||
>{{ label || statusLabels[status as ProjectStatus] || status }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,4 @@
|
||||
/** 公共 UI 的统一导出入口。 */
|
||||
export { default as AppDialog } from './AppDialog.vue'
|
||||
export { default as EmptyState } from './EmptyState.vue'
|
||||
export { default as StatusBadge } from './StatusBadge.vue'
|
||||
Reference in New Issue
Block a user