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"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"aimodels-prices/database"
|
"aimodels-prices/database"
|
||||||
|
"aimodels-prices/handlers"
|
||||||
"aimodels-prices/models"
|
"aimodels-prices/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,6 +74,8 @@ func FetchAndSavePrices() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理每个模型的价格数据
|
// 处理每个模型的价格数据
|
||||||
|
processedCount := 0
|
||||||
|
skippedCount := 0
|
||||||
for _, modelData := range openRouterResp.Data {
|
for _, modelData := range openRouterResp.Data {
|
||||||
// 确定模型类型
|
// 确定模型类型
|
||||||
modelType := determineModelType(modelData.Modality)
|
modelType := determineModelType(modelData.Modality)
|
||||||
@ -87,6 +89,7 @@ func FetchAndSavePrices() error {
|
|||||||
inputPrice, err = parsePrice(modelData.Endpoint.Pricing.Prompt)
|
inputPrice, err = parsePrice(modelData.Endpoint.Pricing.Prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("解析endpoint输入价格失败 %s: %v", modelData.Slug, err)
|
log.Printf("解析endpoint输入价格失败 %s: %v", modelData.Slug, err)
|
||||||
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if modelData.Pricing.Prompt != "" {
|
} else if modelData.Pricing.Prompt != "" {
|
||||||
@ -94,6 +97,7 @@ func FetchAndSavePrices() error {
|
|||||||
inputPrice, err = parsePrice(modelData.Pricing.Prompt)
|
inputPrice, err = parsePrice(modelData.Pricing.Prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("解析输入价格失败 %s: %v", modelData.Slug, err)
|
log.Printf("解析输入价格失败 %s: %v", modelData.Slug, err)
|
||||||
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,60 +106,72 @@ func FetchAndSavePrices() error {
|
|||||||
outputPrice, err = parsePrice(modelData.Endpoint.Pricing.Completion)
|
outputPrice, err = parsePrice(modelData.Endpoint.Pricing.Completion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("解析endpoint输出价格失败 %s: %v", modelData.Slug, err)
|
log.Printf("解析endpoint输出价格失败 %s: %v", modelData.Slug, err)
|
||||||
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if modelData.Pricing.Completion != "" {
|
} else if modelData.Pricing.Completion != "" {
|
||||||
outputPrice, err = parsePrice(modelData.Pricing.Completion)
|
outputPrice, err = parsePrice(modelData.Pricing.Completion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("解析输出价格失败 %s: %v", modelData.Slug, err)
|
log.Printf("解析输出价格失败 %s: %v", modelData.Slug, err)
|
||||||
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建价格对象
|
||||||
|
price := models.Price{
|
||||||
|
Model: modelData.Slug,
|
||||||
|
ModelType: modelType,
|
||||||
|
BillingType: BillingType,
|
||||||
|
ChannelType: ChannelType,
|
||||||
|
Currency: Currency,
|
||||||
|
InputPrice: inputPrice,
|
||||||
|
OutputPrice: outputPrice,
|
||||||
|
PriceSource: PriceSource,
|
||||||
|
Status: Status,
|
||||||
|
CreatedBy: CreatedBy,
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否已存在相同模型的价格记录
|
// 检查是否已存在相同模型的价格记录
|
||||||
var existingPrice models.Price
|
var existingPrice models.Price
|
||||||
result := db.Where("model = ? AND channel_type = ?", modelData.Slug, ChannelType).First(&existingPrice)
|
result := db.Where("model = ? AND channel_type = ?", modelData.Slug, ChannelType).First(&existingPrice)
|
||||||
|
|
||||||
if result.Error == nil {
|
if result.Error == nil {
|
||||||
// 更新现有记录
|
// 使用processPrice函数处理更新
|
||||||
existingPrice.ModelType = modelType
|
_, changed, err := handlers.ProcessPrice(price, &existingPrice, true, CreatedBy)
|
||||||
existingPrice.BillingType = BillingType
|
if err != nil {
|
||||||
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)
|
log.Printf("更新价格记录失败 %s: %v", modelData.Slug, err)
|
||||||
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("更新价格记录: %s", modelData.Slug)
|
|
||||||
} else {
|
|
||||||
// 创建新记录
|
|
||||||
newPrice := models.Price{
|
|
||||||
Model: modelData.Slug,
|
|
||||||
ModelType: modelType,
|
|
||||||
BillingType: BillingType,
|
|
||||||
ChannelType: ChannelType,
|
|
||||||
Currency: Currency,
|
|
||||||
InputPrice: inputPrice,
|
|
||||||
OutputPrice: outputPrice,
|
|
||||||
PriceSource: PriceSource,
|
|
||||||
Status: Status,
|
|
||||||
CreatedBy: CreatedBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.Create(&newPrice).Error; err != nil {
|
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)
|
log.Printf("创建价格记录失败 %s: %v", modelData.Slug, err)
|
||||||
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("创建新价格记录: %s", modelData.Slug)
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"aimodels-prices/database"
|
"aimodels-prices/database"
|
||||||
|
"aimodels-prices/handlers"
|
||||||
"aimodels-prices/models"
|
"aimodels-prices/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +28,11 @@ var blacklist = []string{
|
|||||||
"shap-e",
|
"shap-e",
|
||||||
"palm-2",
|
"palm-2",
|
||||||
"o3-mini-high",
|
"o3-mini-high",
|
||||||
|
"claude-instant",
|
||||||
|
"claude-1",
|
||||||
|
"claude-3-haiku",
|
||||||
|
"claude-3-opus",
|
||||||
|
"claude-3-sonnet",
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -94,6 +99,20 @@ func UpdateOtherPrices() error {
|
|||||||
log.Printf("修正Google模型名称: %s -> %s", parts[1], modelName)
|
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)
|
modelType := determineModelType(modelData.Modality)
|
||||||
@ -102,7 +121,14 @@ func UpdateOtherPrices() error {
|
|||||||
var inputPrice, outputPrice float64
|
var inputPrice, outputPrice float64
|
||||||
var parseErr error
|
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 != "" {
|
if modelData.Endpoint.Pricing.Prompt != "" {
|
||||||
inputPrice, parseErr = parsePrice(modelData.Endpoint.Pricing.Prompt)
|
inputPrice, parseErr = parsePrice(modelData.Endpoint.Pricing.Prompt)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
@ -110,14 +136,6 @@ func UpdateOtherPrices() error {
|
|||||||
skippedCount++
|
skippedCount++
|
||||||
continue
|
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 != "" {
|
if modelData.Endpoint.Pricing.Completion != "" {
|
||||||
@ -127,13 +145,20 @@ func UpdateOtherPrices() error {
|
|||||||
skippedCount++
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if modelData.Pricing.Completion != "" {
|
}
|
||||||
outputPrice, parseErr = parsePrice(modelData.Pricing.Completion)
|
|
||||||
if parseErr != nil {
|
// 创建价格对象
|
||||||
log.Printf("解析输出价格失败 %s: %v", modelData.Slug, parseErr)
|
price := models.Price{
|
||||||
skippedCount++
|
Model: modelName,
|
||||||
continue
|
ModelType: modelType,
|
||||||
}
|
BillingType: BillingType,
|
||||||
|
ChannelType: channelType,
|
||||||
|
Currency: Currency,
|
||||||
|
InputPrice: inputPrice,
|
||||||
|
OutputPrice: outputPrice,
|
||||||
|
PriceSource: OtherPriceSource,
|
||||||
|
Status: OtherStatus,
|
||||||
|
CreatedBy: CreatedBy,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已存在相同模型的价格记录
|
// 检查是否已存在相同模型的价格记录
|
||||||
@ -141,45 +166,37 @@ func UpdateOtherPrices() error {
|
|||||||
result := db.Where("model = ? AND channel_type = ?", modelName, channelType).First(&existingPrice)
|
result := db.Where("model = ? AND channel_type = ?", modelName, channelType).First(&existingPrice)
|
||||||
|
|
||||||
if result.Error == nil {
|
if result.Error == nil {
|
||||||
// 更新现有记录
|
// 使用processPrice函数处理更新
|
||||||
existingPrice.ModelType = modelType
|
_, changed, err := handlers.ProcessPrice(price, &existingPrice, false, CreatedBy)
|
||||||
existingPrice.BillingType = BillingType
|
if err != nil {
|
||||||
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)
|
log.Printf("更新价格记录失败 %s: %v", modelName, err)
|
||||||
skippedCount++
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("更新价格记录: %s (厂商: %s)", modelName, author)
|
|
||||||
processedCount++
|
|
||||||
} else {
|
|
||||||
// 创建新记录
|
|
||||||
newPrice := models.Price{
|
|
||||||
Model: modelName,
|
|
||||||
ModelType: modelType,
|
|
||||||
BillingType: BillingType,
|
|
||||||
ChannelType: channelType,
|
|
||||||
Currency: Currency,
|
|
||||||
InputPrice: inputPrice,
|
|
||||||
OutputPrice: outputPrice,
|
|
||||||
PriceSource: OtherPriceSource,
|
|
||||||
Status: OtherStatus,
|
|
||||||
CreatedBy: CreatedBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.Create(&newPrice).Error; err != nil {
|
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)
|
log.Printf("创建价格记录失败 %s: %v", modelName, err)
|
||||||
skippedCount++
|
skippedCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("创建新价格记录: %s (厂商: %s)", modelName, author)
|
|
||||||
processedCount++
|
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") // 厂商筛选参数
|
channelType := c.Query("channel_type") // 厂商筛选参数
|
||||||
modelType := c.Query("model_type") // 模型类型筛选参数
|
modelType := c.Query("model_type") // 模型类型筛选参数
|
||||||
searchQuery := c.Query("search") // 搜索查询参数
|
searchQuery := c.Query("search") // 搜索查询参数
|
||||||
|
status := c.Query("status") // 状态筛选参数
|
||||||
|
|
||||||
if page < 1 {
|
if page < 1 {
|
||||||
page = 1
|
page = 1
|
||||||
@ -31,8 +32,8 @@ func GetPrices(c *gin.Context) {
|
|||||||
offset := (page - 1) * pageSize
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
// 构建缓存键
|
// 构建缓存键
|
||||||
cacheKey := fmt.Sprintf("prices_page_%d_size_%d_channel_%s_type_%s_search_%s",
|
cacheKey := fmt.Sprintf("prices_page_%d_size_%d_channel_%s_type_%s_search_%s_status_%s",
|
||||||
page, pageSize, channelType, modelType, searchQuery)
|
page, pageSize, channelType, modelType, searchQuery, status)
|
||||||
|
|
||||||
// 尝试从缓存获取
|
// 尝试从缓存获取
|
||||||
if cachedData, found := database.GlobalCache.Get(cacheKey); found {
|
if cachedData, found := database.GlobalCache.Get(cacheKey); found {
|
||||||
@ -56,10 +57,15 @@ func GetPrices(c *gin.Context) {
|
|||||||
if searchQuery != "" {
|
if searchQuery != "" {
|
||||||
query = query.Where("model LIKE ?", "%"+searchQuery+"%")
|
query = query.Where("model LIKE ?", "%"+searchQuery+"%")
|
||||||
}
|
}
|
||||||
|
// 添加状态筛选条件
|
||||||
|
if status != "" {
|
||||||
|
query = query.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取总数 - 使用缓存优化
|
// 获取总数 - 使用缓存优化
|
||||||
var total int64
|
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 cachedTotal, found := database.GlobalCache.Get(totalCacheKey); found {
|
||||||
if t, ok := cachedTotal.(int64); ok {
|
if t, ok := cachedTotal.(int64); ok {
|
||||||
@ -97,6 +103,118 @@ func GetPrices(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, result)
|
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) {
|
func CreatePrice(c *gin.Context) {
|
||||||
var price models.Price
|
var price models.Price
|
||||||
if err := c.ShouldBindJSON(&price); err != nil {
|
if err := c.ShouldBindJSON(&price); err != nil {
|
||||||
@ -123,19 +241,27 @@ func CreatePrice(c *gin.Context) {
|
|||||||
return
|
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"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create price"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除所有价格相关缓存
|
// 清除所有价格相关缓存
|
||||||
clearPriceCache()
|
if changed {
|
||||||
|
clearPriceCache()
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, price)
|
c.JSON(http.StatusCreated, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePriceStatus(c *gin.Context) {
|
func UpdatePriceStatus(c *gin.Context) {
|
||||||
@ -209,23 +335,36 @@ func UpdatePriceStatus(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果是拒绝,清除临时字段
|
// 如果是拒绝
|
||||||
if err := tx.Model(&price).Updates(map[string]interface{}{
|
// 检查是否是新创建的价格(没有原始价格)
|
||||||
"status": input.Status,
|
isNewPrice := price.Model == "" || (price.TempModel != nil && price.Model == *price.TempModel)
|
||||||
"updated_at": time.Now(),
|
|
||||||
"temp_model": nil,
|
if isNewPrice {
|
||||||
"temp_model_type": nil,
|
// 如果是新创建的价格,直接删除
|
||||||
"temp_billing_type": nil,
|
if err := tx.Delete(&price).Error; err != nil {
|
||||||
"temp_channel_type": nil,
|
tx.Rollback()
|
||||||
"temp_currency": nil,
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete rejected price"})
|
||||||
"temp_input_price": nil,
|
return
|
||||||
"temp_output_price": nil,
|
}
|
||||||
"temp_price_source": nil,
|
} else {
|
||||||
"updated_by": nil,
|
// 如果是更新的价格,恢复到原始状态(清除临时字段并设置状态为approved)
|
||||||
}).Error; err != nil {
|
if err := tx.Model(&price).Updates(map[string]interface{}{
|
||||||
tx.Rollback()
|
"status": "approved", // 恢复为已批准状态
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update price status"})
|
"updated_at": time.Now(),
|
||||||
return
|
"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 update price status"})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,11 +378,20 @@ func UpdatePriceStatus(c *gin.Context) {
|
|||||||
// 清除所有价格相关缓存
|
// 清除所有价格相关缓存
|
||||||
clearPriceCache()
|
clearPriceCache()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
// 根据操作类型返回不同的消息
|
||||||
"message": "Status updated successfully",
|
if input.Status == "rejected" && (price.Model == "" || (price.TempModel != nil && price.Model == *price.TempModel)) {
|
||||||
"status": input.Status,
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"updated_at": time.Now(),
|
"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) {
|
func UpdatePrice(c *gin.Context) {
|
||||||
@ -288,55 +436,19 @@ func UpdatePrice(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据用户角色决定更新方式
|
// 处理价格更新
|
||||||
if currentUser.Role == "admin" {
|
result, changed, err := ProcessPrice(price, &existingPrice, currentUser.Role == "admin", currentUser.Username)
|
||||||
// 管理员直接更新主字段
|
if err != nil {
|
||||||
existingPrice.Model = price.Model
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update price"})
|
||||||
existingPrice.ModelType = price.ModelType
|
return
|
||||||
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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除所有价格相关缓存
|
// 清除所有价格相关缓存
|
||||||
clearPriceCache()
|
if changed {
|
||||||
|
clearPriceCache()
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, existingPrice)
|
c.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeletePrice(c *gin.Context) {
|
func DeletePrice(c *gin.Context) {
|
||||||
@ -362,6 +474,16 @@ func DeletePrice(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ApproveAllPrices(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
|
var pendingPrices []models.Price
|
||||||
if err := database.DB.Where("status = 'pending'").Find(&pendingPrices).Error; err != nil {
|
if err := database.DB.Where("status = 'pending'").Find(&pendingPrices).Error; err != nil {
|
||||||
@ -371,54 +493,94 @@ func ApproveAllPrices(c *gin.Context) {
|
|||||||
|
|
||||||
// 开始事务
|
// 开始事务
|
||||||
tx := database.DB.Begin()
|
tx := database.DB.Begin()
|
||||||
|
processedCount := 0
|
||||||
|
deletedCount := 0
|
||||||
|
|
||||||
for _, price := range pendingPrices {
|
for _, price := range pendingPrices {
|
||||||
updateMap := map[string]interface{}{
|
if input.Action == "approve" {
|
||||||
"status": "approved",
|
// 批准操作
|
||||||
"updated_at": time.Now(),
|
updateMap := map[string]interface{}{
|
||||||
}
|
"status": "approved",
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
// 如果临时字段有值,则更新主字段
|
// 如果临时字段有值,则更新主字段
|
||||||
if price.TempModel != nil {
|
if price.TempModel != nil {
|
||||||
updateMap["model"] = *price.TempModel
|
updateMap["model"] = *price.TempModel
|
||||||
}
|
}
|
||||||
if price.TempModelType != nil {
|
if price.TempModelType != nil {
|
||||||
updateMap["model_type"] = *price.TempModelType
|
updateMap["model_type"] = *price.TempModelType
|
||||||
}
|
}
|
||||||
if price.TempBillingType != nil {
|
if price.TempBillingType != nil {
|
||||||
updateMap["billing_type"] = *price.TempBillingType
|
updateMap["billing_type"] = *price.TempBillingType
|
||||||
}
|
}
|
||||||
if price.TempChannelType != nil {
|
if price.TempChannelType != nil {
|
||||||
updateMap["channel_type"] = *price.TempChannelType
|
updateMap["channel_type"] = *price.TempChannelType
|
||||||
}
|
}
|
||||||
if price.TempCurrency != nil {
|
if price.TempCurrency != nil {
|
||||||
updateMap["currency"] = *price.TempCurrency
|
updateMap["currency"] = *price.TempCurrency
|
||||||
}
|
}
|
||||||
if price.TempInputPrice != nil {
|
if price.TempInputPrice != nil {
|
||||||
updateMap["input_price"] = *price.TempInputPrice
|
updateMap["input_price"] = *price.TempInputPrice
|
||||||
}
|
}
|
||||||
if price.TempOutputPrice != nil {
|
if price.TempOutputPrice != nil {
|
||||||
updateMap["output_price"] = *price.TempOutputPrice
|
updateMap["output_price"] = *price.TempOutputPrice
|
||||||
}
|
}
|
||||||
if price.TempPriceSource != nil {
|
if price.TempPriceSource != nil {
|
||||||
updateMap["price_source"] = *price.TempPriceSource
|
updateMap["price_source"] = *price.TempPriceSource
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除所有临时字段
|
// 清除所有临时字段
|
||||||
updateMap["temp_model"] = nil
|
updateMap["temp_model"] = nil
|
||||||
updateMap["temp_model_type"] = nil
|
updateMap["temp_model_type"] = nil
|
||||||
updateMap["temp_billing_type"] = nil
|
updateMap["temp_billing_type"] = nil
|
||||||
updateMap["temp_channel_type"] = nil
|
updateMap["temp_channel_type"] = nil
|
||||||
updateMap["temp_currency"] = nil
|
updateMap["temp_currency"] = nil
|
||||||
updateMap["temp_input_price"] = nil
|
updateMap["temp_input_price"] = nil
|
||||||
updateMap["temp_output_price"] = nil
|
updateMap["temp_output_price"] = nil
|
||||||
updateMap["temp_price_source"] = nil
|
updateMap["temp_price_source"] = nil
|
||||||
updateMap["updated_by"] = nil
|
updateMap["updated_by"] = nil
|
||||||
|
|
||||||
if err := tx.Model(&price).Updates(updateMap).Error; err != nil {
|
if err := tx.Model(&price).Updates(updateMap).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to approve prices"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to approve prices"})
|
||||||
return
|
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()
|
clearPriceCache()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
// 根据操作类型返回不同的消息
|
||||||
"message": "All pending prices approved successfully",
|
if input.Action == "approve" {
|
||||||
"count": len(pendingPrices),
|
c.JSON(http.StatusOK, gin.H{
|
||||||
})
|
"message": "All pending prices approved successfully",
|
||||||
|
"count": processedCount,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "All pending prices rejected successfully",
|
||||||
|
"processed": processedCount,
|
||||||
|
"deleted": deletedCount,
|
||||||
|
"total": processedCount + deletedCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearPriceCache 清除所有价格相关的缓存
|
// clearPriceCache 清除所有价格相关的缓存
|
||||||
|
@ -23,39 +23,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 添加搜索框 -->
|
<!-- 添加搜索框 -->
|
||||||
<div class="search-section">
|
<div class="filter-section">
|
||||||
<el-input
|
<div class="filter-label" style="min-width:80px;">搜索模型:</div>
|
||||||
v-model="searchQuery"
|
<div>
|
||||||
placeholder="搜索模型名称"
|
<el-input v-model="searchQuery" placeholder="搜索模型名称" clearable prefix-icon="Search" @input="handleSearch">
|
||||||
clearable
|
<template #prefix>
|
||||||
prefix-icon="Search"
|
<el-icon>
|
||||||
@input="handleSearch"
|
<Search />
|
||||||
>
|
</el-icon>
|
||||||
<template #prefix>
|
</template>
|
||||||
<el-icon><Search /></el-icon>
|
</el-input>
|
||||||
</template>
|
</div>
|
||||||
</el-input>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<div class="filter-label">厂商筛选:</div>
|
<div class="filter-label" style="min-width:80px;">厂商筛选:</div>
|
||||||
<div class="provider-filters">
|
<div class="provider-filters">
|
||||||
<el-button
|
<el-button :type="!selectedProvider ? 'primary' : ''" @click="selectedProvider = ''">全部</el-button>
|
||||||
:type="!selectedProvider ? 'primary' : ''"
|
<el-button v-for="provider in providers" :key="provider.id"
|
||||||
@click="selectedProvider = ''"
|
|
||||||
>全部</el-button>
|
|
||||||
<el-button
|
|
||||||
v-for="provider in providers"
|
|
||||||
:key="provider.id"
|
|
||||||
:type="selectedProvider === provider.id.toString() ? 'primary' : ''"
|
: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">
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
<el-image
|
<el-image v-if="provider.icon" :src="provider.icon" style="width: 16px; height: 16px" />
|
||||||
v-if="provider.icon"
|
|
||||||
:src="provider.icon"
|
|
||||||
style="width: 16px; height: 16px"
|
|
||||||
/>
|
|
||||||
<span>{{ provider.name }}</span>
|
<span>{{ provider.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -63,18 +52,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<div class="filter-label">模型类别:</div>
|
<div class="filter-label" style="min-width:80px;">模型类别:</div>
|
||||||
<div class="model-type-filters">
|
<div class="model-type-filters">
|
||||||
<el-button
|
<el-button :type="!selectedModelType ? 'primary' : ''" @click="selectedModelType = ''">全部</el-button>
|
||||||
:type="!selectedModelType ? 'primary' : ''"
|
<el-button v-for="(label, key) in modelTypeMap" :key="key" :type="selectedModelType === key ? 'primary' : ''"
|
||||||
@click="selectedModelType = ''"
|
@click="selectedModelType = key">
|
||||||
>全部</el-button>
|
|
||||||
<el-button
|
|
||||||
v-for="(label, key) in modelTypeMap"
|
|
||||||
:key="key"
|
|
||||||
:type="selectedModelType === key ? 'primary' : ''"
|
|
||||||
@click="selectedModelType = key"
|
|
||||||
>
|
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -86,14 +68,9 @@
|
|||||||
<el-skeleton :rows="1" animated />
|
<el-skeleton :rows="1" animated />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table
|
<el-table :data="prices" style="width: 100%" @selection-change="handlePriceSelectionChange"
|
||||||
:data="prices"
|
v-loading="tableLoading" element-loading-text="加载中...">
|
||||||
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 v-if="isAdmin" type="selection" width="55" />
|
||||||
<el-table-column label="模型">
|
<el-table-column label="模型">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@ -109,7 +86,8 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
<span>{{ getModelType(row.model_type) }}</span>
|
<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) }}
|
待审核: {{ getModelType(row.temp_model_type) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -119,7 +97,8 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
<span>{{ getBillingType(row.billing_type) }}</span>
|
<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) }}
|
待审核: {{ getBillingType(row.temp_billing_type) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -129,14 +108,12 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
<div style="display: flex; align-items: center; gap: 8px">
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
<el-image
|
<el-image v-if="getProvider(row.channel_type)?.icon" :src="getProvider(row.channel_type)?.icon"
|
||||||
v-if="getProvider(row.channel_type)?.icon"
|
style="width: 24px; height: 24px" />
|
||||||
:src="getProvider(row.channel_type)?.icon"
|
|
||||||
style="width: 24px; height: 24px"
|
|
||||||
/>
|
|
||||||
<span>{{ getProvider(row.channel_type)?.name || row.channel_type }}</span>
|
<span>{{ getProvider(row.channel_type)?.name || row.channel_type }}</span>
|
||||||
</div>
|
</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 }}
|
待审核: {{ getProvider(row.temp_channel_type)?.name || row.temp_channel_type }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -146,7 +123,8 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
<span>{{ row.currency }}</span>
|
<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 }}
|
待审核: {{ row.temp_currency }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -156,7 +134,9 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
<span>{{ row.input_price === 0 ? '免费' : row.input_price }}</span>
|
<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 }}
|
待审核: {{ row.temp_input_price === 0 ? '免费' : row.temp_input_price }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -166,7 +146,9 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
<span>{{ row.output_price === 0 ? '免费' : row.output_price }}</span>
|
<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 }}
|
待审核: {{ row.temp_output_price === 0 ? '免费' : row.temp_output_price }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -174,11 +156,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column width="80">
|
<el-table-column width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-popover
|
<el-popover placement="left" :width="200" trigger="hover">
|
||||||
placement="left"
|
|
||||||
:width="200"
|
|
||||||
trigger="hover"
|
|
||||||
>
|
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button link type="primary">详情</el-button>
|
<el-button link type="primary">详情</el-button>
|
||||||
</template>
|
</template>
|
||||||
@ -191,7 +169,8 @@
|
|||||||
<span class="detail-label">价格来源:</span>
|
<span class="detail-label">价格来源:</span>
|
||||||
<div class="detail-value">
|
<div class="detail-value">
|
||||||
<span>{{ row.price_source }}</span>
|
<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 }}
|
待审核: {{ row.temp_price_source }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@ -210,44 +189,41 @@
|
|||||||
<template v-if="isAdmin">
|
<template v-if="isAdmin">
|
||||||
<el-tooltip content="编辑" placement="top">
|
<el-tooltip content="编辑" placement="top">
|
||||||
<el-button type="primary" link @click="handleEdit(row)">
|
<el-button type="primary" link @click="handleEdit(row)">
|
||||||
<el-icon><Edit /></el-icon>
|
<el-icon>
|
||||||
|
<Edit />
|
||||||
|
</el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="删除" placement="top">
|
<el-tooltip content="删除" placement="top">
|
||||||
<el-button type="danger" link @click="handleDelete(row)">
|
<el-button type="danger" link @click="handleDelete(row)">
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon>
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip :content="row.status === 'pending' ? '通过审核' : '已审核'" placement="top">
|
<el-tooltip :content="row.status === 'pending' ? '通过审核' : '已审核'" placement="top">
|
||||||
<el-button
|
<el-button type="success" link @click="updateStatus(row.id, 'approved')"
|
||||||
type="success"
|
:disabled="row.status !== 'pending'">
|
||||||
link
|
<el-icon>
|
||||||
@click="updateStatus(row.id, 'approved')"
|
<Check />
|
||||||
:disabled="row.status !== 'pending'"
|
</el-icon>
|
||||||
>
|
|
||||||
<el-icon><Check /></el-icon>
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip :content="row.status === 'pending' ? '拒绝审核' : '已审核'" placement="top">
|
<el-tooltip :content="row.status === 'pending' ? '拒绝审核' : '已审核'" placement="top">
|
||||||
<el-button
|
<el-button type="danger" link @click="updateStatus(row.id, 'rejected')"
|
||||||
type="danger"
|
:disabled="row.status !== 'pending'">
|
||||||
link
|
<el-icon>
|
||||||
@click="updateStatus(row.id, 'rejected')"
|
<Close />
|
||||||
:disabled="row.status !== 'pending'"
|
</el-icon>
|
||||||
>
|
|
||||||
<el-icon><Close /></el-icon>
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-tooltip :content="row.status === 'pending' ? '等待审核中' : '提交修改'" placement="top">
|
<el-tooltip :content="row.status === 'pending' ? '等待审核中' : '提交修改'" placement="top">
|
||||||
<el-button
|
<el-button type="primary" link @click="handleQuickEdit(row)" :disabled="row.status === 'pending'">
|
||||||
type="primary"
|
<el-icon>
|
||||||
link
|
<Edit />
|
||||||
@click="handleQuickEdit(row)"
|
</el-icon>
|
||||||
:disabled="row.status === 'pending'"
|
|
||||||
>
|
|
||||||
<el-icon><Edit /></el-icon>
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@ -255,21 +231,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 修改分页组件 -->
|
<!-- 修改分页组件 -->
|
||||||
<div class="pagination-container">
|
<div class="pagination-container">
|
||||||
<el-pagination
|
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[10, 20, 50, 100]"
|
||||||
v-model:current-page="currentPage"
|
:total="total" layout="total, sizes, prev, pager, next" :small="false" @size-change="handleSizeChange"
|
||||||
v-model:page-size="pageSize"
|
@current-change="handleCurrentChange">
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
:total="total"
|
|
||||||
layout="total, sizes, prev, pager, next"
|
|
||||||
:small="false"
|
|
||||||
@size-change="handleSizeChange"
|
|
||||||
@current-change="handleCurrentChange"
|
|
||||||
>
|
|
||||||
<template #sizes>
|
<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>
|
<template #prefix>每页</template>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
@ -283,48 +253,39 @@
|
|||||||
<div class="batch-toolbar">
|
<div class="batch-toolbar">
|
||||||
<el-button type="primary" @click="addRow">添加行</el-button>
|
<el-button type="primary" @click="addRow">添加行</el-button>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-popover
|
<el-popover placement="bottom" :width="400" trigger="click">
|
||||||
placement="bottom"
|
|
||||||
:width="400"
|
|
||||||
trigger="click"
|
|
||||||
>
|
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button type="success">从表格导入</el-button>
|
<el-button type="success">从表格导入</el-button>
|
||||||
</template>
|
</template>
|
||||||
<div class="import-popover">
|
<div class="import-popover">
|
||||||
<p class="import-tip">请粘贴表格数据(支持从Excel复制),每行格式为:</p>
|
<p class="import-tip">请粘贴表格数据(支持从Excel复制),每行格式为:</p>
|
||||||
<p class="import-format">模型名称 计费类型 厂商 货币 输入价格 输出价格</p>
|
<p class="import-format">模型名称 计费类型 厂商 货币 输入价格 输出价格</p>
|
||||||
<el-input
|
<el-input v-model="importText" type="textarea" :rows="8" placeholder="例如:
|
||||||
v-model="importText"
|
|
||||||
type="textarea"
|
|
||||||
:rows="8"
|
|
||||||
placeholder="例如:
|
|
||||||
dall-e-2 按Token收费 OpenAI 美元 16.000000 16.000000
|
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">
|
<div class="import-actions">
|
||||||
<el-button type="primary" @click="handleImport">导入</el-button>
|
<el-button type="primary" @click="handleImport">导入</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table :data="batchForms" style="width: 100%" height="400">
|
||||||
:data="batchForms"
|
|
||||||
style="width: 100%"
|
|
||||||
height="400"
|
|
||||||
>
|
|
||||||
<el-table-column label="操作" width="100">
|
<el-table-column label="操作" width="100">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row, $index }">
|
||||||
<div class="row-actions">
|
<div class="row-actions">
|
||||||
<el-tooltip content="复制" placement="top">
|
<el-tooltip content="复制" placement="top">
|
||||||
<el-button type="primary" link @click="duplicateRow($index)">
|
<el-button type="primary" link @click="duplicateRow($index)">
|
||||||
<el-icon><Document /></el-icon>
|
<el-icon>
|
||||||
|
<Document />
|
||||||
|
</el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="删除" placement="top">
|
<el-tooltip content="删除" placement="top">
|
||||||
<el-button type="danger" link @click="removeRow($index)">
|
<el-button type="danger" link @click="removeRow($index)">
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon>
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -337,19 +298,9 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="模型类型" width="120">
|
<el-table-column label="模型类型" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select
|
<el-select v-model="row.model_type" placeholder="请选择或输入" allow-create filterable
|
||||||
v-model="row.model_type"
|
@create="handleModelTypeCreate">
|
||||||
placeholder="请选择或输入"
|
<el-option v-for="(label, value) in modelTypeMap" :key="value" :label="label" :value="value" />
|
||||||
allow-create
|
|
||||||
filterable
|
|
||||||
@create="handleModelTypeCreate"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="(label, value) in modelTypeMap"
|
|
||||||
:key="value"
|
|
||||||
:label="label"
|
|
||||||
:value="value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -364,18 +315,10 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
|||||||
<el-table-column label="模型厂商" width="180">
|
<el-table-column label="模型厂商" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select v-model="row.channel_type" placeholder="请选择">
|
<el-select v-model="row.channel_type" placeholder="请选择">
|
||||||
<el-option
|
<el-option v-for="provider in providers" :key="provider.id" :label="provider.name"
|
||||||
v-for="provider in providers"
|
:value="provider.id.toString()">
|
||||||
:key="provider.id"
|
|
||||||
:label="provider.name"
|
|
||||||
:value="provider.id.toString()"
|
|
||||||
>
|
|
||||||
<div style="display: flex; align-items: center; gap: 8px">
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
<el-image
|
<el-image v-if="provider.icon" :src="provider.icon" style="width: 24px; height: 24px" />
|
||||||
v-if="provider.icon"
|
|
||||||
:src="provider.icon"
|
|
||||||
style="width: 24px; height: 24px"
|
|
||||||
/>
|
|
||||||
<span>{{ provider.name }}</span>
|
<span>{{ provider.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-option>
|
</el-option>
|
||||||
@ -392,12 +335,14 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="输入价格(M)" width="150">
|
<el-table-column label="输入价格(M)" width="150">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="输出价格(M)" width="150">
|
<el-table-column label="输出价格(M)" width="150">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="价格来源" min-width="200" width="200">
|
<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>
|
||||||
|
|
||||||
<!-- 现有的单个添加对话框 -->
|
<!-- 现有的单个添加对话框 -->
|
||||||
<el-dialog
|
<el-dialog v-model="dialogVisible" :title="editingPrice ? (isAdmin ? '编辑价格' : '提交价格修改') : '提交价格'" width="700px">
|
||||||
v-model="dialogVisible"
|
|
||||||
:title="editingPrice ? (isAdmin ? '编辑价格' : '提交价格修改') : '提交价格'"
|
|
||||||
width="700px"
|
|
||||||
>
|
|
||||||
<el-form :model="form" label-width="100px">
|
<el-form :model="form" label-width="100px">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@ -432,19 +373,9 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="模型类型">
|
<el-form-item label="模型类型">
|
||||||
<el-select
|
<el-select v-model="form.model_type" placeholder="请选择或输入" allow-create filterable
|
||||||
v-model="form.model_type"
|
@create="handleModelTypeCreate">
|
||||||
placeholder="请选择或输入"
|
<el-option v-for="(label, value) in modelTypeMap" :key="value" :label="label" :value="value" />
|
||||||
allow-create
|
|
||||||
filterable
|
|
||||||
@create="handleModelTypeCreate"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="(label, value) in modelTypeMap"
|
|
||||||
:key="value"
|
|
||||||
:label="label"
|
|
||||||
:value="value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -459,18 +390,10 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
|||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="模型厂商">
|
<el-form-item label="模型厂商">
|
||||||
<el-select v-model="form.channel_type" placeholder="请选择">
|
<el-select v-model="form.channel_type" placeholder="请选择">
|
||||||
<el-option
|
<el-option v-for="provider in providers" :key="provider.id" :label="provider.name"
|
||||||
v-for="provider in providers"
|
:value="provider.id.toString()">
|
||||||
:key="provider.id"
|
|
||||||
:label="provider.name"
|
|
||||||
:value="provider.id.toString()"
|
|
||||||
>
|
|
||||||
<div style="display: flex; align-items: center; gap: 8px">
|
<div style="display: flex; align-items: center; gap: 8px">
|
||||||
<el-image
|
<el-image v-if="provider.icon" :src="provider.icon" style="width: 24px; height: 24px" />
|
||||||
v-if="provider.icon"
|
|
||||||
:src="provider.icon"
|
|
||||||
style="width: 24px; height: 24px"
|
|
||||||
/>
|
|
||||||
<span>{{ provider.name }}</span>
|
<span>{{ provider.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-option>
|
</el-option>
|
||||||
@ -487,12 +410,14 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="输入价格(M)">
|
<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-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="输出价格(M)">
|
<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-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
@ -590,13 +515,13 @@ const cachedPrices = ref(new Map()) // 用于缓存数据
|
|||||||
|
|
||||||
const loadPrices = async () => {
|
const loadPrices = async () => {
|
||||||
tableLoading.value = true
|
tableLoading.value = true
|
||||||
|
|
||||||
// 构建查询参数
|
// 构建查询参数
|
||||||
const params = {
|
const params = {
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value
|
pageSize: pageSize.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加筛选参数
|
// 添加筛选参数
|
||||||
if (selectedProvider.value) {
|
if (selectedProvider.value) {
|
||||||
params.channel_type = selectedProvider.value
|
params.channel_type = selectedProvider.value
|
||||||
@ -608,24 +533,24 @@ const loadPrices = async () => {
|
|||||||
if (searchQuery.value) {
|
if (searchQuery.value) {
|
||||||
params.search = searchQuery.value
|
params.search = searchQuery.value
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [pricesRes, providersRes] = await Promise.all([
|
const [pricesRes, providersRes] = await Promise.all([
|
||||||
axios.get('/api/prices', { params }),
|
axios.get('/api/prices', { params }),
|
||||||
axios.get('/api/providers')
|
axios.get('/api/providers')
|
||||||
])
|
])
|
||||||
|
|
||||||
prices.value = pricesRes.data.data
|
prices.value = pricesRes.data.data
|
||||||
total.value = pricesRes.data.total
|
total.value = pricesRes.data.total
|
||||||
providers.value = providersRes.data
|
providers.value = providersRes.data
|
||||||
|
|
||||||
// 缓存数据
|
// 缓存数据
|
||||||
const cacheKey = `${currentPage.value}-${pageSize.value}-${selectedProvider.value}-${selectedModelType.value}-${searchQuery.value}`
|
const cacheKey = `${currentPage.value}-${pageSize.value}-${selectedProvider.value}-${selectedModelType.value}-${searchQuery.value}`
|
||||||
cachedPrices.value.set(cacheKey, {
|
cachedPrices.value.set(cacheKey, {
|
||||||
prices: pricesRes.data.data,
|
prices: pricesRes.data.data,
|
||||||
total: pricesRes.data.total
|
total: pricesRes.data.total
|
||||||
})
|
})
|
||||||
|
|
||||||
// 限制缓存大小
|
// 限制缓存大小
|
||||||
if (cachedPrices.value.size > 10) {
|
if (cachedPrices.value.size > 10) {
|
||||||
const firstKey = cachedPrices.value.keys().next().value
|
const firstKey = cachedPrices.value.keys().next().value
|
||||||
@ -700,7 +625,7 @@ const handleQuickEdit = (row) => {
|
|||||||
}
|
}
|
||||||
editingPrice.value = row
|
editingPrice.value = row
|
||||||
// 复制现有数据作为修改建议的基础
|
// 复制现有数据作为修改建议的基础
|
||||||
form.value = {
|
form.value = {
|
||||||
model: row.model,
|
model: row.model,
|
||||||
model_type: row.model_type,
|
model_type: row.model_type,
|
||||||
billing_type: row.billing_type,
|
billing_type: row.billing_type,
|
||||||
@ -717,21 +642,21 @@ const handleQuickEdit = (row) => {
|
|||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
try {
|
try {
|
||||||
form.value.created_by = props.user.username
|
form.value.created_by = props.user.username
|
||||||
|
|
||||||
// 创建一个新对象用于提交,将 channel_type 转换为数字类型
|
// 创建一个新对象用于提交,将 channel_type 转换为数字类型
|
||||||
const formToSubmit = { ...form.value }
|
const formToSubmit = { ...form.value }
|
||||||
if (formToSubmit.channel_type) {
|
if (formToSubmit.channel_type) {
|
||||||
formToSubmit.channel_type = parseInt(formToSubmit.channel_type, 10)
|
formToSubmit.channel_type = parseInt(formToSubmit.channel_type, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
let response
|
let response
|
||||||
if (editingPrice.value) {
|
if (editingPrice.value) {
|
||||||
// 更新已存在的价格
|
// 更新已存在的价格
|
||||||
response = await axios.put(`/api/prices/${editingPrice.value.id}`, formToSubmit)
|
response = await axios.put(`/api/prices/${editingPrice.value.id}`, formToSubmit)
|
||||||
} else {
|
} else {
|
||||||
// 检查模型是否已存在
|
// 检查模型是否已存在
|
||||||
const existingPrice = prices.value?.find(p =>
|
const existingPrice = prices.value?.find(p =>
|
||||||
p.model === form.value.model &&
|
p.model === form.value.model &&
|
||||||
p.channel_type === form.value.channel_type
|
p.channel_type === form.value.channel_type
|
||||||
)
|
)
|
||||||
if (existingPrice) {
|
if (existingPrice) {
|
||||||
@ -840,7 +765,7 @@ const handleModelTypeCreate = async (value) => {
|
|||||||
if (existingKey) {
|
if (existingKey) {
|
||||||
return existingKey
|
return existingKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果输入的是英文key,直接使用
|
// 如果输入的是英文key,直接使用
|
||||||
let type_key = value
|
let type_key = value
|
||||||
let type_label = value
|
let type_label = value
|
||||||
@ -910,10 +835,10 @@ const submitBatchForms = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证数据
|
// 验证数据
|
||||||
const invalidForms = batchForms.value.filter(form =>
|
const invalidForms = batchForms.value.filter(form =>
|
||||||
!form.model || !form.channel_type || !form.price_source
|
!form.model || !form.channel_type || !form.price_source
|
||||||
)
|
)
|
||||||
|
|
||||||
if (invalidForms.length) {
|
if (invalidForms.length) {
|
||||||
ElMessage.error('请填写完整所有必填字段')
|
ElMessage.error('请填写完整所有必填字段')
|
||||||
return
|
return
|
||||||
@ -930,7 +855,7 @@ const submitBatchForms = async () => {
|
|||||||
}
|
}
|
||||||
await axios.post('/api/prices', formToSubmit)
|
await axios.post('/api/prices', formToSubmit)
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadPrices()
|
await loadPrices()
|
||||||
batchDialogVisible.value = false
|
batchDialogVisible.value = false
|
||||||
ElMessage.success('批量添加成功')
|
ElMessage.success('批量添加成功')
|
||||||
@ -966,7 +891,7 @@ const handleImport = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [model, billingType, providerName, currency, inputPrice, outputPrice] = parts
|
const [model, billingType, providerName, currency, inputPrice, outputPrice] = parts
|
||||||
|
|
||||||
// 查找模型厂商ID
|
// 查找模型厂商ID
|
||||||
const provider = providers.value.find(p => p.name === providerName)
|
const provider = providers.value.find(p => p.name === providerName)
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
@ -1109,14 +1034,19 @@ const removeRow = (index) => {
|
|||||||
const approveAllPending = async () => {
|
const approveAllPending = async () => {
|
||||||
try {
|
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
|
const pendingCount = data.total
|
||||||
|
|
||||||
if (pendingCount === 0) {
|
if (pendingCount === 0) {
|
||||||
ElMessage.info('当前没有待审核的价格')
|
ElMessage.info('当前没有待审核的价格')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认操作
|
// 确认操作
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
`确定要通过所有 ${pendingCount} 条待审核价格吗?`,
|
`确定要通过所有 ${pendingCount} 条待审核价格吗?`,
|
||||||
@ -1127,12 +1057,13 @@ const approveAllPending = async () => {
|
|||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 批量更新所有待审核价格的状态
|
// 批量更新所有待审核价格的状态
|
||||||
await axios.put('/api/prices/approve-all', { status: 'approved' })
|
const response = await axios.put('/api/prices/approve-all', { status: 'approved' })
|
||||||
|
|
||||||
await loadPrices()
|
await loadPrices()
|
||||||
ElMessage.success('已通过所有待审核价格')
|
// 使用后端返回的实际审核数量
|
||||||
|
ElMessage.success(`已通过 ${response.data.count} 条待审核价格`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error === 'cancel') return
|
if (error === 'cancel') return
|
||||||
console.error('Failed to approve all pending prices:', error)
|
console.error('Failed to approve all pending prices:', error)
|
||||||
@ -1176,11 +1107,6 @@ onMounted(() => {
|
|||||||
color: #606266;
|
color: #606266;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-section {
|
|
||||||
margin: 16px 0;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.provider-filters {
|
.provider-filters {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -1325,6 +1251,7 @@ onMounted(() => {
|
|||||||
.el-loading-text {
|
.el-loading-text {
|
||||||
color: #409EFF;
|
color: #409EFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.path {
|
.path {
|
||||||
stroke: #409EFF;
|
stroke: #409EFF;
|
||||||
}
|
}
|
||||||
@ -1355,25 +1282,25 @@ onMounted(() => {
|
|||||||
width: auto !important;
|
width: auto !important;
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select .el-input {
|
.el-select .el-input {
|
||||||
width: 140px !important;
|
width: 140px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select-dropdown__item {
|
.el-select-dropdown__item {
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-pagination__sizes {
|
.el-pagination__sizes {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修复选择框宽度问题 */
|
/* 修复选择框宽度问题 */
|
||||||
.el-select__wrapper {
|
.el-select__wrapper {
|
||||||
min-width: 140px !important;
|
min-width: 140px !important;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 确保下拉菜单也足够宽 */
|
/* 确保下拉菜单也足够宽 */
|
||||||
.el-select__popper {
|
.el-select__popper {
|
||||||
min-width: 140px !important;
|
min-width: 140px !important;
|
||||||
@ -1425,4 +1352,4 @@ onMounted(() => {
|
|||||||
.el-select-dropdown {
|
.el-select-dropdown {
|
||||||
min-width: 140px !important;
|
min-width: 140px !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -7,7 +7,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://aimodels-prices.q58.club',
|
target: 'http://localhost:8080',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user