功能完善

This commit is contained in:
2026-07-10 11:53:18 +08:00
parent 9f659b371a
commit dd846c82dc
6 changed files with 312 additions and 15 deletions

View File

@@ -34,5 +34,11 @@
"userTurns": 1,
"basisHash": "e0e08f907071b904",
"updatedAt": 1783654440818
},
"topic_20260710-034033_6432ee6566f7b432": {
"stage": 1,
"userTurns": 1,
"basisHash": "6b126156c40b1534",
"updatedAt": 1783655064287
}
}

View File

@@ -5,5 +5,7 @@
"topic_20260706-153317_238f65a7a470703c": 1783351997029,
"topic_20260710-024855_5c4b1833149d66bb": 1783651735446,
"topic_20260710-030936_cedad2907c3954fe": 1783652976310,
"topic_20260710-032907_600adc6b00791607": 1783654147796
"topic_20260710-032907_600adc6b00791607": 1783654147796,
"topic_20260710-034033_6432ee6566f7b432": 1783654833341,
"topic_20260710-034724_3c6fb29d8d0a4119": 1783655244536
}

View File

@@ -43,5 +43,7 @@
"topic_20260708-095428_990617acd4168b40": "auto",
"topic_20260710-024855_5c4b1833149d66bb": "auto",
"topic_20260710-030936_cedad2907c3954fe": "auto",
"topic_20260710-032907_600adc6b00791607": "auto"
"topic_20260710-032907_600adc6b00791607": "auto",
"topic_20260710-034033_6432ee6566f7b432": "auto",
"topic_20260710-034724_3c6fb29d8d0a4119": "auto"
}

View File

@@ -43,5 +43,7 @@
"topic_20260708-095428_990617acd4168b40": "帮我检查一下这个应用在进行放大缩小的…",
"topic_20260710-024855_5c4b1833149d66bb": "帮我给右侧的目录树添加一个功能,当用…",
"topic_20260710-030936_cedad2907c3954fe": "鼠标放在tree上的时候只有hov…",
"topic_20260710-032907_600adc6b00791607": "目录树中显示的文件夹markdow…"
"topic_20260710-032907_600adc6b00791607": "目录树中显示的文件夹markdow…",
"topic_20260710-034033_6432ee6566f7b432": "我在点击目录树的时候新增文件夹和新建…",
"topic_20260710-034724_3c6fb29d8d0a4119": "新的会话"
}

View File

@@ -0,0 +1,240 @@
<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
const props = withDefaults(
defineProps<{
show: boolean;
title: string;
message?: string;
placeholder?: string;
modelValue?: string;
confirmText?: string;
cancelText?: string;
}>(),
{
message: "",
placeholder: "",
modelValue: "",
confirmText: "确认",
cancelText: "取消",
},
);
const emit = defineEmits<{
confirm: [value: string];
cancel: [];
}>();
const inputValue = ref(props.modelValue);
const inputRef = ref<HTMLInputElement | null>(null);
// Reset and auto-focus when dialog opens
watch(
() => props.show,
async (visible) => {
if (visible) {
inputValue.value = props.modelValue;
await nextTick();
inputRef.value?.focus();
inputRef.value?.select();
}
},
);
function onSubmit() {
const trimmed = inputValue.value.trim();
if (trimmed) {
emit("confirm", trimmed);
}
}
function onKeydown(e: KeyboardEvent) {
if (e.key === "Enter") {
e.preventDefault();
onSubmit();
} else if (e.key === "Escape") {
emit("cancel");
}
}
</script>
<template>
<Teleport to="body">
<div
v-if="show"
class="prompt-overlay"
role="presentation"
@click.self="emit('cancel')"
>
<section
class="prompt-shell"
role="dialog"
aria-modal="true"
:aria-label="title"
>
<header class="prompt-header">
<h2>{{ title }}</h2>
</header>
<div class="prompt-body">
<p v-if="message" class="prompt-message">{{ message }}</p>
<input
ref="inputRef"
v-model="inputValue"
class="prompt-input"
type="text"
:placeholder="placeholder"
@keydown="onKeydown"
/>
</div>
<footer class="prompt-footer">
<button
class="prompt-btn prompt-btn-cancel"
type="button"
@click="emit('cancel')"
>
{{ cancelText }}
</button>
<button
class="prompt-btn prompt-btn-primary"
type="button"
:disabled="!inputValue.trim()"
@click="onSubmit"
>
{{ confirmText }}
</button>
</footer>
</section>
</div>
</Teleport>
</template>
<style scoped>
.prompt-overlay {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
padding: 28px;
background: var(--app-overlay);
backdrop-filter: blur(8px);
}
.prompt-shell {
width: min(380px, calc(100vw - 48px));
overflow: hidden;
border: 1px solid var(--app-border-strong);
border-radius: 12px;
background: var(--app-surface);
box-shadow:
0 24px 70px var(--app-shadow),
0 2px 10px color-mix(in srgb, var(--app-shadow) 46%, transparent);
}
.prompt-header {
display: flex;
align-items: center;
min-height: 48px;
padding: 0 20px;
border-bottom: 1px solid var(--app-border);
}
.prompt-header h2 {
margin: 0;
color: var(--app-text);
font-size: 14px;
font-weight: 700;
}
.prompt-body {
padding: 20px;
}
.prompt-message {
margin: 0 0 14px;
color: var(--app-text-soft);
font-size: 13px;
line-height: 1.6;
}
.prompt-input {
display: block;
width: 100%;
height: 36px;
padding: 0 12px;
border: 1px solid var(--app-border-strong);
border-radius: 8px;
background: var(--app-surface-muted);
color: var(--app-text);
font-size: 14px;
font-family: inherit;
outline: none;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
box-sizing: border-box;
}
.prompt-input::placeholder {
color: var(--app-subtle);
}
.prompt-input:focus {
border-color: var(--app-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-accent) 12%, transparent);
}
.prompt-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 20px;
border-top: 1px solid var(--app-border);
background: var(--app-panel-bg);
}
.prompt-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 72px;
height: 32px;
padding: 0 16px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition:
background-color 0.15s ease,
color 0.15s ease,
opacity 0.15s ease;
}
.prompt-btn-cancel {
background: var(--app-hover);
color: var(--app-text-soft);
}
.prompt-btn-cancel:hover {
background: var(--app-border);
color: var(--app-text);
}
.prompt-btn-primary {
background: var(--app-accent);
color: #fff;
}
.prompt-btn-primary:hover:not(:disabled) {
opacity: 0.88;
}
.prompt-btn-primary:disabled {
opacity: 0.45;
cursor: not-allowed;
}
</style>

