6 Commits
5.0.3 ... 5.0.4

Author SHA1 Message Date
zzc
c60ea798fd fix: add robot score
All checks were successful
continuous-integration/drone/tag Build is passing
2026-07-04 20:29:49 +08:00
zzc
f2389e73aa fix: add robot score 2026-07-04 20:11:32 +08:00
zzc
fe7613c6df fix: add robot score 2026-07-04 19:27:41 +08:00
zzc
7d62377380 fix: add robot score 2026-07-04 18:31:37 +08:00
zzc
93da905e1d fix: topic pinned 2026-06-25 07:16:35 +08:00
zzc
1361bf34f8 fix: comment 2026-06-25 07:09:12 +08:00
11 changed files with 430 additions and 29 deletions

View File

@@ -26,7 +26,7 @@ export function getParticipantList(data) {
/**
* pk 投票
* @param { pkId: string, itemId: string, userId: string } data
* @param { pkId: string, itemId: string, userId?: string, count?: number } data
*/
export function vote(data) {
return request({

View File

@@ -66,3 +66,17 @@ export function toggleEnable(id, isEnabled) {
data: { isEnabled },
})
}
/**
* 置顶话题置顶状态
* @param {id: string} id 话题 id
* @param {isPinned: boolean} 是否置顶话题
* @returns
*/
export function pinnedTopic(id, isPinned) {
return request({
url: `/management/api/rating/topic/pinned/${id}`,
method: 'patch',
data: { isPinned },
})
}

View File

@@ -58,3 +58,16 @@ export function getPendingList(data) {
params: data,
})
}
/**
* 给某个分数段投票
* @param {topicId: string, itemId: string, score: number, count: number} data
* @returns
*/
export function ItemScorevote(data) {
return request({
url: 'management/api/rating/topicItem/vote/robot',
method: 'post',
data,
})
}

View File

