mirror of
https://github.com/woodchen-ink/aimodels-prices.git
synced 2025-07-18 05:32:00 +08:00
优化价格处理逻辑,整合价格创建和更新功能
- 在 prices.go 中新增 ProcessPrice 函数,统一处理价格的创建和更新逻辑 - 更新 FetchAndSavePrices 和 UpdateOtherPrices 函数,使用 ProcessPrice 进行价格记录的处理 - 在 GetPrices 函数中添加状态筛选参数,优化价格查询功能 - 前端 Prices.vue 中调整搜索框和筛选功能的样式,提升用户体验 - 修复部分代码格式和样式问题,增强代码可读性
This commit is contained in:
parent
dce4815654
commit
75d62d978a
@ -8,9 +8,9 @@ import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"aimodels-prices/database"
|
||||
"aimodels-prices/handlers"
|
||||
"aimodels-prices/models"
|
||||
)
|
||||
|
||||
@ -74,6 +74,8 @@ func FetchAndSavePrices() error {
|
||||
}
|
||||
|
||||
// 处理每个模型的价格数据
|
||||
processedCount := 0
|
||||
skippedCount := 0
|
||||
for _, modelData := range openRouterResp.Data {
|
||||
// 确定模型类型
|
||||
modelType := determineModelType(modelData.Modality)
|
||||
@ -87,6 +89,7 @@ func FetchAndSavePrices() error {
|
||||
inputPrice, err = parsePrice(modelData.Endpoint.Pricing.Prompt)
|
||||
if err != nil {
|
||||
log.Printf("解析endpoint输入价格失败 %s: %v", modelData.Slug, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
} else if modelData.Pricing.Prompt != "" {
|
||||
@ -94,6 +97,7 @@ func FetchAndSavePrices() error {
|
||||
inputPrice, err = parsePrice(modelData.Pricing.Prompt)
|
||||
if err != nil {
|
||||
log.Printf("解析输入价格失败 %s: %v", modelData.Slug, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -102,39 +106,20 @@ func FetchAndSavePrices() error {
|
||||
outputPrice, err = parsePrice(modelData.Endpoint.Pricing.Completion)
|
||||
if err != nil {
|
||||
log.Printf("解析endpoint输出价格失败 %s: %v", modelData.Slug, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
} else if modelData.Pricing.Completion != "" {
|
||||
outputPrice, err = parsePrice(modelData.Pricing.Completion)
|
||||
if err != nil {
|
||||
log.Printf("解析输出价格失败 %s: %v", modelData.Slug, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已存在相同模型的价格记录
|
||||
var existingPrice models.Price
|
||||
result := db.Where("model = ? AND channel_type = ?", modelData.Slug, ChannelType).First(&existingPrice)
|
||||
|
||||
if result.Error == nil {
|
||||
// 更新现有记录
|
||||
existingPrice.ModelType = modelType
|
||||
existingPrice.BillingType = BillingType
|
||||
existingPrice.Currency = Currency
|
||||
existingPrice.InputPrice = inputPrice
|
||||
existingPrice.OutputPrice = outputPrice
|
||||
existingPrice.PriceSource = PriceSource
|
||||
existingPrice.Status = Status
|
||||
existingPrice.UpdatedAt = time.Now()
|
||||
|
||||
if err := db.Save(&existingPrice).Error; err != nil {
|
||||
log.Printf("更新价格记录失败 %s: %v", modelData.Slug, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("更新价格记录: %s", modelData.Slug)
|
||||
} else {
|
||||
// 创建新记录
|
||||
newPrice := models.Price{
|
||||
// 创建价格对象
|
||||
price := models.Price{
|
||||
Model: modelData.Slug,
|
||||
ModelType: modelType,
|
||||
BillingType: BillingType,
|
||||
@ -147,15 +132,46 @@ func FetchAndSavePrices() error {
|
||||
CreatedBy: CreatedBy,
|
||||
}
|
||||
|
||||
if err := db.Create(&newPrice).Error; err != nil {
|
||||
log.Printf("创建价格记录失败 %s: %v", modelData.Slug, err)
|
||||
// 检查是否已存在相同模型的价格记录
|
||||
var existingPrice models.Price
|
||||
result := db.Where("model = ? AND channel_type = ?", modelData.Slug, ChannelType).First(&existingPrice)
|
||||
|
||||
if result.Error == nil {
|
||||
// 使用processPrice函数处理更新
|
||||
_, changed, err := handlers.ProcessPrice(price, &existingPrice, true, CreatedBy)
|
||||
if err != nil {
|
||||
log.Printf("更新价格记录失败 %s: %v", modelData.Slug, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if changed {
|
||||
log.Printf("更新价格记录: %s", modelData.Slug)
|
||||
processedCount++
|
||||
} else {
|
||||
log.Printf("价格无变化,跳过更新: %s", modelData.Slug)
|
||||
skippedCount++
|
||||
}
|
||||
} else {
|
||||
// 使用processPrice函数处理创建
|
||||
_, changed, err := handlers.ProcessPrice(price, nil, true, CreatedBy)
|
||||
if err != nil {
|
||||
log.Printf("创建价格记录失败 %s: %v", modelData.Slug, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if changed {
|
||||
log.Printf("创建新价格记录: %s", modelData.Slug)
|
||||
processedCount++
|
||||
} else {
|
||||
log.Printf("价格创建失败: %s", modelData.Slug)
|
||||
skippedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("OpenRouter价格数据处理完成")
|
||||
log.Printf("OpenRouter价格数据处理完成,成功处理: %d, 跳过: %d", processedCount, skippedCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"aimodels-prices/database"
|
||||
"aimodels-prices/handlers"
|
||||
"aimodels-prices/models"
|
||||
)
|
||||
|
||||
@ -28,6 +28,11 @@ var blacklist = []string{
|
||||
"shap-e",
|
||||
"palm-2",
|
||||
"o3-mini-high",
|
||||
"claude-instant",
|
||||
"claude-1",
|
||||
"claude-3-haiku",
|
||||
"claude-3-opus",
|
||||
"claude-3-sonnet",
|
||||
}
|
||||
|
||||
const (
|
||||
@ -94,6 +99,20 @@ func UpdateOtherPrices() error {
|
||||
log.Printf("修正Google模型名称: %s -> %s", parts[1], modelName)
|
||||
}
|
||||
}
|
||||
if author == "anthropic" {
|
||||
// 处理claude-3.5-sonnet系列模型名称
|
||||
if strings.HasPrefix(modelName, "claude-3.5") {
|
||||
suffix := strings.TrimPrefix(modelName, "claude-3.5")
|
||||
modelName = "claude-3-5" + suffix
|
||||
log.Printf("修正Claude模型名称: %s -> %s", parts[1], modelName)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(modelName, "claude-3.7") {
|
||||
suffix := strings.TrimPrefix(modelName, "claude-3.7")
|
||||
modelName = "claude-3-7" + suffix
|
||||
log.Printf("修正Claude模型名称: %s -> %s", parts[1], modelName)
|
||||
}
|
||||
}
|
||||
|
||||
// 确定模型类型
|
||||
modelType := determineModelType(modelData.Modality)
|
||||
@ -102,7 +121,14 @@ func UpdateOtherPrices() error {
|
||||
var inputPrice, outputPrice float64
|
||||
var parseErr error
|
||||
|
||||
// 优先使用endpoint中的pricing
|
||||
// 如果输入或输出价格为空,直接跳过
|
||||
if modelData.Endpoint.Pricing.Prompt == "" || modelData.Endpoint.Pricing.Completion == "" {
|
||||
log.Printf("跳过价格数据不完整的模型: %s", modelData.Slug)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 使用endpoint中的pricing
|
||||
if modelData.Endpoint.Pricing.Prompt != "" {
|
||||
inputPrice, parseErr = parsePrice(modelData.Endpoint.Pricing.Prompt)
|
||||
if parseErr != nil {
|
||||
@ -110,14 +136,6 @@ func UpdateOtherPrices() error {
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
} else if modelData.Pricing.Prompt != "" {
|
||||
// 如果endpoint中没有,则使用顶层pricing
|
||||
inputPrice, parseErr = parsePrice(modelData.Pricing.Prompt)
|
||||
if parseErr != nil {
|
||||
log.Printf("解析输入价格失败 %s: %v", modelData.Slug, parseErr)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if modelData.Endpoint.Pricing.Completion != "" {
|
||||
@ -127,40 +145,10 @@ func UpdateOtherPrices() error {
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
} else if modelData.Pricing.Completion != "" {
|
||||
outputPrice, parseErr = parsePrice(modelData.Pricing.Completion)
|
||||
if parseErr != nil {
|
||||
log.Printf("解析输出价格失败 %s: %v", modelData.Slug, parseErr)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已存在相同模型的价格记录
|
||||
var existingPrice models.Price
|
||||
result := db.Where("model = ? AND channel_type = ?", modelName, channelType).First(&existingPrice)
|
||||
|
||||
if result.Error == nil {
|
||||
// 更新现有记录
|
||||
existingPrice.ModelType = modelType
|
||||
existingPrice.BillingType = BillingType
|
||||
existingPrice.Currency = Currency
|
||||
existingPrice.InputPrice = inputPrice
|
||||
existingPrice.OutputPrice = outputPrice
|
||||
existingPrice.PriceSource = OtherPriceSource
|
||||
existingPrice.Status = OtherStatus
|
||||
existingPrice.UpdatedAt = time.Now()
|
||||
|
||||
if err := db.Save(&existingPrice).Error; err != nil {
|
||||
log.Printf("更新价格记录失败 %s: %v", modelName, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
log.Printf("更新价格记录: %s (厂商: %s)", modelName, author)
|
||||
processedCount++
|
||||
} else {
|
||||
// 创建新记录
|
||||
newPrice := models.Price{
|
||||
// 创建价格对象
|
||||
price := models.Price{
|
||||
Model: modelName,
|
||||
ModelType: modelType,
|
||||
BillingType: BillingType,
|
||||
@ -173,13 +161,42 @@ func UpdateOtherPrices() error {
|
||||
CreatedBy: CreatedBy,
|
||||
}
|
||||
|
||||
if err := db.Create(&newPrice).Error; err != nil {
|
||||
// 检查是否已存在相同模型的价格记录
|
||||
var existingPrice models.Price
|
||||
result := db.Where("model = ? AND channel_type = ?", modelName, channelType).First(&existingPrice)
|
||||
|
||||
if result.Error == nil {
|
||||
// 使用processPrice函数处理更新
|
||||
_, changed, err := handlers.ProcessPrice(price, &existingPrice, false, CreatedBy)
|
||||
if err != nil {
|
||||
log.Printf("更新价格记录失败 %s: %v", modelName, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if changed {
|
||||
log.Printf("更新价格记录: %s (厂商: %s)", modelName, author)
|
||||
processedCount++
|
||||
} else {
|
||||
log.Printf("价格无变化,跳过更新: %s (厂商: %s)", modelName, author)
|
||||
skippedCount++
|
||||
}
|
||||
} else {
|
||||
// 使用processPrice函数处理创建
|
||||
_, changed, err := handlers.ProcessPrice(price, nil, false, CreatedBy)
|
||||
if err != nil {
|
||||
log.Printf("创建价格记录失败 %s: %v", modelName, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if changed {
|
||||
log.Printf("创建新价格记录: %s (厂商: %s)", modelName, author)
|
||||
processedCount++
|
||||
} else {
|
||||
log.Printf("价格创建失败: %s (厂商: %s)", modelName, author)
|
||||
skippedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ func GetPrices(c *gin.Context) {
|
||||
channelType := c.Query("channel_type") // 厂商筛选参数
|
||||
modelType := c.Query("model_type") // 模型类型筛选参数
|
||||
searchQuery := c.Query("search") // 搜索查询参数
|
||||
status := c.Query("status") // 状态筛选参数
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
@ -31,8 +32,8 @@ func GetPrices(c *gin.Context) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// 构建缓存键
|
||||
cacheKey := fmt.Sprintf("prices_page_%d_size_%d_channel_%s_type_%s_search_%s",
|
||||
page, pageSize, channelType, modelType, searchQuery)
|
||||
cacheKey := fmt.Sprintf("prices_page_%d_size_%d_channel_%s_type_%s_search_%s_status_%s",
|
||||
page, pageSize, channelType, modelType, searchQuery, status)
|
||||
|
||||
// 尝试从缓存获取
|
||||
if cachedData, found := database.GlobalCache.Get(cacheKey); found {
|
||||
@ -56,10 +57,15 @@ func GetPrices(c *gin.Context) {
|
||||
if searchQuery != "" {
|
||||
query = query.Where("model LIKE ?", "%"+searchQuery+"%")
|
||||
}
|
||||
// 添加状态筛选条件
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 获取总数 - 使用缓存优化
|
||||
var total int64
|
||||
totalCacheKey := fmt.Sprintf("prices_count_channel_%s_type_%s_search_%s", channelType, modelType, searchQuery)
|
||||
totalCacheKey := fmt.Sprintf("prices_count_channel_%s_type_%s_search_%s_status_%s",
|
||||
channelType, modelType, searchQuery, status)
|
||||
|
||||
if cachedTotal, found := database.GlobalCache.Get(totalCacheKey); found {
|
||||
if t, ok := cachedTotal.(int64); ok {
|
||||
@ -97,6 +103,118 @@ func GetPrices(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// processPrice 处理价格的创建和更新逻辑
|
||||
func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool, username string) (models.Price, bool, error) {
|
||||
// 如果是更新操作且存在现有记录
|
||||
if existingPrice != nil {
|
||||
// 检查价格是否有变化
|
||||
if isAdmin {
|
||||
// 管理员直接更新主字段,检查是否有实际变化
|
||||
if existingPrice.Model == price.Model &&
|
||||
existingPrice.ModelType == price.ModelType &&
|
||||
existingPrice.BillingType == price.BillingType &&
|
||||
existingPrice.ChannelType == price.ChannelType &&
|
||||
existingPrice.Currency == price.Currency &&
|
||||
existingPrice.InputPrice == price.InputPrice &&
|
||||
existingPrice.OutputPrice == price.OutputPrice &&
|
||||
existingPrice.PriceSource == price.PriceSource {
|
||||
// 没有变化,不需要更新
|
||||
return *existingPrice, false, nil
|
||||
}
|
||||
|
||||
// 有变化,更新字段
|
||||
existingPrice.Model = price.Model
|
||||
existingPrice.ModelType = price.ModelType
|
||||
existingPrice.BillingType = price.BillingType
|
||||
existingPrice.ChannelType = price.ChannelType
|
||||
existingPrice.Currency = price.Currency
|
||||
existingPrice.InputPrice = price.InputPrice
|
||||
existingPrice.OutputPrice = price.OutputPrice
|
||||
existingPrice.PriceSource = price.PriceSource
|
||||
existingPrice.Status = "approved"
|
||||
existingPrice.UpdatedBy = &username
|
||||
existingPrice.TempModel = nil
|
||||
existingPrice.TempModelType = nil
|
||||
existingPrice.TempBillingType = nil
|
||||
existingPrice.TempChannelType = nil
|
||||
existingPrice.TempCurrency = nil
|
||||
existingPrice.TempInputPrice = nil
|
||||
existingPrice.TempOutputPrice = nil
|
||||
existingPrice.TempPriceSource = nil
|
||||
|
||||
// 保存更新
|
||||
if err := database.DB.Save(existingPrice).Error; err != nil {
|
||||
return *existingPrice, false, err
|
||||
}
|
||||
return *existingPrice, true, nil
|
||||
} else {
|
||||
// 普通用户更新临时字段,检查是否有实际变化
|
||||
// 创建临时值的指针
|
||||
modelPtr := &price.Model
|
||||
modelTypePtr := &price.ModelType
|
||||
billingTypePtr := &price.BillingType
|
||||
channelTypePtr := &price.ChannelType
|
||||
currencyPtr := &price.Currency
|
||||
inputPricePtr := &price.InputPrice
|
||||
outputPricePtr := &price.OutputPrice
|
||||
priceSourcePtr := &price.PriceSource
|
||||
|
||||
// 检查临时字段与现有主字段是否相同
|
||||
if (existingPrice.Model == price.Model &&
|
||||
existingPrice.ModelType == price.ModelType &&
|
||||
existingPrice.BillingType == price.BillingType &&
|
||||
existingPrice.ChannelType == price.ChannelType &&
|
||||
existingPrice.Currency == price.Currency &&
|
||||
existingPrice.InputPrice == price.InputPrice &&
|
||||
existingPrice.OutputPrice == price.OutputPrice &&
|
||||
existingPrice.PriceSource == price.PriceSource) ||
|
||||
// 或者检查临时字段与现有临时字段是否相同
|
||||
(existingPrice.TempModel != nil && *existingPrice.TempModel == price.Model &&
|
||||
existingPrice.TempModelType != nil && *existingPrice.TempModelType == price.ModelType &&
|
||||
existingPrice.TempBillingType != nil && *existingPrice.TempBillingType == price.BillingType &&
|
||||
existingPrice.TempChannelType != nil && *existingPrice.TempChannelType == price.ChannelType &&
|
||||
existingPrice.TempCurrency != nil && *existingPrice.TempCurrency == price.Currency &&
|
||||
existingPrice.TempInputPrice != nil && *existingPrice.TempInputPrice == price.InputPrice &&
|
||||
existingPrice.TempOutputPrice != nil && *existingPrice.TempOutputPrice == price.OutputPrice &&
|
||||
existingPrice.TempPriceSource != nil && *existingPrice.TempPriceSource == price.PriceSource) {
|
||||
// 没有变化,不需要更新
|
||||
return *existingPrice, false, nil
|
||||
}
|
||||
|
||||
// 有变化,更新临时字段
|
||||
existingPrice.TempModel = modelPtr
|
||||
existingPrice.TempModelType = modelTypePtr
|
||||
existingPrice.TempBillingType = billingTypePtr
|
||||
existingPrice.TempChannelType = channelTypePtr
|
||||
existingPrice.TempCurrency = currencyPtr
|
||||
existingPrice.TempInputPrice = inputPricePtr
|
||||
existingPrice.TempOutputPrice = outputPricePtr
|
||||
existingPrice.TempPriceSource = priceSourcePtr
|
||||
existingPrice.Status = "pending"
|
||||
existingPrice.UpdatedBy = &username
|
||||
|
||||
// 保存更新
|
||||
if err := database.DB.Save(existingPrice).Error; err != nil {
|
||||
return *existingPrice, false, err
|
||||
}
|
||||
return *existingPrice, true, nil
|
||||
}
|
||||
} else {
|
||||
// 创建新记录
|
||||
price.Status = "pending"
|
||||
if isAdmin {
|
||||
price.Status = "approved"
|
||||
}
|
||||
price.CreatedBy = username
|
||||
|
||||
// 保存新记录
|
||||
if err := database.DB.Create(&price).Error; err != nil {
|
||||
return price, false, err
|
||||
}
|
||||
return price, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func CreatePrice(c *gin.Context) {
|
||||
var price models.Price
|
||||
if err := c.ShouldBindJSON(&price); err != nil {
|
||||
@ -123,19 +241,27 @@ func CreatePrice(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置状态和创建者
|
||||
price.Status = "pending"
|
||||
// 获取当前用户
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
currentUser := user.(*models.User)
|
||||
|
||||
// 创建记录
|
||||
if err := database.DB.Create(&price).Error; err != nil {
|
||||
// 处理价格创建
|
||||
result, changed, err := ProcessPrice(price, nil, currentUser.Role == "admin", currentUser.Username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create price"})
|
||||
return
|
||||
}
|
||||
|
||||
// 清除所有价格相关缓存
|
||||
if changed {
|
||||
clearPriceCache()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, price)
|
||||
c.JSON(http.StatusCreated, result)
|
||||
}
|
||||
|
||||
func UpdatePriceStatus(c *gin.Context) {
|
||||
@ -209,9 +335,21 @@ func UpdatePriceStatus(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 如果是拒绝,清除临时字段
|
||||
// 如果是拒绝
|
||||
// 检查是否是新创建的价格(没有原始价格)
|
||||
isNewPrice := price.Model == "" || (price.TempModel != nil && price.Model == *price.TempModel)
|
||||
|
||||
if isNewPrice {
|
||||
// 如果是新创建的价格,直接删除
|
||||
if err := tx.Delete(&price).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete rejected price"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 如果是更新的价格,恢复到原始状态(清除临时字段并设置状态为approved)
|
||||
if err := tx.Model(&price).Updates(map[string]interface{}{
|
||||
"status": input.Status,
|
||||
"status": "approved", // 恢复为已批准状态
|
||||
"updated_at": time.Now(),
|
||||
"temp_model": nil,
|
||||
"temp_model_type": nil,
|
||||
@ -228,6 +366,7 @@ func UpdatePriceStatus(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
@ -239,12 +378,21 @@ func UpdatePriceStatus(c *gin.Context) {
|
||||
// 清除所有价格相关缓存
|
||||
clearPriceCache()
|
||||
|
||||
// 根据操作类型返回不同的消息
|
||||
if input.Status == "rejected" && (price.Model == "" || (price.TempModel != nil && price.Model == *price.TempModel)) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Price rejected and deleted successfully",
|
||||
"status": input.Status,
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Status updated successfully",
|
||||
"status": input.Status,
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func UpdatePrice(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
@ -288,55 +436,19 @@ func UpdatePrice(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 根据用户角色决定更新方式
|
||||
if currentUser.Role == "admin" {
|
||||
// 管理员直接更新主字段
|
||||
existingPrice.Model = price.Model
|
||||
existingPrice.ModelType = price.ModelType
|
||||
existingPrice.BillingType = price.BillingType
|
||||
existingPrice.ChannelType = price.ChannelType
|
||||
existingPrice.Currency = price.Currency
|
||||
existingPrice.InputPrice = price.InputPrice
|
||||
existingPrice.OutputPrice = price.OutputPrice
|
||||
existingPrice.PriceSource = price.PriceSource
|
||||
existingPrice.Status = "approved"
|
||||
existingPrice.UpdatedBy = ¤tUser.Username
|
||||
existingPrice.TempModel = nil
|
||||
existingPrice.TempModelType = nil
|
||||
existingPrice.TempBillingType = nil
|
||||
existingPrice.TempChannelType = nil
|
||||
existingPrice.TempCurrency = nil
|
||||
existingPrice.TempInputPrice = nil
|
||||
existingPrice.TempOutputPrice = nil
|
||||
existingPrice.TempPriceSource = nil
|
||||
|
||||
if err := database.DB.Save(&existingPrice).Error; err != nil {
|
||||
// 处理价格更新
|
||||
result, changed, err := ProcessPrice(price, &existingPrice, currentUser.Role == "admin", currentUser.Username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update price"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 普通用户更新临时字段
|
||||
existingPrice.TempModel = &price.Model
|
||||
existingPrice.TempModelType = &price.ModelType
|
||||
existingPrice.TempBillingType = &price.BillingType
|
||||
existingPrice.TempChannelType = &price.ChannelType
|
||||
existingPrice.TempCurrency = &price.Currency
|
||||
existingPrice.TempInputPrice = &price.InputPrice
|
||||
existingPrice.TempOutputPrice = &price.OutputPrice
|
||||
existingPrice.TempPriceSource = &price.PriceSource
|
||||
existingPrice.Status = "pending"
|
||||
existingPrice.UpdatedBy = ¤tUser.Username
|
||||
|
||||
if err := database.DB.Save(&existingPrice).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update price"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有价格相关缓存
|
||||
if changed {
|
||||
clearPriceCache()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, existingPrice)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func DeletePrice(c *gin.Context) {
|
||||
@ -362,6 +474,16 @@ func DeletePrice(c *gin.Context) {
|
||||
}
|
||||
|
||||
func ApproveAllPrices(c *gin.Context) {
|
||||
// 获取操作类型(批准或拒绝)
|
||||
var input struct {
|
||||
Action string `json:"action" binding:"required,oneof=approve reject"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid action, must be 'approve' or 'reject'"})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找所有待审核的价格
|
||||
var pendingPrices []models.Price
|
||||
if err := database.DB.Where("status = 'pending'").Find(&pendingPrices).Error; err != nil {
|
||||
@ -371,8 +493,12 @@ func ApproveAllPrices(c *gin.Context) {
|
||||
|
||||
// 开始事务
|
||||
tx := database.DB.Begin()
|
||||
processedCount := 0
|
||||
deletedCount := 0
|
||||
|
||||
for _, price := range pendingPrices {
|
||||
if input.Action == "approve" {
|
||||
// 批准操作
|
||||
updateMap := map[string]interface{}{
|
||||
"status": "approved",
|
||||
"updated_at": time.Now(),
|
||||
@ -420,6 +546,42 @@ func ApproveAllPrices(c *gin.Context) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to approve prices"})
|
||||
return
|
||||
}
|
||||
processedCount++
|
||||
} else {
|
||||
// 拒绝操作
|
||||
// 检查是否是新创建的价格(没有原始价格)
|
||||
isNewPrice := price.Model == "" || (price.TempModel != nil && price.Model == *price.TempModel)
|
||||
|
||||
if isNewPrice {
|
||||
// 如果是新创建的价格,直接删除
|
||||
if err := tx.Delete(&price).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete rejected price"})
|
||||
return
|
||||
}
|
||||
deletedCount++
|
||||
} else {
|
||||
// 如果是更新的价格,恢复到原始状态(清除临时字段并设置状态为approved)
|
||||
if err := tx.Model(&price).Updates(map[string]interface{}{
|
||||
"status": "approved", // 恢复为已批准状态
|
||||
"updated_at": time.Now(),
|
||||
"temp_model": nil,
|
||||
"temp_model_type": nil,
|
||||
"temp_billing_type": nil,
|
||||
"temp_channel_type": nil,
|
||||
"temp_currency": nil,
|
||||
"temp_input_price": nil,
|
||||
"temp_output_price": nil,
|
||||
"temp_price_source": nil,
|
||||
"updated_by": nil,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reject prices"})
|
||||
return
|
||||
}
|
||||
processedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
@ -432,10 +594,20 @@ func ApproveAllPrices(c *gin.Context) {
|
||||
// 清除所有价格相关缓存
|
||||
clearPriceCache()
|
||||
|
||||
// 根据操作类型返回不同的消息
|
||||
if input.Action == "approve" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "All pending prices approved successfully",
|
||||
"count": len(pendingPrices),
|
||||
"count": processedCount,
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "All pending prices rejected successfully",
|
||||
"processed": processedCount,
|
||||
"deleted": deletedCount,
|
||||
"total": processedCount + deletedCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// clearPriceCache 清除所有价格相关的缓存
|
||||
|
@ -23,39 +23,28 @@
|
||||
</template>
|
||||
|
||||
<!-- 添加搜索框 -->
|
||||
<div class="search-section">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="搜索模型名称"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<div class="filter-section">
|
||||
<div class="filter-label" style="min-width:80px;">搜索模型:</div>
|
||||
<div>
|
||||
<el-input v-model="searchQuery" placeholder="搜索模型名称" clearable prefix-icon="Search" @input="handleSearch">
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
<el-icon>
|
||||
<Search />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-section">
|
||||
<div class="filter-label">厂商筛选:</div>
|
||||
<div class="filter-label" style="min-width:80px;">厂商筛选:</div>
|
||||
<div class="provider-filters">
|
||||
<el-button
|
||||
:type="!selectedProvider ? 'primary' : ''"
|
||||
@click="selectedProvider = ''"
|
||||
>全部</el-button>
|
||||
<el-button
|
||||
v-for="provider in providers"
|
||||
:key="provider.id"
|
||||
<el-button :type="!selectedProvider ? 'primary' : ''" @click="selectedProvider = ''">全部</el-button>
|
||||
<el-button v-for="provider in providers" :key="provider.id"
|
||||
:type="selectedProvider === provider.id.toString() ? 'primary' : ''"
|
||||
@click="selectedProvider = provider.id.toString()"
|
||||
>
|
||||
@click="selectedProvider = provider.id.toString()">
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<el-image
|
||||
v-if="provider.icon"
|
||||
:src="provider.icon"
|
||||
style="width: 16px; height: 16px"
|
||||
/>
|
||||
<el-image v-if="provider.icon" :src="provider.icon" style="width: 16px; height: 16px" />
|
||||
<span>{{ provider.name }}</span>
|
||||
</div>
|
||||
</el-button>
|
||||
@ -63,18 +52,11 @@
|
||||
</div>
|
||||
|
||||
<div class="filter-section">
|
||||
<div class="filter-label">模型类别:</div>
|
||||
<div class="filter-label" style="min-width:80px;">模型类别:</div>
|
||||
<div class="model-type-filters">
|
||||
<el-button
|
||||
:type="!selectedModelType ? 'primary' : ''"
|
||||
@click="selectedModelType = ''"
|
||||
>全部</el-button>
|
||||
<el-button
|
||||
v-for="(label, key) in modelTypeMap"
|
||||
:key="key"
|
||||
:type="selectedModelType === key ? 'primary' : ''"
|
||||
@click="selectedModelType = key"
|
||||
>
|
||||
<el-button :type="!selectedModelType ? 'primary' : ''" @click="selectedModelType = ''">全部</el-button>
|
||||
<el-button v-for="(label, key) in modelTypeMap" :key="key" :type="selectedModelType === key ? 'primary' : ''"
|
||||
@click="selectedModelType = key">
|
||||
{{ label }}
|
||||
</el-button>
|
||||
</div>
|
||||
@ -87,13 +69,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="prices"
|
||||
style="width: 100%"
|
||||
@selection-change="handlePriceSelectionChange"
|
||||
v-loading="tableLoading"
|
||||
element-loading-text="加载中..."
|
||||
>
|
||||
<el-table :data="prices" style="width: 100%" @selection-change="handlePriceSelectionChange"
|
||||
v-loading="tableLoading" element-loading-text="加载中...">
|
||||
<el-table-column v-if="isAdmin" type="selection" width="55" />
|
||||
<el-table-column label="模型">
|
||||
<template #default="{ row }">
|
||||
@ -109,7 +86,8 @@
|
||||
<template #default="{ row }">
|
||||
<div class="value-container">
|
||||
<span>{{ getModelType(row.model_type) }}</span>
|
||||
<el-tag v-if="row.temp_model_type && row.temp_model_type !== 'NULL'" type="warning" size="small" effect="light">
|
||||
<el-tag v-if="row.temp_model_type && row.temp_model_type !== 'NULL'" type="warning" size="small"
|
||||
effect="light">
|
||||
待审核: {{ getModelType(row.temp_model_type) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@ -119,7 +97,8 @@
|
||||
<template #default="{ row }">
|
||||
<div class="value-container">
|
||||
<span>{{ getBillingType(row.billing_type) }}</span>
|
||||
<el-tag v-if="row.temp_billing_type && row.temp_billing_type !== 'NULL'" type="warning" size="small" effect="light">
|
||||
<el-tag v-if="row.temp_billing_type && row.temp_billing_type !== 'NULL'" type="warning" size="small"
|
||||
effect="light">
|
||||
待审核: {{ getBillingType(row.temp_billing_type) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@ -129,14 +108,12 @@
|
||||
<template #default="{ row }">
|
||||
<div class="value-container">
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<el-image
|
||||
v-if="getProvider(row.channel_type)?.icon"
|
||||
:src="getProvider(row.channel_type)?.icon"
|
||||
style="width: 24px; height: 24px"
|
||||
/>
|
||||
<el-image v-if="getProvider(row.channel_type)?.icon" :src="getProvider(row.channel_type)?.icon"
|
||||
style="width: 24px; height: 24px" />
|
||||
<span>{{ getProvider(row.channel_type)?.name || row.channel_type }}</span>
|
||||
</div>
|
||||
<el-tag v-if="row.temp_channel_type && row.temp_channel_type !== 'NULL'" type="warning" size="small" effect="light">
|
||||
<el-tag v-if="row.temp_channel_type && row.temp_channel_type !== 'NULL'" type="warning" size="small"
|
||||
effect="light">
|
||||
待审核: {{ getProvider(row.temp_channel_type)?.name || row.temp_channel_type }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@ -146,7 +123,8 @@
|
||||
<template #default="{ row }">
|
||||
<div class="value-container">
|
||||
<span>{{ row.currency }}</span>
|
||||
<el-tag v-if="row.temp_currency && row.temp_currency !== 'NULL'" type="warning" size="small" effect="light">
|
||||
<el-tag v-if="row.temp_currency && row.temp_currency !== 'NULL'" type="warning" size="small"
|
||||
effect="light">
|
||||
待审核: {{ row.temp_currency }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@ -156,7 +134,9 @@
|
||||
<template #default="{ row }">
|
||||
<div class="value-container">
|
||||
<span>{{ row.input_price === 0 ? '免费' : row.input_price }}</span>
|
||||
<el-tag v-if="row.temp_input_price !== null && row.temp_input_price !== undefined && row.temp_input_price !== 'NULL'" type="warning" size="small" effect="light">
|
||||
<el-tag
|
||||
v-if="row.temp_input_price !== null && row.temp_input_price !== undefined && row.temp_input_price !== 'NULL'"
|
||||
type="warning" size="small" effect="light">
|
||||
待审核: {{ row.temp_input_price === 0 ? '免费' : row.temp_input_price }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@ -166,7 +146,9 @@
|
||||
<template #default="{ row }">
|
||||
<div class="value-container">
|
||||
<span>{{ row.output_price === 0 ? '免费' : row.output_price }}</span>
|
||||
<el-tag v-if="row.temp_output_price !== null && row.temp_output_price !== undefined && row.temp_output_price !== 'NULL'" type="warning" size="small" effect="light">
|
||||
<el-tag
|
||||
v-if="row.temp_output_price !== null && row.temp_output_price !== undefined && row.temp_output_price !== 'NULL'"
|
||||
type="warning" size="small" effect="light">
|
||||
待审核: {{ row.temp_output_price === 0 ? '免费' : row.temp_output_price }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@ -174,11 +156,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column width="80">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
placement="left"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
>
|
||||
<el-popover placement="left" :width="200" trigger="hover">
|
||||
<template #reference>
|
||||
<el-button link type="primary">详情</el-button>
|
||||
</template>
|
||||
@ -191,7 +169,8 @@
|
||||
<span class="detail-label">价格来源:</span>
|
||||
<div class="detail-value">
|
||||
<span>{{ row.price_source }}</span>
|
||||
<el-tag v-if="row.temp_price_source && row.temp_price_source !== 'NULL'" type="warning" size="small" effect="light">
|
||||
<el-tag v-if="row.temp_price_source && row.temp_price_source !== 'NULL'" type="warning" size="small"
|
||||
effect="light">
|
||||
待审核: {{ row.temp_price_source }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@ -210,44 +189,41 @@
|
||||
<template v-if="isAdmin">
|
||||
<el-tooltip content="编辑" placement="top">
|
||||
<el-button type="primary" link @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
<el-icon>
|
||||
<Edit />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button type="danger" link @click="handleDelete(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="row.status === 'pending' ? '通过审核' : '已审核'" placement="top">
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
@click="updateStatus(row.id, 'approved')"
|
||||
:disabled="row.status !== 'pending'"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
<el-button type="success" link @click="updateStatus(row.id, 'approved')"
|
||||
:disabled="row.status !== 'pending'">
|
||||
<el-icon>
|
||||
<Check />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="row.status === 'pending' ? '拒绝审核' : '已审核'" placement="top">
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="updateStatus(row.id, 'rejected')"
|
||||
:disabled="row.status !== 'pending'"
|
||||
>
|
||||
<el-icon><Close /></el-icon>
|
||||
<el-button type="danger" link @click="updateStatus(row.id, 'rejected')"
|
||||
:disabled="row.status !== 'pending'">
|
||||
<el-icon>
|
||||
<Close />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tooltip :content="row.status === 'pending' ? '等待审核中' : '提交修改'" placement="top">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleQuickEdit(row)"
|
||||
:disabled="row.status === 'pending'"
|
||||
>
|
||||
<el-icon><Edit /></el-icon>
|
||||
<el-button type="primary" link @click="handleQuickEdit(row)" :disabled="row.status === 'pending'">
|
||||
<el-icon>
|
||||
<Edit />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
@ -258,18 +234,12 @@
|
||||
|
||||
<!-- 修改分页组件 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:small="false"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[10, 20, 50, 100]"
|
||||
:total="total" layout="total, sizes, prev, pager, next" :small="false" @size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange">
|
||||
<template #sizes>
|
||||
<el-select v-model="pageSize" :options="[10, 20, 50, 100].map(item => ({ value: item, label: `${item} 条/页` }))">
|
||||
<el-select v-model="pageSize"
|
||||
:options="[10, 20, 50, 100].map(item => ({ value: item, label: `${item} 条/页` }))">
|
||||
<template #prefix>每页</template>
|
||||
</el-select>
|
||||
</template>
|
||||
@ -283,25 +253,16 @@
|
||||
<div class="batch-toolbar">
|
||||
<el-button type="primary" @click="addRow">添加行</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
>
|
||||
<el-popover placement="bottom" :width="400" trigger="click">
|
||||
<template #reference>
|
||||
<el-button type="success">从表格导入</el-button>
|
||||
</template>
|
||||
<div class="import-popover">
|
||||
<p class="import-tip">请粘贴表格数据(支持从Excel复制),每行格式为:</p>
|
||||
<p class="import-format">模型名称 计费类型 厂商 货币 输入价格 输出价格</p>
|
||||
<el-input
|
||||
v-model="importText"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
placeholder="例如:
|
||||
<el-input v-model="importText" type="textarea" :rows="8" placeholder="例如:
|
||||
dall-e-2 按Token收费 OpenAI 美元 16.000000 16.000000
|
||||
dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||
/>
|
||||
dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000" />
|
||||
<div class="import-actions">
|
||||
<el-button type="primary" @click="handleImport">导入</el-button>
|
||||
</div>
|
||||
@ -309,22 +270,22 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||
</el-popover>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="batchForms"
|
||||
style="width: 100%"
|
||||
height="400"
|
||||
>
|
||||
<el-table :data="batchForms" style="width: 100%" height="400">
|
||||
<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-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-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -337,19 +298,9 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||
</el-table-column>
|
||||
<el-table-column label="模型类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.model_type"
|
||||
placeholder="请选择或输入"
|
||||
allow-create
|
||||
filterable
|
||||
@create="handleModelTypeCreate"
|
||||
>
|
||||
<el-option
|
||||
v-for="(label, value) in modelTypeMap"
|
||||
:key="value"
|
||||
:label="label"
|
||||
:value="value"
|
||||
/>
|
||||
<el-select v-model="row.model_type" placeholder="请选择或输入" allow-create filterable
|
||||
@create="handleModelTypeCreate">
|
||||
<el-option v-for="(label, value) in modelTypeMap" :key="value" :label="label" :value="value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -364,18 +315,10 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||
<el-table-column label="模型厂商" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.channel_type" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="provider in providers"
|
||||
:key="provider.id"
|
||||
:label="provider.name"
|
||||
:value="provider.id.toString()"
|
||||
>
|
||||
<el-option v-for="provider in providers" :key="provider.id" :label="provider.name"
|
||||
:value="provider.id.toString()">
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<el-image
|
||||
v-if="provider.icon"
|
||||
:src="provider.icon"
|
||||
style="width: 24px; height: 24px"
|
||||
/>
|
||||
<el-image v-if="provider.icon" :src="provider.icon" style="width: 24px; height: 24px" />
|
||||
<span>{{ provider.name }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
@ -392,12 +335,14 @@ 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%" :controls="false" placeholder="请输入价格" />
|
||||
<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%" :controls="false" placeholder="请输入价格" />
|
||||
<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">
|
||||
@ -418,11 +363,7 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||
</el-dialog>
|
||||
|
||||
<!-- 现有的单个添加对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="editingPrice ? (isAdmin ? '编辑价格' : '提交价格修改') : '提交价格'"
|
||||
width="700px"
|
||||
>
|
||||
<el-dialog v-model="dialogVisible" :title="editingPrice ? (isAdmin ? '编辑价格' : '提交价格修改') : '提交价格'" width="700px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
@ -432,19 +373,9 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="模型类型">
|
||||
<el-select
|
||||
v-model="form.model_type"
|
||||
placeholder="请选择或输入"
|
||||
allow-create
|
||||
filterable
|
||||
@create="handleModelTypeCreate"
|
||||
>
|
||||
<el-option
|
||||
v-for="(label, value) in modelTypeMap"
|
||||
:key="value"
|
||||
:label="label"
|
||||
:value="value"
|
||||
/>
|
||||
<el-select v-model="form.model_type" placeholder="请选择或输入" allow-create filterable
|
||||
@create="handleModelTypeCreate">
|
||||
<el-option v-for="(label, value) in modelTypeMap" :key="value" :label="label" :value="value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -459,18 +390,10 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||
<el-col :span="12">
|
||||
<el-form-item label="模型厂商">
|
||||
<el-select v-model="form.channel_type" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="provider in providers"
|
||||
:key="provider.id"
|
||||
:label="provider.name"
|
||||
:value="provider.id.toString()"
|
||||
>
|
||||
<el-option v-for="provider in providers" :key="provider.id" :label="provider.name"
|
||||
:value="provider.id.toString()">
|
||||
<div style="display: flex; align-items: center; gap: 8px">
|
||||
<el-image
|
||||
v-if="provider.icon"
|
||||
:src="provider.icon"
|
||||
style="width: 24px; height: 24px"
|
||||
/>
|
||||
<el-image v-if="provider.icon" :src="provider.icon" style="width: 24px; height: 24px" />
|
||||
<span>{{ provider.name }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
@ -487,12 +410,14 @@ 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%" :controls="false" placeholder="请输入价格" />
|
||||
<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%" :controls="false" placeholder="请输入价格" />
|
||||
<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">
|
||||
@ -1109,7 +1034,12 @@ const removeRow = (index) => {
|
||||
const approveAllPending = async () => {
|
||||
try {
|
||||
// 获取所有待审核的价格数量
|
||||
const { data } = await axios.get('/api/prices', { params: { status: 'pending', pageSize: 1 } })
|
||||
const { data } = await axios.get('/api/prices', {
|
||||
params: {
|
||||
status: 'pending',
|
||||
pageSize: 1
|
||||
}
|
||||
})
|
||||
const pendingCount = data.total
|
||||
|
||||
if (pendingCount === 0) {
|
||||
@ -1129,10 +1059,11 @@ const approveAllPending = async () => {
|
||||
)
|
||||
|
||||
// 批量更新所有待审核价格的状态
|
||||
await axios.put('/api/prices/approve-all', { status: 'approved' })
|
||||
const response = await axios.put('/api/prices/approve-all', { status: 'approved' })
|
||||
|
||||
await loadPrices()
|
||||
ElMessage.success('已通过所有待审核价格')
|
||||
// 使用后端返回的实际审核数量
|
||||
ElMessage.success(`已通过 ${response.data.count} 条待审核价格`)
|
||||
} catch (error) {
|
||||
if (error === 'cancel') return
|
||||
console.error('Failed to approve all pending prices:', error)
|
||||
@ -1176,11 +1107,6 @@ onMounted(() => {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin: 16px 0;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.provider-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -1325,6 +1251,7 @@ onMounted(() => {
|
||||
.el-loading-text {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.path {
|
||||
stroke: #409EFF;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://aimodels-prices.q58.club',
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user