View File

@@ -6,6 +6,7 @@ export default { name: "TreeEntry" };
<script setup lang="ts">
import { computed, ref, watch, onMounted, onUnmounted } from "vue";
import ConfirmDialog from "./ConfirmDialog.vue";
import PromptDialog from "./PromptDialog.vue";
interface DirEntry {
name: string;
@@ -112,29 +113,62 @@ onUnmounted(() => {
document.removeEventListener("click", onDocumentClick);
});
// ── Prompt dialog state ──────────────────────────────────────
const promptShow = ref(false);
const promptTitle = ref("");
const promptMessage = ref("");
const promptPlaceholder = ref("");
const promptDefaultValue = ref("");
let promptCallback: ((value: string) => void) | null = null;
function openPrompt(
title: string,
message: string,
placeholder: string,
defaultValue: string,
cb: (value: string) => void,
) {
promptTitle.value = title;
promptMessage.value = message;
promptPlaceholder.value = placeholder;
promptDefaultValue.value = defaultValue;
promptCallback = cb;
promptShow.value = true;
}
function onPromptConfirm(value: string) {
promptShow.value = false;
promptCallback?.(value);
promptCallback = null;
}
function onPromptCancel() {
promptShow.value = false;
promptCallback = null;
}
// ── Actions ─────────────────────────────────────────────────
function handleCreateFolder() {
closeMenu();
const name = window.prompt("请输入文件夹名称:");
if (name && name.trim()) {
emit("createFolder", props.entry.path + "/" + name.trim());
}
openPrompt("新建文件夹", "", "输入文件夹名称", "", (name) => {
emit("createFolder", props.entry.path + "/" + name);
});
}
function handleCreateFile() {
closeMenu();
const name = window.prompt("输入文件名称(无需 .md 后缀)");
if (name && name.trim()) {
emit("createFile", props.entry.path + "/" + name.trim() + ".md");
}
openPrompt("新建 MD 文件", "", "输入文件名称(无需 .md 后缀)", "", (name) => {
emit("createFile", props.entry.path + "/" + name + ".md");
});
}
function handleRename() {
closeMenu();
const newName = window.prompt("请输入新名称", props.entry.name);
if (newName && newName.trim() && newName.trim() !== props.entry.name) {
emit("renameEntry", props.entry.path, newName.trim());
openPrompt("重命名", "", "输入新名称", props.entry.name, (newName) => {
if (newName !== props.entry.name) {
emit("renameEntry", props.entry.path, newName);
}
});
}
// ── Delete confirmation ─────────────────────────────────────
@@ -183,6 +217,17 @@ function onClick() {
@cancel="confirmShow = false"
/>
<!-- Prompt dialog -->
<PromptDialog
:show="promptShow"
:title="promptTitle"
:message="promptMessage"
:placeholder="promptPlaceholder"
:model-value="promptDefaultValue"
@confirm="onPromptConfirm"
@cancel="onPromptCancel"
/>
<template v-if="entry.is_dir">
<!-- Folder row -->
<div