- 和挑战列表同一种样式:一行一个主题,显示描述、“已有作品 / 还没开始”和更新时间 - 一个都没有时给 4 个主题点子,点了带着名称和描述打开新建框 - 删除改用页面内确认;新建框打开时光标在名称上 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
81e1b233ee
commit
6f0dd33de3
1 file changed
+198
-83
@@ -1,59 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="creation-home">
|
<main class="creation-home">
|
||||||
<n-flex
|
<header class="head">
|
||||||
justify="space-between"
|
|
||||||
align="flex-end"
|
|
||||||
style="margin-bottom: 32px"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<n-h2 style="margin: 0">自由创作</n-h2>
|
<h2>自由创作</h2>
|
||||||
<n-text depth="3">自己定一个主题,和 AI 一起把它做出来</n-text>
|
<p class="sub">自己定一个主题,和 AI 一起把它做出来。不用交,也不打分</p>
|
||||||
</div>
|
</div>
|
||||||
<n-button type="primary" @click="openCreate">
|
<n-button type="primary" @click="openCreate()">
|
||||||
<template #icon>
|
<template #icon><Icon icon="lucide:plus" /></template>
|
||||||
<Icon icon="lucide:plus" :width="16" />
|
|
||||||
</template>
|
|
||||||
新建主题
|
新建主题
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</header>
|
||||||
|
|
||||||
<n-spin :show="loading">
|
<n-spin :show="loading">
|
||||||
<n-empty
|
<div v-if="!loading && !creations.length" class="empty">
|
||||||
v-if="!loading && !creations.length"
|
<p>还没有创作。不知道做什么?从这些开始:</p>
|
||||||
description="还没有创作,点右上角新建一个主题吧"
|
<div class="ideas">
|
||||||
style="margin-top: 72px"
|
<button v-for="idea in IDEAS" :key="idea.title" class="idea" @click="openCreate(idea)">
|
||||||
/>
|
<b>{{ idea.title }}</b>
|
||||||
<div v-else class="card-grid">
|
<span>{{ idea.content }}</span>
|
||||||
<n-card
|
</button>
|
||||||
v-for="item in creations"
|
</div>
|
||||||
:key="item.display"
|
|
||||||
class="creation-card"
|
|
||||||
hoverable
|
|
||||||
@click="open(item)"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<n-ellipsis style="max-width: 100%">{{ item.title }}</n-ellipsis>
|
|
||||||
</template>
|
|
||||||
<template #header-extra>
|
|
||||||
<n-flex :size="4" align="center">
|
|
||||||
<n-tag v-if="item.has_code" type="success" size="small">
|
|
||||||
已有作品
|
|
||||||
</n-tag>
|
|
||||||
<n-button text @click.stop="remove(item)">
|
|
||||||
<Icon icon="lucide:trash-2" :width="15" />
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
<n-ellipsis :line-clamp="2" class="creation-desc">
|
|
||||||
{{ item.content || "还没写主题描述" }}
|
|
||||||
</n-ellipsis>
|
|
||||||
<template #footer>
|
|
||||||
<n-text depth="3" style="font-size: 12px">
|
|
||||||
更新于 {{ formatTime(item.modified) }}
|
|
||||||
</n-text>
|
|
||||||
</template>
|
|
||||||
</n-card>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ol v-else class="list">
|
||||||
|
<li v-for="item in creations" :key="item.display">
|
||||||
|
<router-link class="row" :to="{ name: 'creation', params: { display: item.display } }">
|
||||||
|
<span class="main">
|
||||||
|
<span class="title">{{ item.title }}</span>
|
||||||
|
<span class="desc">{{ item.content || "还没写主题描述" }}</span>
|
||||||
|
</span>
|
||||||
|
<span :class="['state', { done: item.has_code }]">
|
||||||
|
{{ item.has_code ? "已有作品" : "还没开始" }}
|
||||||
|
</span>
|
||||||
|
<span class="time">{{ formatTime(item.modified) }}</span>
|
||||||
|
<n-popconfirm @positive-click="remove(item)">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button text class="del" title="删除" @click.prevent.stop>
|
||||||
|
<Icon icon="lucide:trash-2" :width="15" />
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
删除「{{ item.title }}」?和它相关的对话也会一起删掉。
|
||||||
|
</n-popconfirm>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
</n-spin>
|
</n-spin>
|
||||||
|
|
||||||
<n-modal
|
<n-modal
|
||||||
@@ -65,6 +54,7 @@
|
|||||||
<n-form>
|
<n-form>
|
||||||
<n-form-item label="主题名称">
|
<n-form-item label="主题名称">
|
||||||
<n-input
|
<n-input
|
||||||
|
ref="titleRef"
|
||||||
v-model:value="form.title"
|
v-model:value="form.title"
|
||||||
placeholder="例如:我的番茄钟"
|
placeholder="例如:我的番茄钟"
|
||||||
maxlength="100"
|
maxlength="100"
|
||||||
@@ -98,22 +88,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, reactive, ref } from "vue"
|
import { nextTick, onMounted, reactive, ref } from "vue"
|
||||||
import { useRouter } from "vue-router"
|
import { useRouter } from "vue-router"
|
||||||
import { useDialog, useMessage } from "naive-ui"
|
import { useMessage } from "naive-ui"
|
||||||
import { Icon } from "../icons"
|
import { Icon } from "../icons"
|
||||||
import { Creation } from "../api"
|
import { Creation } from "../api"
|
||||||
import type { CreationSlim } from "../utils/type"
|
import type { CreationSlim } from "../utils/type"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialog = useDialog()
|
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const creating = ref(false)
|
const creating = ref(false)
|
||||||
const showCreate = ref(false)
|
const showCreate = ref(false)
|
||||||
const creations = ref<CreationSlim[]>([])
|
const creations = ref<CreationSlim[]>([])
|
||||||
const form = reactive({ title: "", content: "" })
|
const form = reactive({ title: "", content: "" })
|
||||||
|
const titleRef = ref<{ focus: () => void } | null>(null)
|
||||||
|
|
||||||
|
// 一个创作都没有时给几个主题点子,点了带进新建框
|
||||||
|
const IDEAS = [
|
||||||
|
{ title: "番茄钟", content: "25 分钟倒计时,能开始、暂停、重置,时间到了提醒我休息" },
|
||||||
|
{ title: "我的个人主页", content: "头像、自我介绍、爱好,再放几张喜欢的图片" },
|
||||||
|
{ title: "点击小游戏", content: "方块随机出现,点中加分,30 秒后显示得分" },
|
||||||
|
{ title: "今日待办", content: "能添加、勾选完成、删除待办,刷新后还在" },
|
||||||
|
]
|
||||||
|
|
||||||
function formatTime(value: string) {
|
function formatTime(value: string) {
|
||||||
return new Date(value).toLocaleString("zh-CN", {
|
return new Date(value).toLocaleString("zh-CN", {
|
||||||
@@ -124,14 +122,11 @@ function formatTime(value: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(item: CreationSlim) {
|
function openCreate(idea?: { title: string; content: string }) {
|
||||||
router.push({ name: "creation", params: { display: item.display } })
|
form.title = idea?.title ?? ""
|
||||||
}
|
form.content = idea?.content ?? ""
|
||||||
|
|
||||||
function openCreate() {
|
|
||||||
form.title = ""
|
|
||||||
form.content = ""
|
|
||||||
showCreate.value = true
|
showCreate.value = true
|
||||||
|
nextTick(() => titleRef.value?.focus())
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
@@ -149,24 +144,14 @@ async function submit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove(item: CreationSlim) {
|
async function remove(item: CreationSlim) {
|
||||||
dialog.warning({
|
try {
|
||||||
title: "删除主题",
|
await Creation.remove(item.display)
|
||||||
content: `确定删除「${item.title}」吗?和它相关的对话也会一起删除。`,
|
creations.value = creations.value.filter((c) => c.display !== item.display)
|
||||||
positiveText: "删除",
|
message.success("已删除")
|
||||||
negativeText: "取消",
|
} catch {
|
||||||
onPositiveClick: async () => {
|
message.error("删除失败,请重试")
|
||||||
try {
|
}
|
||||||
await Creation.remove(item.display)
|
|
||||||
creations.value = creations.value.filter(
|
|
||||||
(c) => c.display !== item.display,
|
|
||||||
)
|
|
||||||
message.success("已删除")
|
|
||||||
} catch {
|
|
||||||
message.error("删除失败,请重试")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -180,23 +165,153 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.creation-home {
|
.creation-home {
|
||||||
max-width: 1180px;
|
max-width: 880px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 32px 20px 48px;
|
padding: 28px 20px 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid {
|
.head {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creation-card {
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
color: var(--wp-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--wp-line);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--wp-surface);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list li + li {
|
||||||
|
border-top: 1px solid var(--wp-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 96px 24px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: var(--wp-ink);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:hover {
|
||||||
|
background: var(--wp-paper);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:focus-visible {
|
||||||
|
outline: 2px solid var(--wp-brand);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--wp-muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--wp-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.state.done {
|
||||||
|
color: var(--wp-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
font: 12px var(--wp-font-mono);
|
||||||
|
color: var(--wp-muted);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.del {
|
||||||
|
color: var(--wp-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.del:hover {
|
||||||
|
color: var(--wp-bad);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
padding: 28px 24px;
|
||||||
|
border: 1px dashed var(--wp-line);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--wp-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty p {
|
||||||
|
margin: 0 0 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--wp-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ideas {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idea {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid var(--wp-line);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--wp-paper);
|
||||||
|
color: var(--wp-ink);
|
||||||
|
font: inherit;
|
||||||
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creation-desc {
|
.idea:hover {
|
||||||
font-size: 13px;
|
border-color: var(--wp-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.idea b {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idea span {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
color: var(--wp-muted);
|
color: var(--wp-muted);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in new issue
Block a user