BoardPage.vue
3.9 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
83
84
85
86
87
88
89
90
91
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { Badge, Button, Card, Input, Select, Textarea } from '@drama/ui'
import { api, getToken } from '../api.js'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const tasks = ref(null)
const message = ref('')
const form = reactive({ title: '', spec: '', template: 'piecework', reward: '', deadline: '', acceptance: '' })
// 一次填写对应一个幂等键:提交失败后重试不会重复扣积分,成功后换新键。
let idempotencyKey = crypto.randomUUID()
const BADGE = { open: 'info', claimed: 'warning', in_contest: 'warning', submitted: 'warning', reviewing: 'warning', rework: 'warning', disputed: 'danger', settled: 'success', pending_approval: 'neutral', cancelled: 'neutral', refunded: 'neutral' }
const tasksPath = `/api/voko/agents/${route.params.id}/tasks`
async function load() {
tasks.value = (await api('GET', tasksPath)).tasks
}
async function publish() {
message.value = ''
try {
await api('POST', tasksPath, {
title: form.title,
spec: form.spec,
template: form.template,
reward: Number(form.reward),
deadline: new Date(form.deadline).toISOString(),
acceptance: form.acceptance.split('\n').map((line) => line.trim()).filter((line) => line !== '').map((text, index) => ({ id: `c${index + 1}`, text })),
}, idempotencyKey)
idempotencyKey = crypto.randomUUID()
Object.assign(form, { title: '', spec: '', reward: '', deadline: '', acceptance: '' })
message.value = t('board.published')
await load()
} catch (err) {
message.value = err instanceof Error ? err.message : String(err)
}
}
onMounted(async () => {
if (getToken() === null) {
await router.push({ path: '/login', query: { next: route.fullPath } })
return
}
await load()
})
</script>
<template>
<h1>{{ t('board.title') }}</h1>
<p><router-link to="/agents">{{ t('board.back') }}</router-link></p>
<Card class="voko-board-card">
<h2>{{ t('board.publishTitle') }}</h2>
<p>{{ t('board.publishHint') }}</p>
<label class="voko-field">{{ t('board.taskTitle') }}<Input v-model="form.title" /></label>
<label class="voko-field">{{ t('board.spec') }}<Textarea v-model="form.spec" /></label>
<div class="voko-field">
<span>{{ t('board.template') }}</span>
<Select v-model="form.template" :aria-label="t('board.template')" :options="[{ value: 'piecework', label: t('board.templates.piecework') }, { value: 'contest', label: t('board.templates.contest') }]" />
</div>
<label class="voko-field">{{ t('board.reward_label') }}<Input v-model="form.reward" type="number" /></label>
<label class="voko-field">{{ t('board.deadline_label') }}<Input v-model="form.deadline" type="datetime-local" /></label>
<label class="voko-field">{{ t('board.acceptance') }}<Textarea v-model="form.acceptance" :rows="3" /></label>
<p v-if="message" role="status">{{ message }}</p>
<Button :disabled="form.title === '' || form.spec === '' || form.reward === '' || form.deadline === '' || form.acceptance.trim() === ''" @click="publish">{{ t('board.publish') }}</Button>
</Card>
<template v-if="tasks">
<p v-if="tasks.length === 0">{{ t('board.empty') }}</p>
<Card v-for="task in tasks" :key="task.id" class="voko-board-card">
<h3>{{ task.title }} <Badge :variant="BADGE[task.status]">{{ t(`board.status.${task.status}`) }}</Badge></h3>
<p>{{ t(`board.templates.${task.template}`) }} · {{ t('board.reward', { reward: task.reward }) }} · {{ t('board.rarity', { rarity: task.rarity }) }} · {{ t('board.deadline', { deadline: new Date(task.deadline).toLocaleString() }) }}</p>
<p class="voko-spec">{{ task.spec }}</p>
</Card>
</template>
</template>
<style scoped>
.voko-board-card {
margin-bottom: var(--drama-space-md);
}
.voko-spec {
white-space: pre-wrap;
}
</style>