feat(ECR-020): QualityFeedback 问答质量反馈并 Closed
新增 ask_quality_feedback、运营/C端评分 API 与 admin-h5 问答反馈区;禁改消息/UGC/真支付。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -275,6 +275,26 @@ export const adminApi = {
|
||||
'/content-safety/evaluate',
|
||||
{ text },
|
||||
),
|
||||
askFeedback: () =>
|
||||
request<{
|
||||
items: Array<{
|
||||
id: string
|
||||
thread_id: string
|
||||
message_id?: string
|
||||
source: string
|
||||
rating: number
|
||||
tag?: string
|
||||
note?: string
|
||||
created_at: string
|
||||
}>
|
||||
}>('GET', '/ask/feedback'),
|
||||
createAskFeedback: (threadId: string, body: { rating: number; tag?: string; note?: string; message_id?: string }) =>
|
||||
request<{
|
||||
id: string
|
||||
thread_id: string
|
||||
rating: number
|
||||
source: string
|
||||
}>('POST', `/ask/threads/${threadId}/feedback`, body),
|
||||
orders: () =>
|
||||
request<{
|
||||
items: Array<{
|
||||
|
||||
@@ -2,23 +2,34 @@
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { adminApi } from '@/api/client'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
type Thread = Awaited<ReturnType<typeof adminApi.askThreads>>['items'][number]
|
||||
type Detail = Awaited<ReturnType<typeof adminApi.askThread>>
|
||||
type Feedback = Awaited<ReturnType<typeof adminApi.askFeedback>>['items'][number]
|
||||
|
||||
const auth = useAuthStore()
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const items = ref<Thread[]>([])
|
||||
const selected = ref<Detail | null>(null)
|
||||
const detailErr = ref('')
|
||||
const detailLoading = ref(false)
|
||||
const feedback = ref<Feedback[]>([])
|
||||
const rating = ref(4)
|
||||
const tag = ref('helpful')
|
||||
const note = ref('')
|
||||
const fbMsg = ref('')
|
||||
|
||||
const canWriteFeedback = () => auth.can('admin.ask.feedback.write')
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const res = await adminApi.askThreads()
|
||||
items.value = res.items || []
|
||||
const [threads, fb] = await Promise.all([adminApi.askThreads(), adminApi.askFeedback()])
|
||||
items.value = threads.items || []
|
||||
feedback.value = fb.items || []
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '加载失败'
|
||||
} finally {
|
||||
@@ -30,6 +41,7 @@ async function openThread(id: string) {
|
||||
detailLoading.value = true
|
||||
detailErr.value = ''
|
||||
selected.value = null
|
||||
fbMsg.value = ''
|
||||
try {
|
||||
selected.value = await adminApi.askThread(id)
|
||||
} catch (e) {
|
||||
@@ -39,6 +51,24 @@ async function openThread(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitFeedback() {
|
||||
if (!selected.value) return
|
||||
fbMsg.value = ''
|
||||
try {
|
||||
await adminApi.createAskFeedback(selected.value.id, {
|
||||
rating: rating.value,
|
||||
tag: tag.value,
|
||||
note: note.value || undefined,
|
||||
})
|
||||
fbMsg.value = '已提交质量反馈'
|
||||
note.value = ''
|
||||
const fb = await adminApi.askFeedback()
|
||||
feedback.value = fb.items || []
|
||||
} catch (e) {
|
||||
fbMsg.value = e instanceof Error ? e.message : '提交失败'
|
||||
}
|
||||
}
|
||||
|
||||
function fmtTime(iso?: string) {
|
||||
if (!iso) return '—'
|
||||
try {
|
||||
@@ -54,7 +84,7 @@ onMounted(load)
|
||||
<template>
|
||||
<section>
|
||||
<h1>问答会话</h1>
|
||||
<p class="muted">只读 AskSessionView · 不可改写消息</p>
|
||||
<p class="muted">AskSessionView · QualityFeedback · 不可改写消息</p>
|
||||
<p v-if="loading" class="muted">加载中…</p>
|
||||
<p v-else-if="error" class="err">{{ error }}</p>
|
||||
<div v-else class="layout">
|
||||
@@ -76,6 +106,13 @@ onMounted(load)
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 class="sub">近期反馈</h2>
|
||||
<p v-if="!feedback.length" class="muted">暂无</p>
|
||||
<ul v-else class="fb-list">
|
||||
<li v-for="f in feedback.slice(0, 8)" :key="f.id">
|
||||
{{ f.source }} · {{ f.rating }}★ · {{ f.tag || '—' }} · {{ fmtTime(f.created_at) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card detail">
|
||||
<h2>会话详情</h2>
|
||||
@@ -93,6 +130,25 @@ onMounted(load)
|
||||
<time>{{ fmtTime(m.created_at) }}</time>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="canWriteFeedback()" class="fb-form">
|
||||
<h3>质量反馈</h3>
|
||||
<select v-model.number="rating">
|
||||
<option :value="1">1</option>
|
||||
<option :value="2">2</option>
|
||||
<option :value="3">3</option>
|
||||
<option :value="4">4</option>
|
||||
<option :value="5">5</option>
|
||||
</select>
|
||||
<select v-model="tag">
|
||||
<option value="helpful">helpful</option>
|
||||
<option value="off_topic">off_topic</option>
|
||||
<option value="unsafe">unsafe</option>
|
||||
<option value="other">other</option>
|
||||
</select>
|
||||
<input v-model="note" type="text" placeholder="备注(可选)" />
|
||||
<button class="btn" type="button" @click="submitFeedback">提交</button>
|
||||
<span class="muted">{{ fbMsg }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="muted">选择左侧会话查看消息</p>
|
||||
</div>
|
||||
@@ -103,6 +159,8 @@ onMounted(load)
|
||||
<style scoped>
|
||||
h1 { margin: 0 0 0.35rem; font-size: 1.35rem; }
|
||||
h2 { margin: 0 0 0.6rem; font-size: 1.05rem; }
|
||||
h2.sub { margin-top: 1rem; font-size: 0.95rem; }
|
||||
h3 { margin: 0.75rem 0 0.4rem; font-size: 0.95rem; }
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 1fr;
|
||||
@@ -121,6 +179,15 @@ h2 { margin: 0 0 0.6rem; font-size: 1.05rem; }
|
||||
.msgs p { margin: 0.25rem 0; white-space: pre-wrap; word-break: break-word; }
|
||||
.msgs time { font-size: 0.75rem; color: var(--muted); }
|
||||
.msgs .assistant { background: rgba(255, 244, 242, 0.6); }
|
||||
.fb-list { margin: 0; padding-left: 1.1rem; font-size: 0.85rem; color: var(--muted); }
|
||||
.fb-form {
|
||||
display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: center;
|
||||
margin-top: 0.85rem; padding-top: 0.75rem; border-top: 1px solid var(--line);
|
||||
}
|
||||
.fb-form select, .fb-form input {
|
||||
border: 1px solid var(--line); border-radius: 8px; padding: 0.4rem 0.55rem;
|
||||
}
|
||||
.fb-form input { min-width: 10rem; flex: 1; }
|
||||
@media (max-width: 900px) {
|
||||
.layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user