@@ -9,7 +9,7 @@ export function getList(data) {
}
/**
* 获取机器人列表(基础信息) {appId, pageNo, pageSize, topicId, itemId, commentId?}
* 获取机器人列表(基础信息) {appId, pageNo, pageSize, topicId, itemId, commentId?, pkId?}
* @param {*} data
* @returns
*/
@@ -21,10 +21,10 @@ export function getListBase(data) {
})
}
export function generate(appId) {
export function generate(appId, count) {
return request({
url: 'management/api/system/robot',
method: 'post',
data: { appId },
data: { appId, count },
})
}

View File

@@ -95,9 +95,9 @@
getThumbUrl,
getPkLabel(item) {
if (!item) return ''
const left = item.leftItem ? item.leftItem.name : '未知'
const right = item.rightItem ? item.rightItem.name : '未知'
return `[${item.topic ? item.topic.title : '未知主题'}] ${left} vs ${right}`
const left = item.leftItemName ? item.leftItemName : '未知'
const right = item.rightItemName ? item.rightItemName : '未知'
return `[${item.topicTitle ? item.topicTitle : '未知主题'}] ${left} vs ${right}`
},
showEdit(replyToRow = null, parentCommentId = null) {
this.dialogFormVisible = true

View File

@@ -65,12 +65,12 @@
<el-table-column align="left" label="PK赛目标" min-width="250">
<template #default="{ row }">
<div v-if="row.pk" class="target-info-cell">
<div class="topic-title">[{{ row.pk.topic ? row.pk.topic.title : '未知主题' }}]</div>
<div v-if="row.leftItemName && row.rightItemName" class="target-info-cell">
<div class="topic-title">[{{ row.topicTitle ? row.topicTitle : '未知主题' }}]</div>
<div class="item-info">
<span>{{ row.pk.leftItem ? row.pk.leftItem.name : '未知' }}</span>
<span>{{ row.leftItemName ? row.leftItemName : '未知123' }}</span>
<span style="margin: 0 8px; color: #f56c6c; font-weight: bold">VS</span>
<span>{{ row.pk.rightItem ? row.pk.rightItem.name : '未知' }}</span>
<span>{{ row.rightItemName ? row.rightItemName : '未知123' }}</span>
</div>
</div>
<div v-else>{{ row.pkId }}</div>
@@ -171,9 +171,9 @@
getThumbUrl,
getPkLabel(item) {
if (!item) return ''
const left = item.leftItem ? item.leftItem.name : '未知'
const right = item.rightItem ? item.rightItem.name : '未知'
return `[${item.topic ? item.topic.title : '未知主题'}] ${left} vs ${right}`
const left = item.leftItemName ? item.leftItemName : '未知'
const right = item.rightItemName ? item.rightItemName : '未知'
return `[${item.topicTitle ? item.topicTitle : '未知主题'}] ${left} vs ${right}`
},
// -- PK 列表加载 --

View File

@@ -4,7 +4,14 @@
<el-form-item label="支持项">
<span style="font-weight: bold; color: #409eff">{{ targetName }}</span>
</el-form-item>
<el-form-item label="投票用户" prop="userId">
<el-form-item label="支持方式" prop="voteMode">
<el-radio-group v-model="form.voteMode">
<el-radio label="user">投票用户</el-radio>
<el-radio label="count">支持个数</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.voteMode === 'user'" label="投票用户" prop="userId">
<el-select v-model="form.userId" v-loadmore="loadMoreRobots" filterable placeholder="请选择投票的机器人" style="width: 100%">
<el-option v-for="item in robotList" :key="item.id" :label="item.nickname || item.id" :value="item.id">
<div style="display: flex; align-items: center">
@@ -18,6 +25,10 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="form.voteMode === 'count'" label="支持个数" prop="count">
<el-input-number v-model="form.count" controls-position="right" :max="100000" :min="1" :step="1" style="width: 100%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
@@ -60,10 +71,12 @@
form: {
pkId: '',
itemId: '',
voteMode: 'user',
userId: '',
count: 1,
},
rules: {
userId: [{ required: true, trigger: 'change', message: '请选择投票的机器人' }],
voteMode: [{ required: true, trigger: 'change', message: '请选择支持方式' }],
},
// 机器人下拉列表状态
@@ -117,9 +130,26 @@
save() {
this.$refs['form'].validate(async (valid) => {
if (valid) {
if (this.form.voteMode === 'user' && !this.form.userId) {
this.$baseMessage('请选择投票的机器人', 'error')
return false
}
if (this.form.voteMode === 'count' && (!this.form.count || this.form.count < 1)) {
this.$baseMessage('请输入正确的支持个数', 'error')
return false
}
this.saving = true
try {
const { msg } = await vote(this.form)
const params = {
pkId: this.form.pkId,
itemId: this.form.itemId,
}
if (this.form.voteMode === 'user') {
params.userId = this.form.userId
} else {
params.count = this.form.count
}
const { msg } = await vote(params)
this.$baseMessage(msg || '投票成功', 'success')
this.close()
this.$emit('fetch-data')

View File

@@ -94,6 +94,12 @@
{{ formatTime(row.createdAt) }}
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="100">
<template #default="{ row }">
<el-button type="text" @click="handleComment(row)">评论</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@@ -109,13 +115,48 @@
<pk-generate ref="generate" @fetch-data="fetchData" />
<pk-participant-list ref="participantList" />
<pk-vote-edit ref="voteEdit" @fetch-data="fetchData" />
<el-dialog title="立即评论" :visible.sync="commentDialogVisible" width="600px" @close="closeCommentDialog">
<el-form ref="commentForm" label-width="100px" :model="commentForm" :rules="commentRules">
<el-form-item label="参与机器人" prop="userId">
<el-select
v-model="commentForm.userId"
v-loadmore="loadMoreCommentUsers"
filterable
placeholder="请选择参与评论的机器人"
style="width: 100%"
>
<el-option v-for="item in commentUserList" :key="item.id" :label="item.nickname || item.id" :value="item.id">
<div style="display: flex; align-items: center">
<el-image
v-if="item.avatar"
:src="getThumbUrl(item.avatar)"
style="width: 24px; height: 24px; border-radius: 50%; margin-right: 10px"
/>
<span>{{ item.nickname || '未知用户' }}</span>
</div>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="评论内容" prop="content">
<el-input v-model="commentForm.content" maxlength="500" placeholder="请输入评论内容" :rows="4" show-word-limit type="textarea" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="closeCommentDialog">取消</el-button>
<el-button :loading="commentSaving" type="primary" @click="submitComment">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getList } from '@/api/rating/pk'
import { comment, getList } from '@/api/rating/pk'
import { getList as getTopicList } from '@/api/rating/topic'
import { getList as getItemList } from '@/api/rating/topicItem'
import { getListBase as getRobotListBase } from '@/api/system/robot'
import { formatTime } from '@/utils'
import { getThumbUrl } from '@/utils/blessing'
import PkGenerate from './components/PkGenerate'
@@ -172,6 +213,26 @@
},
itemTotal: 0,
fetchingItems: false,
commentDialogVisible: false,
commentSaving: false,
commentForm: {
pkId: '',
userId: '',
content: '',
},
commentRules: {
userId: [{ required: true, trigger: 'change', message: '请选择参与评论的机器人' }],
content: [{ required: true, trigger: 'blur', message: '请输入评论内容' }],
},
commentUserList: [],
commentUserQuery: {
appId: '6a0d7dbe4c5de50f2ba66475',
pkId: '',
pageNo: 1,
pageSize: 20,
},
commentUserTotal: 0,
fetchingCommentUsers: false,
}
},
created() {
@@ -281,6 +342,58 @@
handleVote(pkId, itemId, itemName) {
this.$refs['voteEdit'].showEdit(pkId, itemId, itemName)
},
handleComment(row) {
this.commentDialogVisible = true
this.commentForm = {
pkId: row.id,
userId: '',
content: '',
}
this.commentUserList = []
this.commentUserTotal = 0
this.commentUserQuery.pageNo = 1
this.commentUserQuery.pkId = row.id
this.fetchCommentUsers()
},
closeCommentDialog() {
if (this.$refs['commentForm']) {
this.$refs['commentForm'].resetFields()
}
this.commentDialogVisible = false
},
async fetchCommentUsers() {
if (this.fetchingCommentUsers || !this.commentUserQuery.pkId) return
this.fetchingCommentUsers = true
try {
const { data } = await getRobotListBase(this.commentUserQuery)
this.commentUserList = this.commentUserList.concat(data.list || [])
this.commentUserTotal = data.totalCount || 0
} catch (error) {
console.error(error)
} finally {
this.fetchingCommentUsers = false
}
},
loadMoreCommentUsers() {
if (this.commentUserList.length >= this.commentUserTotal || this.fetchingCommentUsers) return
this.commentUserQuery.pageNo += 1
this.fetchCommentUsers()
},
submitComment() {
this.$refs['commentForm'].validate(async (valid) => {
if (!valid) return false
this.commentSaving = true
try {
const { msg } = await comment(this.commentForm)
this.$baseMessage(msg || '评论成功', 'success')
this.closeCommentDialog()
} catch (error) {
console.error(error)
} finally {
this.commentSaving = false
}
})
},
},
}
</script>

View File

@@ -1,11 +1,33 @@
<template>
<div class="rating-topic-container">
<el-alert
v-if="!queryForm.categoryId"
:closable="false"
show-icon
style="margin-bottom: 20px"
title="选择类别后可以置顶话题"
type="info"
/>
<vab-query-form>
<vab-query-form-left-panel>
<el-button icon="el-icon-plus" type="primary" @click="handleAdd">创建话题</el-button>
</vab-query-form-left-panel>
<vab-query-form-left-panel>
<el-form :inline="true" :model="queryForm" @submit.native.prevent>
<el-form-item>
<el-select
v-model="queryForm.categoryId"
v-loadmore="loadMoreCategories"
clearable
filterable
placeholder="请选择分类"
style="width: 200px"
@change="queryData"
>
<el-option v-for="item in categoryList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="queryForm.status" clearable placeholder="请选择状态" @change="queryData">
<el-option label="审核中" :value="1" />
@@ -96,9 +118,15 @@
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="150">
<el-table-column align="center" label="操作" width="200">
<template #default="{ row }">
<el-button type="text" @click="handleViewItems(row)">查看评分项</el-button>
<template v-if="queryForm.categoryId">
<el-button v-if="!row.isPinned" style="color: #e6a23c" type="text" @click="handleTogglePin(row, true)">置顶</el-button>
<el-button v-else style="color: #909399" type="text" @click="handleTogglePin(row, false)">取消置顶</el-button>
</template>
<el-button v-if="row.status !== 4" style="color: #f56c6c" type="text" @click="handleDisable(row)">禁用</el-button>
<el-button v-if="row.status === 4" style="color: #67c23a" type="text" @click="handleEnable(row)">启用</el-button>
</template>
@@ -120,7 +148,8 @@
</template>
<script>
import { getList } from '@/api/rating/topic'
import { getList, pinnedTopic } from '@/api/rating/topic'
import { getList as getCategoryList } from '@/api/rating/topicCategory'
import { reviewTopic } from '@/api/rating/topicOperate'
import { formatTime } from '@/utils'
import { getThumbUrl } from '@/utils/blessing'
@@ -130,6 +159,23 @@
export default {
name: 'RatingTopicIndex',
components: { TopicEdit, UserInfo },
directives: {
loadmore: {
bind(el, binding) {
setTimeout(() => {
const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
if (SELECTWRAP_DOM) {
SELECTWRAP_DOM.addEventListener('scroll', function () {
const condition = this.scrollHeight - this.scrollTop <= this.clientHeight + 2
if (condition) {
binding.value()
}
})
}
}, 0)
},
},
},
data() {
return {
list: [],
@@ -142,18 +188,42 @@
pageSize: 10,
keyword: '',
status: '',
categoryId: '',
},
categoryList: [],
categoryQuery: { pageNo: 1, pageSize: 20 },
categoryTotal: 0,
fetchingCategories: false,
}
},
created() {
if (this.$route.query.topicId) {
this.queryForm.keyword = this.$route.query.topicId
}
this.fetchCategoryList()
this.fetchData()
},
methods: {
formatTime,
getThumbUrl,
async fetchCategoryList() {
if (this.fetchingCategories) return
this.fetchingCategories = true
try {
const { data } = await getCategoryList(this.categoryQuery)
this.categoryList = this.categoryList.concat(data.list || [])
this.categoryTotal = data.totalCount || 0
} catch (error) {
console.error(error)
} finally {
this.fetchingCategories = false
}
},
loadMoreCategories() {
if (this.categoryList.length >= this.categoryTotal || this.fetchingCategories) return
this.categoryQuery.pageNo += 1
this.fetchCategoryList()
},
handleAdd() {
this.$refs['edit'].showEdit()
},
@@ -172,7 +242,9 @@
async fetchData() {
this.listLoading = true
try {
const { data } = await getList(this.queryForm)
const params = { ...this.queryForm }
if (!params.categoryId) delete params.categoryId
const { data } = await getList(params)
this.list = data.list
this.total = data.totalCount
} catch (error) {
@@ -187,6 +259,15 @@
query: { topicId: row.id },
})
},
async handleTogglePin(row, isPinned) {
try {
const { msg } = await pinnedTopic(row.id, isPinned)
this.$baseMessage(msg || (isPinned ? '置顶成功' : '已取消置顶'), 'success')
this.fetchData()
} catch (error) {
console.error(error)
}
},
handleDisable(row) {
this.$confirm('确定要禁用该话题吗?', '提示', {
confirmButtonText: '确定',

View File

@@ -31,10 +31,11 @@
/>
<el-table v-loading="listLoading" :data="list" :element-loading-text="elementLoadingText">
<el-table-column align="center" fixed="left" label="操作" width="120">
<el-table-column align="center" fixed="left" label="操作" width="180">
<template #default="{ row }">
<el-button type="text" @click="handleRating(row)">评价</el-button>
<el-button type="text" @click="handleComment(row)">评论</el-button>
<el-button type="text" @click="handleScoreVote(row)">补评分</el-button>
</template>
</el-table-column>
@@ -91,6 +92,17 @@
</template>
</el-table-column>
<el-table-column align="left" label="分段票数" min-width="260">
<template #default="{ row }">
<div class="score-segment-list">
<div v-for="score in scoreOptions" :key="score" class="score-segment-item">
<span class="score-segment-label">{{ score }}</span>
<span class="score-segment-value">{{ getScoreCount(row.scores, score) }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column align="left" label="互动数据" min-width="150">
<template #default="{ row }">
<div class="stat-cell">
@@ -147,11 +159,31 @@
<topic-item-rating-edit ref="ratingEdit" @fetch-data="fetchData" />
<topic-item-comment-edit ref="commentEdit" @fetch-data="fetchData" />
<el-dialog title="补充分段评分" :visible.sync="scoreVoteVisible" width="480px" @close="closeScoreVoteDialog">
<el-form ref="scoreVoteForm" label-width="100px" :model="scoreVoteForm" :rules="scoreVoteRules">
<el-form-item label="评价项">
<span>{{ scoreVoteForm.itemName || '-' }}</span>
</el-form-item>
<el-form-item label="分数段" prop="score">
<el-select v-model="scoreVoteForm.score" placeholder="请选择分数段" style="width: 100%">
<el-option v-for="score in scoreOptions" :key="score" :label="`${score}分`" :value="score" />
</el-select>
</el-form-item>
<el-form-item label="补充数量" prop="count">
<el-input-number v-model="scoreVoteForm.count" controls-position="right" :max="100000" :min="1" :step="1" style="width: 100%" />
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="closeScoreVoteDialog"> </el-button>
<el-button :loading="scoreVoteSubmitting" type="primary" @click="submitScoreVote"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { getList } from '@/api/rating/topicItem'
import { getList, ItemScorevote } from '@/api/rating/topicItem'
import { getList as getTopicList } from '@/api/rating/topic'
import { formatTime } from '@/utils'
import { getThumbUrl } from '@/utils/blessing'
@@ -176,6 +208,20 @@
keyword: '',
topicId: '',
},
scoreOptions: [2, 4, 6, 8, 10],
scoreVoteVisible: false,
scoreVoteSubmitting: false,
scoreVoteForm: {
topicId: '',
itemId: '',
itemName: '',
score: 2,
count: 1,
},
scoreVoteRules: {
score: [{ required: true, trigger: 'change', message: '请选择分数段' }],
count: [{ required: true, trigger: 'change', message: '请输入补充数量' }],
},
}
},
watch: {
@@ -243,6 +289,48 @@
handleComment(row) {
this.$refs['commentEdit'].showEdit(row, this.queryForm.topicId)
},
getScoreCount(scores, score) {
if (!scores) return 0
return scores[score] || scores[String(score)] || 0
},
handleScoreVote(row) {
this.scoreVoteForm = {
topicId: this.queryForm.topicId,
itemId: row.id,
itemName: row.name,
score: 2,
count: 1,
}
this.scoreVoteVisible = true
},
closeScoreVoteDialog() {
this.scoreVoteVisible = false
if (this.$refs['scoreVoteForm']) {
this.$refs['scoreVoteForm'].resetFields()
}
},
submitScoreVote() {
this.$refs['scoreVoteForm'].validate(async (valid) => {
if (!valid) return false
this.scoreVoteSubmitting = true
try {
const params = {
topicId: this.scoreVoteForm.topicId,
itemId: this.scoreVoteForm.itemId,
score: this.scoreVoteForm.score,
count: this.scoreVoteForm.count,
}
const { msg } = await ItemScorevote(params)
this.$baseMessage(msg || '补充评分成功', 'success')
this.closeScoreVoteDialog()
this.fetchData()
} catch (error) {
console.error(error)
} finally {
this.scoreVoteSubmitting = false
}
})
},
},
}
</script>
@@ -323,4 +411,29 @@
color: #409eff;
font-weight: bold;
}
.score-segment-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.score-segment-item {
display: inline-flex;
align-items: center;
padding: 4px 10px;
background: #f5f7fa;
border-radius: 4px;
font-size: 12px;
}
.score-segment-label {
color: #606266;
margin-right: 6px;
}
.score-segment-value {
color: #409eff;
font-weight: bold;
}
</style>

View File

@@ -92,6 +92,18 @@
<robot-rating-edit ref="ratingEdit" @fetch-data="fetchData" />
<robot-comment-edit ref="commentEdit" @fetch-data="fetchData" />
<robot-comment-view ref="commentView" />
<el-dialog title="生成机器人" :visible.sync="generateDialogVisible" width="420px" @close="closeGenerateDialog">
<el-form ref="generateForm" label-width="100px" :model="generateForm" :rules="generateRules">
<el-form-item label="生成个数" prop="count">
<el-input-number v-model="generateForm.count" controls-position="right" :max="1000" :min="1" :step="1" style="width: 100%" />
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="closeGenerateDialog">取消</el-button>
<el-button :loading="generateSubmitting" type="primary" @click="submitGenerate">确定</el-button>
</span>
</el-dialog>
</div>
</template>
@@ -121,6 +133,14 @@
keyword: '',
},
applicationList: [],
generateDialogVisible: false,
generateSubmitting: false,
generateForm: {
count: 1,
},
generateRules: {
count: [{ required: true, trigger: 'change', message: '请输入生成个数' }],
},
}
},
created() {
@@ -150,13 +170,30 @@
this.$baseMessage('请先选择应用', 'error')
return
}
try {
const { msg } = await generate(this.queryForm.appId)
this.$baseMessage(msg || '生成成功', 'success')
this.fetchData()
} catch (error) {
console.error(error)
this.generateDialogVisible = true
},
closeGenerateDialog() {
this.generateDialogVisible = false
if (this.$refs['generateForm']) {
this.$refs['generateForm'].resetFields()
}
this.generateForm.count = 1
},
submitGenerate() {
this.$refs['generateForm'].validate(async (valid) => {
if (!valid) return false
this.generateSubmitting = true
try {
const { msg } = await generate(this.queryForm.appId, this.generateForm.count)
this.$baseMessage(msg || '生成成功', 'success')
this.closeGenerateDialog()
this.fetchData()
} catch (error) {
console.error(error)
} finally {
this.generateSubmitting = false
}
})
},
handleSizeChange(val) {
this.queryForm.pageSize = val