FeedbackModal.vue
3.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div class="modal-backdrop" @click.self="$emit('close')">
<div class="modal">
<header>
<h2>+ NEW REQUIREMENT</h2>
<button class="x" @click="$emit('close')">×</button>
</header>
<p class="hint">提交一段新的需求 / 反馈 → PM 进 incremental 模式,追加 PRD 章节并 bump rev。</p>
<textarea
v-model="text"
:disabled="submitting"
placeholder="例如:增加 toggle done 功能;或:把 POST /todos 改成支持 text+priority"
/>
<div v-if="err" class="err">{{ err }}</div>
<div v-if="ok" class="ok">{{ ok }}</div>
<footer>
<button class="btn cancel" @click="$emit('close')" :disabled="submitting">Cancel</button>
<button class="btn submit" @click="submit" :disabled="!text.trim() || submitting">{{ submitting ? '...' : 'Submit' }}</button>
</footer>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps({ pid: { type: String, required: true } });
const emit = defineEmits(['close', 'submitted']);
const text = ref('');
const submitting = ref(false);
const err = ref('');
const ok = ref('');
async function submit() {
if (!text.value.trim()) return;
submitting.value = true;
err.value = ''; ok.value = '';
try {
const r = await fetch(`/api/devloop/projects/${props.pid}/feedback`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: text.value.trim(), by: 'human' }),
});
const j = await r.json();
if (!r.ok) {
err.value = j.error || `HTTP ${r.status}`;
} else {
ok.value = `PRD bumped: rev ${j.prd_rev_before} → ${j.prd_rev_after} · status=${j.status}`;
setTimeout(() => emit('submitted'), 800);
}
} catch (e) {
err.value = e.message;
} finally {
submitting.value = false;
}
}
</script>
<style scoped>
.modal-backdrop {
position:fixed; inset:0; background:rgba(0,0,0,.4); z-index:1000;
display:flex; align-items:center; justify-content:center;
}
.modal {
background:#fff; border-radius:8px; width:560px; max-width:90vw;
padding:20px 24px; box-shadow:0 8px 32px rgba(0,0,0,.2);
}
header { display:flex; justify-content:space-between; align-items:center; margin-bottom:8px; }
h2 { margin:0; font-size:16px; }
.x { background:none; border:none; font-size:24px; cursor:pointer; color:#888; line-height:1; }
.hint { color:#888; font-size:12px; margin:0 0 12px 0; }
textarea {
width:100%; min-height:120px; padding:10px; box-sizing:border-box;
border:1px solid #ccc; border-radius:4px; font-family:inherit; resize:vertical;
}
.err { color:#c00; font-size:12px; margin-top:8px; }
.ok { color:#070; font-size:12px; margin-top:8px; }
footer { margin-top:12px; display:flex; justify-content:flex-end; gap:8px; }
.btn { padding:6px 14px; border:none; border-radius:4px; cursor:pointer; font-size:13px; }
.btn:disabled { background:#ddd !important; color:#999 !important; cursor:not-allowed; }
.btn.cancel { background:#eee; color:#222; }
.btn.submit { background:#222; color:#fff; }
</style>