新增批量审核价格功能并优化前端交互

- 后端新增 `/prices/approve-all` 接口,支持管理员一键通过所有待审核价格
- 前端价格管理页面添加"全部通过"按钮,仅对管理员可见
- 优化批量添加价格页面交互,增加行复制和删除按钮
- 调整价格输入为可为空,移除默认值为0的限制
This commit is contained in:
wood chen 2025-03-06 22:43:30 +08:00
parent e7e93dc2ad
commit da79bf3d6d
3 changed files with 166 additions and 17 deletions

View File

@ -364,3 +364,85 @@ func GetPriceRates(c *gin.Context) {
c.JSON(http.StatusOK, rates)
}
// ApproveAllPrices 批量通过所有待审核的价格
func ApproveAllPrices(c *gin.Context) {
var input struct {
Status string `json:"status" binding:"required,eq=approved"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db := c.MustGet("db").(*sql.DB)
now := time.Now()
// 获取当前用户
user, exists := c.Get("user")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"})
return
}
currentUser := user.(*models.User)
// 只有管理员可以批量通过
if currentUser.Role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Admin permission required"})
return
}
// 查询待审核的价格数量
var pendingCount int
err := db.QueryRow("SELECT COUNT(*) FROM price WHERE status = 'pending'").Scan(&pendingCount)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count pending prices"})
return
}
if pendingCount == 0 {
c.JSON(http.StatusOK, gin.H{
"message": "No pending prices to approve",
"count": 0,
})
return
}
// 批量更新所有待审核的价格
result, err := db.Exec(`
UPDATE price
SET model = COALESCE(temp_model, model),
model_type = COALESCE(temp_model_type, model_type),
billing_type = COALESCE(temp_billing_type, billing_type),
channel_type = COALESCE(temp_channel_type, channel_type),
currency = COALESCE(temp_currency, currency),
input_price = COALESCE(temp_input_price, input_price),
output_price = COALESCE(temp_output_price, output_price),
price_source = COALESCE(temp_price_source, price_source),
status = ?,
updated_at = ?,
temp_model = NULL,
temp_model_type = NULL,
temp_billing_type = NULL,
temp_channel_type = NULL,
temp_currency = NULL,
temp_input_price = NULL,
temp_output_price = NULL,
temp_price_source = NULL,
updated_by = NULL
WHERE status = 'pending'`, input.Status, now)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to approve all prices"})
return
}
updatedCount, _ := result.RowsAffected()
c.JSON(http.StatusOK, gin.H{
"message": "All pending prices approved successfully",
"count": updatedCount,
"updated_at": now,
})
}

View File

@ -68,6 +68,7 @@ func main() {
prices.PUT("/:id", middleware.AuthRequired(), handlers.UpdatePrice)
prices.DELETE("/:id", middleware.AuthRequired(), handlers.DeletePrice)
prices.PUT("/:id/status", middleware.AuthRequired(), middleware.AdminRequired(), handlers.UpdatePriceStatus)
prices.PUT("/approve-all", middleware.AuthRequired(), middleware.AdminRequired(), handlers.ApproveAllPrices)
}
// 模型厂商相关路由

View File

@ -12,6 +12,10 @@
<el-button type="danger" @click="batchUpdateStatus('rejected')">批量拒绝</el-button>
<el-divider direction="vertical" />
</template>
<template v-if="isAdmin">
<el-button type="success" @click="approveAllPending">全部通过</el-button>
<el-divider direction="vertical" />
</template>
<el-button type="primary" @click="handleBatchAdd">批量添加</el-button>
<el-button type="primary" @click="handleAdd">提交价格</el-button>
</div>
@ -260,11 +264,10 @@
</el-card>
<!-- 批量添加对话框 -->
<el-dialog v-model="batchDialogVisible" title="批量添加模型价格" width="1200px">
<el-dialog v-model="batchDialogVisible" title="批量添加模型价格" width="1330px">
<div class="batch-add-container">
<div class="batch-toolbar">
<el-button type="primary" @click="addRow">添加行</el-button>
<el-button type="danger" @click="removeSelectedRows" :disabled="!selectedRows.length">删除选中行</el-button>
<el-divider direction="vertical" />
<el-popover
placement="bottom"
@ -295,10 +298,24 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
<el-table
:data="batchForms"
style="width: 100%"
@selection-change="handleSelectionChange"
height="400"
>
<el-table-column type="selection" width="55" />
<el-table-column label="操作" width="100">
<template #default="{ row, $index }">
<div class="row-actions">
<el-tooltip content="复制" placement="top">
<el-button type="primary" link @click="duplicateRow($index)">
<el-icon><Document /></el-icon>
</el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button type="danger" link @click="removeRow($index)">
<el-icon><Delete /></el-icon>
</el-button>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column label="模型" width="180">
<template #default="{ row }">
<el-input v-model="row.model" placeholder="请输入模型名称" />
@ -361,12 +378,12 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
</el-table-column>
<el-table-column label="输入价格(M)" width="150">
<template #default="{ row }">
<el-input-number v-model="row.input_price" :precision="4" :step="0.0001" style="width: 100%" />
<el-input-number v-model="row.input_price" :precision="4" :step="0.0001" style="width: 100%" :controls="false" placeholder="请输入价格" />
</template>
</el-table-column>
<el-table-column label="输出价格(M)" width="150">
<template #default="{ row }">
<el-input-number v-model="row.output_price" :precision="4" :step="0.0001" style="width: 100%" />
<el-input-number v-model="row.output_price" :precision="4" :step="0.0001" style="width: 100%" :controls="false" placeholder="请输入价格" />
</template>
</el-table-column>
<el-table-column label="价格来源" min-width="200" width="200">
@ -456,12 +473,12 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
</el-col>
<el-col :span="12">
<el-form-item label="输入价格(M)">
<el-input-number v-model="form.input_price" :precision="4" :step="0.0001" style="width: 100%" />
<el-input-number v-model="form.input_price" :precision="4" :step="0.0001" style="width: 100%" :controls="false" placeholder="请输入价格" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="输出价格(M)">
<el-input-number v-model="form.output_price" :precision="4" :step="0.0001" style="width: 100%" />
<el-input-number v-model="form.output_price" :precision="4" :step="0.0001" style="width: 100%" :controls="false" placeholder="请输入价格" />
</el-form-item>
</el-col>
<el-col :span="24">
@ -486,7 +503,7 @@ import { ref, computed, onMounted, watch } from 'vue'
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { Edit, Delete, Check, Close } from '@element-plus/icons-vue'
import { Edit, Delete, Check, Close, Document } from '@element-plus/icons-vue'
const props = defineProps({
user: Object
@ -500,8 +517,8 @@ const form = ref({
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
input_price: 0,
output_price: 0,
input_price: null,
output_price: null,
price_source: '',
created_by: ''
})
@ -644,8 +661,8 @@ const handleAdd = () => {
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
input_price: 0,
output_price: 0,
input_price: null,
output_price: null,
price_source: '',
created_by: ''
}
@ -734,8 +751,8 @@ const handleSubmitResponse = async (response) => {
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
input_price: 0,
output_price: 0,
input_price: null,
output_price: null,
price_source: '',
created_by: ''
}
@ -821,8 +838,8 @@ const createNewRow = () => ({
billing_type: 'tokens',
channel_type: '',
currency: 'USD',
input_price: 0,
output_price: 0,
input_price: null,
output_price: null,
price_source: '',
created_by: props.user?.username || ''
})
@ -1026,6 +1043,55 @@ watch(selectedModelType, () => {
loadPrices()
})
//
const duplicateRow = (index) => {
const newRow = { ...batchForms.value[index] }
batchForms.value.splice(index + 1, 0, newRow)
}
//
const removeRow = (index) => {
batchForms.value.splice(index, 1)
if (batchForms.value.length === 0) {
addRow() //
}
}
//
const approveAllPending = async () => {
try {
//
const { data } = await axios.get('/api/prices', { params: { status: 'pending', pageSize: 1 } })
const pendingCount = data.total
if (pendingCount === 0) {
ElMessage.info('当前没有待审核的价格')
return
}
//
await ElMessageBox.confirm(
`确定要通过所有 ${pendingCount} 条待审核价格吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'success'
}
)
//
await axios.put('/api/prices/approve-all', { status: 'approved' })
await loadPrices()
ElMessage.success('已通过所有待审核价格')
} catch (error) {
if (error === 'cancel') return
console.error('Failed to approve all pending prices:', error)
ElMessage.error('操作失败')
}
}
onMounted(() => {
loadModelTypes()
loadPrices()