mirror of
https://github.com/woodchen-ink/aimodels-prices.git
synced 2025-07-18 05:32:00 +08:00
删除价格处理相关的旧代码,更新价格模型以支持新的扩展价格字段,优化前端展示逻辑,提升用户体验和代码可维护性。
This commit is contained in:
parent
817f51b75a
commit
eedee45861
@ -11,13 +11,28 @@ import (
|
||||
"aimodels-prices/models"
|
||||
)
|
||||
|
||||
// ExtraRatios 扩展价格倍率结构
|
||||
type ExtraRatios struct {
|
||||
InputAudioTokens *float64 `json:"input_audio_tokens,omitempty"`
|
||||
OutputAudioTokens *float64 `json:"output_audio_tokens,omitempty"`
|
||||
CachedTokens *float64 `json:"cached_tokens,omitempty"`
|
||||
CachedReadTokens *float64 `json:"cached_read_tokens,omitempty"`
|
||||
CachedWriteTokens *float64 `json:"cached_write_tokens,omitempty"`
|
||||
ReasoningTokens *float64 `json:"reasoning_tokens,omitempty"`
|
||||
InputTextTokens *float64 `json:"input_text_tokens,omitempty"`
|
||||
OutputTextTokens *float64 `json:"output_text_tokens,omitempty"`
|
||||
InputImageTokens *float64 `json:"input_image_tokens,omitempty"`
|
||||
OutputImageTokens *float64 `json:"output_image_tokens,omitempty"`
|
||||
}
|
||||
|
||||
// PriceRate 价格倍率结构
|
||||
type PriceRate struct {
|
||||
Model string `json:"model"`
|
||||
Type string `json:"type"`
|
||||
ChannelType uint `json:"channel_type"`
|
||||
Input float64 `json:"input"`
|
||||
Output float64 `json:"output"`
|
||||
Model string `json:"model"`
|
||||
Type string `json:"type"`
|
||||
ChannelType uint `json:"channel_type"`
|
||||
Input float64 `json:"input"`
|
||||
Output float64 `json:"output"`
|
||||
ExtraRatios *ExtraRatios `json:"extra_ratios,omitempty"`
|
||||
}
|
||||
|
||||
// GetPriceRates 获取价格倍率
|
||||
@ -34,7 +49,7 @@ func GetPriceRates(c *gin.Context) {
|
||||
|
||||
// 使用索引优化查询,只查询需要的字段
|
||||
var prices []models.Price
|
||||
if err := database.DB.Select("model, billing_type, channel_type, input_price, output_price, currency, status").
|
||||
if err := database.DB.Select("model, billing_type, channel_type, input_price, output_price, currency, status, input_audio_tokens, output_audio_tokens, cached_tokens, cached_read_tokens, cached_write_tokens, reasoning_tokens, input_text_tokens, output_text_tokens, input_image_tokens, output_image_tokens").
|
||||
Where("status = 'approved'").
|
||||
Find(&prices).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch prices"})
|
||||
@ -59,6 +74,119 @@ func GetPriceRates(c *gin.Context) {
|
||||
outputRate = round(price.OutputPrice/14, 4)
|
||||
}
|
||||
|
||||
// 创建额外价格倍率
|
||||
var extraRatios *ExtraRatios
|
||||
|
||||
// 只有当至少有一个扩展价格字段不为nil时才创建ExtraRatios
|
||||
if price.InputAudioTokens != nil || price.OutputAudioTokens != nil ||
|
||||
price.CachedTokens != nil || price.CachedReadTokens != nil || price.CachedWriteTokens != nil ||
|
||||
price.ReasoningTokens != nil || price.InputTextTokens != nil || price.OutputTextTokens != nil ||
|
||||
price.InputImageTokens != nil || price.OutputImageTokens != nil {
|
||||
|
||||
extraRatios = &ExtraRatios{}
|
||||
|
||||
// 计算各扩展价格字段的倍率
|
||||
if price.InputAudioTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.InputAudioTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.InputAudioTokens/14, 4)
|
||||
}
|
||||
extraRatios.InputAudioTokens = &rate
|
||||
}
|
||||
|
||||
if price.OutputAudioTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.OutputAudioTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.OutputAudioTokens/14, 4)
|
||||
}
|
||||
extraRatios.OutputAudioTokens = &rate
|
||||
}
|
||||
|
||||
if price.CachedTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.CachedTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.CachedTokens/14, 4)
|
||||
}
|
||||
extraRatios.CachedTokens = &rate
|
||||
}
|
||||
|
||||
if price.CachedReadTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.CachedReadTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.CachedReadTokens/14, 4)
|
||||
}
|
||||
extraRatios.CachedReadTokens = &rate
|
||||
}
|
||||
|
||||
if price.CachedWriteTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.CachedWriteTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.CachedWriteTokens/14, 4)
|
||||
}
|
||||
extraRatios.CachedWriteTokens = &rate
|
||||
}
|
||||
|
||||
if price.ReasoningTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.ReasoningTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.ReasoningTokens/14, 4)
|
||||
}
|
||||
extraRatios.ReasoningTokens = &rate
|
||||
}
|
||||
|
||||
if price.InputTextTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.InputTextTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.InputTextTokens/14, 4)
|
||||
}
|
||||
extraRatios.InputTextTokens = &rate
|
||||
}
|
||||
|
||||
if price.OutputTextTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.OutputTextTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.OutputTextTokens/14, 4)
|
||||
}
|
||||
extraRatios.OutputTextTokens = &rate
|
||||
}
|
||||
|
||||
if price.InputImageTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.InputImageTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.InputImageTokens/14, 4)
|
||||
}
|
||||
extraRatios.InputImageTokens = &rate
|
||||
}
|
||||
|
||||
if price.OutputImageTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.OutputImageTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.OutputImageTokens/14, 4)
|
||||
}
|
||||
extraRatios.OutputImageTokens = &rate
|
||||
}
|
||||
}
|
||||
|
||||
// 创建当前价格的PriceRate
|
||||
currentRate := PriceRate{
|
||||
Model: price.Model,
|
||||
@ -66,6 +194,7 @@ func GetPriceRates(c *gin.Context) {
|
||||
ChannelType: price.ChannelType,
|
||||
Input: inputRate,
|
||||
Output: outputRate,
|
||||
ExtraRatios: extraRatios,
|
||||
}
|
||||
|
||||
// 转换为小写以实现不区分大小写比较
|
||||
@ -115,7 +244,7 @@ func GetOfficialPriceRates(c *gin.Context) {
|
||||
// 使用索引优化查询,只查询需要的字段,并添加厂商ID筛选条件
|
||||
var prices []models.Price
|
||||
result := database.DB.Model(&models.Price{}).
|
||||
Select("model", "billing_type", "channel_type", "input_price", "output_price", "currency", "status").
|
||||
Select("model, billing_type, channel_type, input_price, output_price, currency, status, input_audio_tokens, output_audio_tokens, cached_tokens, cached_read_tokens, cached_write_tokens, reasoning_tokens, input_text_tokens, output_text_tokens, input_image_tokens, output_image_tokens").
|
||||
Where(&models.Price{Status: "approved"}).
|
||||
Where("channel_type < ?", 1000).
|
||||
Find(&prices)
|
||||
@ -143,6 +272,119 @@ func GetOfficialPriceRates(c *gin.Context) {
|
||||
outputRate = round(price.OutputPrice/14, 4)
|
||||
}
|
||||
|
||||
// 创建额外价格倍率
|
||||
var extraRatios *ExtraRatios
|
||||
|
||||
// 只有当至少有一个扩展价格字段不为nil时才创建ExtraRatios
|
||||
if price.InputAudioTokens != nil || price.OutputAudioTokens != nil ||
|
||||
price.CachedTokens != nil || price.CachedReadTokens != nil || price.CachedWriteTokens != nil ||
|
||||
price.ReasoningTokens != nil || price.InputTextTokens != nil || price.OutputTextTokens != nil ||
|
||||
price.InputImageTokens != nil || price.OutputImageTokens != nil {
|
||||
|
||||
extraRatios = &ExtraRatios{}
|
||||
|
||||
// 计算各扩展价格字段的倍率
|
||||
if price.InputAudioTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.InputAudioTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.InputAudioTokens/14, 4)
|
||||
}
|
||||
extraRatios.InputAudioTokens = &rate
|
||||
}
|
||||
|
||||
if price.OutputAudioTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.OutputAudioTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.OutputAudioTokens/14, 4)
|
||||
}
|
||||
extraRatios.OutputAudioTokens = &rate
|
||||
}
|
||||
|
||||
if price.CachedTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.CachedTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.CachedTokens/14, 4)
|
||||
}
|
||||
extraRatios.CachedTokens = &rate
|
||||
}
|
||||
|
||||
if price.CachedReadTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.CachedReadTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.CachedReadTokens/14, 4)
|
||||
}
|
||||
extraRatios.CachedReadTokens = &rate
|
||||
}
|
||||
|
||||
if price.CachedWriteTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.CachedWriteTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.CachedWriteTokens/14, 4)
|
||||
}
|
||||
extraRatios.CachedWriteTokens = &rate
|
||||
}
|
||||
|
||||
if price.ReasoningTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.ReasoningTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.ReasoningTokens/14, 4)
|
||||
}
|
||||
extraRatios.ReasoningTokens = &rate
|
||||
}
|
||||
|
||||
if price.InputTextTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.InputTextTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.InputTextTokens/14, 4)
|
||||
}
|
||||
extraRatios.InputTextTokens = &rate
|
||||
}
|
||||
|
||||
if price.OutputTextTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.OutputTextTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.OutputTextTokens/14, 4)
|
||||
}
|
||||
extraRatios.OutputTextTokens = &rate
|
||||
}
|
||||
|
||||
if price.InputImageTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.InputImageTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.InputImageTokens/14, 4)
|
||||
}
|
||||
extraRatios.InputImageTokens = &rate
|
||||
}
|
||||
|
||||
if price.OutputImageTokens != nil {
|
||||
var rate float64
|
||||
if price.Currency == "USD" {
|
||||
rate = round(*price.OutputImageTokens/2, 4)
|
||||
} else {
|
||||
rate = round(*price.OutputImageTokens/14, 4)
|
||||
}
|
||||
extraRatios.OutputImageTokens = &rate
|
||||
}
|
||||
}
|
||||
|
||||
// 创建当前价格的PriceRate
|
||||
currentRate := PriceRate{
|
||||
Model: price.Model,
|
||||
@ -150,6 +392,7 @@ func GetOfficialPriceRates(c *gin.Context) {
|
||||
ChannelType: price.ChannelType,
|
||||
Input: inputRate,
|
||||
Output: outputRate,
|
||||
ExtraRatios: extraRatios,
|
||||
}
|
||||
|
||||
// 转换为小写以实现不区分大小写比较
|
||||
|
@ -1,167 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"aimodels-prices/database"
|
||||
"aimodels-prices/models"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 在createPrice函数中添加新字段的处理
|
||||
func createPrice(c *gin.Context) {
|
||||
var price models.Price
|
||||
if err := c.ShouldBindJSON(&price); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置创建时间和状态
|
||||
price.Status = "pending"
|
||||
price.CreatedAt = time.Now()
|
||||
|
||||
// 验证必填字段
|
||||
if price.Model == "" || price.ModelType == "" || price.BillingType == "" ||
|
||||
price.Currency == "" || price.CreatedBy == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "必填字段不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证扩展价格字段(如果提供)
|
||||
if price.InputAudioTokens != nil && *price.InputAudioTokens < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "音频输入倍率不能为负数"})
|
||||
return
|
||||
}
|
||||
if price.CachedReadTokens != nil && *price.CachedReadTokens < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "缓存读取倍率不能为负数"})
|
||||
return
|
||||
}
|
||||
if price.ReasoningTokens != nil && *price.ReasoningTokens < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "推理倍率不能为负数"})
|
||||
return
|
||||
}
|
||||
if price.InputTextTokens != nil && *price.InputTextTokens < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "输入文本倍率不能为负数"})
|
||||
return
|
||||
}
|
||||
if price.OutputTextTokens != nil && *price.OutputTextTokens < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "输出文本倍率不能为负数"})
|
||||
return
|
||||
}
|
||||
if price.InputImageTokens != nil && *price.InputImageTokens < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "输入图片倍率不能为负数"})
|
||||
return
|
||||
}
|
||||
if price.OutputImageTokens != nil && *price.OutputImageTokens < 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "输出图片倍率不能为负数"})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建价格记录
|
||||
if err := database.DB.Create(&price).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, price)
|
||||
}
|
||||
|
||||
// 在updatePrice函数中添加新字段的处理
|
||||
func updatePrice(c *gin.Context) {
|
||||
var price models.Price
|
||||
if err := c.ShouldBindJSON(&price); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取现有价格记录
|
||||
var existingPrice models.Price
|
||||
if err := database.DB.First(&existingPrice, price.ID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "价格记录不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新临时字段
|
||||
updates := map[string]interface{}{
|
||||
"temp_model": price.Model,
|
||||
"temp_model_type": price.ModelType,
|
||||
"temp_billing_type": price.BillingType,
|
||||
"temp_channel_type": price.ChannelType,
|
||||
"temp_currency": price.Currency,
|
||||
"temp_input_price": price.InputPrice,
|
||||
"temp_output_price": price.OutputPrice,
|
||||
"temp_input_audio_tokens": price.InputAudioTokens,
|
||||
"temp_cached_read_tokens": price.CachedReadTokens,
|
||||
"temp_reasoning_tokens": price.ReasoningTokens,
|
||||
"temp_input_text_tokens": price.InputTextTokens,
|
||||
"temp_output_text_tokens": price.OutputTextTokens,
|
||||
"temp_input_image_tokens": price.InputImageTokens,
|
||||
"temp_output_image_tokens": price.OutputImageTokens,
|
||||
"temp_price_source": price.PriceSource,
|
||||
"updated_by": price.UpdatedBy,
|
||||
"status": "pending",
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&existingPrice).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, existingPrice)
|
||||
}
|
||||
|
||||
// 在approvePrice函数中添加新字段的处理
|
||||
func approvePrice(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var price models.Price
|
||||
if err := database.DB.First(&price, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "价格记录不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
updates := map[string]interface{}{
|
||||
"model": price.TempModel,
|
||||
"model_type": price.TempModelType,
|
||||
"billing_type": price.TempBillingType,
|
||||
"channel_type": price.TempChannelType,
|
||||
"currency": price.TempCurrency,
|
||||
"input_price": price.TempInputPrice,
|
||||
"output_price": price.TempOutputPrice,
|
||||
"input_audio_tokens": price.TempInputAudioTokens,
|
||||
"cached_read_tokens": price.TempCachedReadTokens,
|
||||
"reasoning_tokens": price.TempReasoningTokens,
|
||||
"input_text_tokens": price.TempInputTextTokens,
|
||||
"output_text_tokens": price.TempOutputTextTokens,
|
||||
"input_image_tokens": price.TempInputImageTokens,
|
||||
"output_image_tokens": price.TempOutputImageTokens,
|
||||
"price_source": price.TempPriceSource,
|
||||
"status": "approved",
|
||||
// 清空临时字段
|
||||
"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_input_audio_tokens": nil,
|
||||
"temp_cached_read_tokens": nil,
|
||||
"temp_reasoning_tokens": nil,
|
||||
"temp_input_text_tokens": nil,
|
||||
"temp_output_text_tokens": nil,
|
||||
"temp_input_image_tokens": nil,
|
||||
"temp_output_image_tokens": nil,
|
||||
"temp_price_source": nil,
|
||||
"updated_by": nil,
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&price).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, price)
|
||||
}
|
@ -115,6 +115,17 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
return math.Abs(a-b) < epsilon
|
||||
}
|
||||
|
||||
// 比较指针类型的浮点数是否相等
|
||||
pointerPriceEqual := func(a, b *float64) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
return priceEqual(*a, *b)
|
||||
}
|
||||
|
||||
// 检查价格是否有变化
|
||||
if isAdmin {
|
||||
// 管理员直接更新主字段,检查是否有实际变化
|
||||
@ -125,6 +136,16 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
existingPrice.Currency == price.Currency &&
|
||||
priceEqual(existingPrice.InputPrice, price.InputPrice) &&
|
||||
priceEqual(existingPrice.OutputPrice, price.OutputPrice) &&
|
||||
pointerPriceEqual(existingPrice.InputAudioTokens, price.InputAudioTokens) &&
|
||||
pointerPriceEqual(existingPrice.OutputAudioTokens, price.OutputAudioTokens) &&
|
||||
pointerPriceEqual(existingPrice.CachedTokens, price.CachedTokens) &&
|
||||
pointerPriceEqual(existingPrice.CachedReadTokens, price.CachedReadTokens) &&
|
||||
pointerPriceEqual(existingPrice.CachedWriteTokens, price.CachedWriteTokens) &&
|
||||
pointerPriceEqual(existingPrice.ReasoningTokens, price.ReasoningTokens) &&
|
||||
pointerPriceEqual(existingPrice.InputTextTokens, price.InputTextTokens) &&
|
||||
pointerPriceEqual(existingPrice.OutputTextTokens, price.OutputTextTokens) &&
|
||||
pointerPriceEqual(existingPrice.InputImageTokens, price.InputImageTokens) &&
|
||||
pointerPriceEqual(existingPrice.OutputImageTokens, price.OutputImageTokens) &&
|
||||
existingPrice.PriceSource == price.PriceSource {
|
||||
// 没有变化,不需要更新
|
||||
return *existingPrice, false, nil
|
||||
@ -138,6 +159,16 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
existingPrice.Currency = price.Currency
|
||||
existingPrice.InputPrice = price.InputPrice
|
||||
existingPrice.OutputPrice = price.OutputPrice
|
||||
existingPrice.InputAudioTokens = price.InputAudioTokens
|
||||
existingPrice.OutputAudioTokens = price.OutputAudioTokens
|
||||
existingPrice.CachedTokens = price.CachedTokens
|
||||
existingPrice.CachedReadTokens = price.CachedReadTokens
|
||||
existingPrice.CachedWriteTokens = price.CachedWriteTokens
|
||||
existingPrice.ReasoningTokens = price.ReasoningTokens
|
||||
existingPrice.InputTextTokens = price.InputTextTokens
|
||||
existingPrice.OutputTextTokens = price.OutputTextTokens
|
||||
existingPrice.InputImageTokens = price.InputImageTokens
|
||||
existingPrice.OutputImageTokens = price.OutputImageTokens
|
||||
existingPrice.PriceSource = price.PriceSource
|
||||
existingPrice.Status = "approved"
|
||||
existingPrice.UpdatedBy = &username
|
||||
@ -148,6 +179,16 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
existingPrice.TempCurrency = nil
|
||||
existingPrice.TempInputPrice = nil
|
||||
existingPrice.TempOutputPrice = nil
|
||||
existingPrice.TempInputAudioTokens = nil
|
||||
existingPrice.TempOutputAudioTokens = nil
|
||||
existingPrice.TempCachedTokens = nil
|
||||
existingPrice.TempCachedReadTokens = nil
|
||||
existingPrice.TempCachedWriteTokens = nil
|
||||
existingPrice.TempReasoningTokens = nil
|
||||
existingPrice.TempInputTextTokens = nil
|
||||
existingPrice.TempOutputTextTokens = nil
|
||||
existingPrice.TempInputImageTokens = nil
|
||||
existingPrice.TempOutputImageTokens = nil
|
||||
existingPrice.TempPriceSource = nil
|
||||
|
||||
// 保存更新
|
||||
@ -168,6 +209,16 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
existingPrice.Currency != price.Currency ||
|
||||
!priceEqual(existingPrice.InputPrice, price.InputPrice) ||
|
||||
!priceEqual(existingPrice.OutputPrice, price.OutputPrice) ||
|
||||
!pointerPriceEqual(existingPrice.InputAudioTokens, price.InputAudioTokens) ||
|
||||
!pointerPriceEqual(existingPrice.OutputAudioTokens, price.OutputAudioTokens) ||
|
||||
!pointerPriceEqual(existingPrice.CachedTokens, price.CachedTokens) ||
|
||||
!pointerPriceEqual(existingPrice.CachedReadTokens, price.CachedReadTokens) ||
|
||||
!pointerPriceEqual(existingPrice.CachedWriteTokens, price.CachedWriteTokens) ||
|
||||
!pointerPriceEqual(existingPrice.ReasoningTokens, price.ReasoningTokens) ||
|
||||
!pointerPriceEqual(existingPrice.InputTextTokens, price.InputTextTokens) ||
|
||||
!pointerPriceEqual(existingPrice.OutputTextTokens, price.OutputTextTokens) ||
|
||||
!pointerPriceEqual(existingPrice.InputImageTokens, price.InputImageTokens) ||
|
||||
!pointerPriceEqual(existingPrice.OutputImageTokens, price.OutputImageTokens) ||
|
||||
existingPrice.PriceSource != price.PriceSource {
|
||||
hasChanges = true
|
||||
}
|
||||
@ -182,6 +233,16 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
(existingPrice.TempCurrency == nil || *existingPrice.TempCurrency == price.Currency) &&
|
||||
(existingPrice.TempInputPrice == nil || priceEqual(*existingPrice.TempInputPrice, price.InputPrice)) &&
|
||||
(existingPrice.TempOutputPrice == nil || priceEqual(*existingPrice.TempOutputPrice, price.OutputPrice)) &&
|
||||
(existingPrice.TempInputAudioTokens == nil || pointerPriceEqual(existingPrice.TempInputAudioTokens, price.InputAudioTokens)) &&
|
||||
(existingPrice.TempOutputAudioTokens == nil || pointerPriceEqual(existingPrice.TempOutputAudioTokens, price.OutputAudioTokens)) &&
|
||||
(existingPrice.TempCachedTokens == nil || pointerPriceEqual(existingPrice.TempCachedTokens, price.CachedTokens)) &&
|
||||
(existingPrice.TempCachedReadTokens == nil || pointerPriceEqual(existingPrice.TempCachedReadTokens, price.CachedReadTokens)) &&
|
||||
(existingPrice.TempCachedWriteTokens == nil || pointerPriceEqual(existingPrice.TempCachedWriteTokens, price.CachedWriteTokens)) &&
|
||||
(existingPrice.TempReasoningTokens == nil || pointerPriceEqual(existingPrice.TempReasoningTokens, price.ReasoningTokens)) &&
|
||||
(existingPrice.TempInputTextTokens == nil || pointerPriceEqual(existingPrice.TempInputTextTokens, price.InputTextTokens)) &&
|
||||
(existingPrice.TempOutputTextTokens == nil || pointerPriceEqual(existingPrice.TempOutputTextTokens, price.OutputTextTokens)) &&
|
||||
(existingPrice.TempInputImageTokens == nil || pointerPriceEqual(existingPrice.TempInputImageTokens, price.InputImageTokens)) &&
|
||||
(existingPrice.TempOutputImageTokens == nil || pointerPriceEqual(existingPrice.TempOutputImageTokens, price.OutputImageTokens)) &&
|
||||
(existingPrice.TempPriceSource == nil || *existingPrice.TempPriceSource == price.PriceSource) {
|
||||
// 与之前提交的临时值相同,不需要更新
|
||||
hasChanges = false
|
||||
@ -201,6 +262,16 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
existingPrice.TempCurrency = &price.Currency
|
||||
existingPrice.TempInputPrice = &price.InputPrice
|
||||
existingPrice.TempOutputPrice = &price.OutputPrice
|
||||
existingPrice.TempInputAudioTokens = price.InputAudioTokens
|
||||
existingPrice.TempOutputAudioTokens = price.OutputAudioTokens
|
||||
existingPrice.TempCachedTokens = price.CachedTokens
|
||||
existingPrice.TempCachedReadTokens = price.CachedReadTokens
|
||||
existingPrice.TempCachedWriteTokens = price.CachedWriteTokens
|
||||
existingPrice.TempReasoningTokens = price.ReasoningTokens
|
||||
existingPrice.TempInputTextTokens = price.InputTextTokens
|
||||
existingPrice.TempOutputTextTokens = price.OutputTextTokens
|
||||
existingPrice.TempInputImageTokens = price.InputImageTokens
|
||||
existingPrice.TempOutputImageTokens = price.OutputImageTokens
|
||||
existingPrice.TempPriceSource = &price.PriceSource
|
||||
existingPrice.Status = "pending"
|
||||
existingPrice.UpdatedBy = &username
|
||||
@ -219,6 +290,38 @@ func ProcessPrice(price models.Price, existingPrice *models.Price, isAdmin bool,
|
||||
}
|
||||
price.CreatedBy = username
|
||||
|
||||
// 验证扩展价格字段(如果提供)
|
||||
if price.InputAudioTokens != nil && *price.InputAudioTokens < 0 {
|
||||
return price, false, fmt.Errorf("音频输入价格不能为负数")
|
||||
}
|
||||
if price.OutputAudioTokens != nil && *price.OutputAudioTokens < 0 {
|
||||
return price, false, fmt.Errorf("音频输出价格不能为负数")
|
||||
}
|
||||
if price.CachedTokens != nil && *price.CachedTokens < 0 {
|
||||
return price, false, fmt.Errorf("缓存价格不能为负数")
|
||||
}
|
||||
if price.CachedReadTokens != nil && *price.CachedReadTokens < 0 {
|
||||
return price, false, fmt.Errorf("缓存读取价格不能为负数")
|
||||
}
|
||||
if price.CachedWriteTokens != nil && *price.CachedWriteTokens < 0 {
|
||||
return price, false, fmt.Errorf("缓存写入价格不能为负数")
|
||||
}
|
||||
if price.ReasoningTokens != nil && *price.ReasoningTokens < 0 {
|
||||
return price, false, fmt.Errorf("推理价格不能为负数")
|
||||
}
|
||||
if price.InputTextTokens != nil && *price.InputTextTokens < 0 {
|
||||
return price, false, fmt.Errorf("输入文本价格不能为负数")
|
||||
}
|
||||
if price.OutputTextTokens != nil && *price.OutputTextTokens < 0 {
|
||||
return price, false, fmt.Errorf("输出文本价格不能为负数")
|
||||
}
|
||||
if price.InputImageTokens != nil && *price.InputImageTokens < 0 {
|
||||
return price, false, fmt.Errorf("输入图片价格不能为负数")
|
||||
}
|
||||
if price.OutputImageTokens != nil && *price.OutputImageTokens < 0 {
|
||||
return price, false, fmt.Errorf("输出图片价格不能为负数")
|
||||
}
|
||||
|
||||
// 保存新记录
|
||||
if err := database.DB.Create(&price).Error; err != nil {
|
||||
return price, false, err
|
||||
@ -326,6 +429,36 @@ func UpdatePriceStatus(c *gin.Context) {
|
||||
if price.TempOutputPrice != nil {
|
||||
updateMap["output_price"] = *price.TempOutputPrice
|
||||
}
|
||||
if price.TempInputAudioTokens != nil {
|
||||
updateMap["input_audio_tokens"] = *price.TempInputAudioTokens
|
||||
}
|
||||
if price.TempOutputAudioTokens != nil {
|
||||
updateMap["output_audio_tokens"] = *price.TempOutputAudioTokens
|
||||
}
|
||||
if price.TempCachedTokens != nil {
|
||||
updateMap["cached_tokens"] = *price.TempCachedTokens
|
||||
}
|
||||
if price.TempCachedReadTokens != nil {
|
||||
updateMap["cached_read_tokens"] = *price.TempCachedReadTokens
|
||||
}
|
||||
if price.TempCachedWriteTokens != nil {
|
||||
updateMap["cached_write_tokens"] = *price.TempCachedWriteTokens
|
||||
}
|
||||
if price.TempReasoningTokens != nil {
|
||||
updateMap["reasoning_tokens"] = *price.TempReasoningTokens
|
||||
}
|
||||
if price.TempInputTextTokens != nil {
|
||||
updateMap["input_text_tokens"] = *price.TempInputTextTokens
|
||||
}
|
||||
if price.TempOutputTextTokens != nil {
|
||||
updateMap["output_text_tokens"] = *price.TempOutputTextTokens
|
||||
}
|
||||
if price.TempInputImageTokens != nil {
|
||||
updateMap["input_image_tokens"] = *price.TempInputImageTokens
|
||||
}
|
||||
if price.TempOutputImageTokens != nil {
|
||||
updateMap["output_image_tokens"] = *price.TempOutputImageTokens
|
||||
}
|
||||
if price.TempPriceSource != nil {
|
||||
updateMap["price_source"] = *price.TempPriceSource
|
||||
}
|
||||
@ -338,6 +471,16 @@ func UpdatePriceStatus(c *gin.Context) {
|
||||
updateMap["temp_currency"] = nil
|
||||
updateMap["temp_input_price"] = nil
|
||||
updateMap["temp_output_price"] = nil
|
||||
updateMap["temp_input_audio_tokens"] = nil
|
||||
updateMap["temp_output_audio_tokens"] = nil
|
||||
updateMap["temp_cached_tokens"] = nil
|
||||
updateMap["temp_cached_read_tokens"] = nil
|
||||
updateMap["temp_cached_write_tokens"] = nil
|
||||
updateMap["temp_reasoning_tokens"] = nil
|
||||
updateMap["temp_input_text_tokens"] = nil
|
||||
updateMap["temp_output_text_tokens"] = nil
|
||||
updateMap["temp_input_image_tokens"] = nil
|
||||
updateMap["temp_output_image_tokens"] = nil
|
||||
updateMap["temp_price_source"] = nil
|
||||
updateMap["updated_by"] = nil
|
||||
|
||||
@ -361,17 +504,27 @@ func UpdatePriceStatus(c *gin.Context) {
|
||||
} 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,
|
||||
"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_input_audio_tokens": nil,
|
||||
"temp_output_audio_tokens": nil,
|
||||
"temp_cached_tokens": nil,
|
||||
"temp_cached_read_tokens": nil,
|
||||
"temp_cached_write_tokens": nil,
|
||||
"temp_reasoning_tokens": nil,
|
||||
"temp_input_text_tokens": nil,
|
||||
"temp_output_text_tokens": nil,
|
||||
"temp_input_image_tokens": nil,
|
||||
"temp_output_image_tokens": 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"})
|
||||
@ -538,6 +691,36 @@ func ApproveAllPrices(c *gin.Context) {
|
||||
if price.TempOutputPrice != nil {
|
||||
updateMap["output_price"] = *price.TempOutputPrice
|
||||
}
|
||||
if price.TempInputAudioTokens != nil {
|
||||
updateMap["input_audio_tokens"] = *price.TempInputAudioTokens
|
||||
}
|
||||
if price.TempOutputAudioTokens != nil {
|
||||
updateMap["output_audio_tokens"] = *price.TempOutputAudioTokens
|
||||
}
|
||||
if price.TempCachedTokens != nil {
|
||||
updateMap["cached_tokens"] = *price.TempCachedTokens
|
||||
}
|
||||
if price.TempCachedReadTokens != nil {
|
||||
updateMap["cached_read_tokens"] = *price.TempCachedReadTokens
|
||||
}
|
||||
if price.TempCachedWriteTokens != nil {
|
||||
updateMap["cached_write_tokens"] = *price.TempCachedWriteTokens
|
||||
}
|
||||
if price.TempReasoningTokens != nil {
|
||||
updateMap["reasoning_tokens"] = *price.TempReasoningTokens
|
||||
}
|
||||
if price.TempInputTextTokens != nil {
|
||||
updateMap["input_text_tokens"] = *price.TempInputTextTokens
|
||||
}
|
||||
if price.TempOutputTextTokens != nil {
|
||||
updateMap["output_text_tokens"] = *price.TempOutputTextTokens
|
||||
}
|
||||
if price.TempInputImageTokens != nil {
|
||||
updateMap["input_image_tokens"] = *price.TempInputImageTokens
|
||||
}
|
||||
if price.TempOutputImageTokens != nil {
|
||||
updateMap["output_image_tokens"] = *price.TempOutputImageTokens
|
||||
}
|
||||
if price.TempPriceSource != nil {
|
||||
updateMap["price_source"] = *price.TempPriceSource
|
||||
}
|
||||
@ -550,6 +733,16 @@ func ApproveAllPrices(c *gin.Context) {
|
||||
updateMap["temp_currency"] = nil
|
||||
updateMap["temp_input_price"] = nil
|
||||
updateMap["temp_output_price"] = nil
|
||||
updateMap["temp_input_audio_tokens"] = nil
|
||||
updateMap["temp_output_audio_tokens"] = nil
|
||||
updateMap["temp_cached_tokens"] = nil
|
||||
updateMap["temp_cached_read_tokens"] = nil
|
||||
updateMap["temp_cached_write_tokens"] = nil
|
||||
updateMap["temp_reasoning_tokens"] = nil
|
||||
updateMap["temp_input_text_tokens"] = nil
|
||||
updateMap["temp_output_text_tokens"] = nil
|
||||
updateMap["temp_input_image_tokens"] = nil
|
||||
updateMap["temp_output_image_tokens"] = nil
|
||||
updateMap["temp_price_source"] = nil
|
||||
updateMap["updated_by"] = nil
|
||||
|
||||
@ -575,17 +768,27 @@ func ApproveAllPrices(c *gin.Context) {
|
||||
} 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,
|
||||
"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_input_audio_tokens": nil,
|
||||
"temp_output_audio_tokens": nil,
|
||||
"temp_cached_tokens": nil,
|
||||
"temp_cached_read_tokens": nil,
|
||||
"temp_cached_write_tokens": nil,
|
||||
"temp_reasoning_tokens": nil,
|
||||
"temp_input_text_tokens": nil,
|
||||
"temp_output_text_tokens": nil,
|
||||
"temp_input_image_tokens": nil,
|
||||
"temp_output_image_tokens": nil,
|
||||
"temp_price_source": nil,
|
||||
"updated_by": nil,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reject prices"})
|
||||
|
@ -15,13 +15,16 @@ type Price struct {
|
||||
Currency string `json:"currency" gorm:"not null"` // USD or CNY
|
||||
InputPrice float64 `json:"input_price" gorm:"not null"`
|
||||
OutputPrice float64 `json:"output_price" gorm:"not null"`
|
||||
InputAudioTokens *float64 `json:"input_audio_tokens,omitempty"` // 音频输入倍率
|
||||
CachedReadTokens *float64 `json:"cached_read_tokens,omitempty"` // 缓存读取倍率
|
||||
ReasoningTokens *float64 `json:"reasoning_tokens,omitempty"` // 推理倍率
|
||||
InputTextTokens *float64 `json:"input_text_tokens,omitempty"` // 输入文本倍率
|
||||
OutputTextTokens *float64 `json:"output_text_tokens,omitempty"` // 输出文本倍率
|
||||
InputImageTokens *float64 `json:"input_image_tokens,omitempty"` // 输入图片倍率
|
||||
OutputImageTokens *float64 `json:"output_image_tokens,omitempty"` // 输出图片倍率
|
||||
InputAudioTokens *float64 `json:"input_audio_tokens,omitempty"` // 音频输入价格
|
||||
OutputAudioTokens *float64 `json:"output_audio_tokens,omitempty"` // 音频输出价格
|
||||
CachedTokens *float64 `json:"cached_tokens,omitempty"` // 缓存价格
|
||||
CachedReadTokens *float64 `json:"cached_read_tokens,omitempty"` // 缓存读取价格
|
||||
CachedWriteTokens *float64 `json:"cached_write_tokens,omitempty"` // 缓存写入价格
|
||||
ReasoningTokens *float64 `json:"reasoning_tokens,omitempty"` // 推理价格
|
||||
InputTextTokens *float64 `json:"input_text_tokens,omitempty"` // 输入文本价格
|
||||
OutputTextTokens *float64 `json:"output_text_tokens,omitempty"` // 输出文本价格
|
||||
InputImageTokens *float64 `json:"input_image_tokens,omitempty"` // 输入图片价格
|
||||
OutputImageTokens *float64 `json:"output_image_tokens,omitempty"` // 输出图片价格
|
||||
PriceSource string `json:"price_source" gorm:"not null"`
|
||||
Status string `json:"status" gorm:"not null;default:pending;index:idx_status"` // pending, approved, rejected
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index:idx_created_at"`
|
||||
@ -37,7 +40,10 @@ type Price struct {
|
||||
TempInputPrice *float64 `json:"temp_input_price,omitempty" gorm:"column:temp_input_price"`
|
||||
TempOutputPrice *float64 `json:"temp_output_price,omitempty" gorm:"column:temp_output_price"`
|
||||
TempInputAudioTokens *float64 `json:"temp_input_audio_tokens,omitempty"`
|
||||
TempOutputAudioTokens *float64 `json:"temp_output_audio_tokens,omitempty"`
|
||||
TempCachedTokens *float64 `json:"temp_cached_tokens,omitempty"`
|
||||
TempCachedReadTokens *float64 `json:"temp_cached_read_tokens,omitempty"`
|
||||
TempCachedWriteTokens *float64 `json:"temp_cached_write_tokens,omitempty"`
|
||||
TempReasoningTokens *float64 `json:"temp_reasoning_tokens,omitempty"`
|
||||
TempInputTextTokens *float64 `json:"temp_input_text_tokens,omitempty"`
|
||||
TempOutputTextTokens *float64 `json:"temp_output_text_tokens,omitempty"`
|
||||
|
@ -84,7 +84,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="price in prices" :key="price.id" class="price-card">
|
||||
<div v-for="price in prices" :key="price.id" class="price-card" :class="price.status">
|
||||
<div class="price-card-header">
|
||||
<div class="provider-info">
|
||||
<el-image
|
||||
@ -133,72 +133,99 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasExtendedPrices(price)" class="extended-prices">
|
||||
<div class="extended-prices" v-if="hasAnyExtendedPrices(price)">
|
||||
<div class="section-title">扩展价格</div>
|
||||
<div class="extended-price-grid">
|
||||
<template v-if="price.input_audio_tokens">
|
||||
<template v-if="hasSpecificPrice(price.input_audio_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">音频输入倍率</span>
|
||||
<span class="ext-price-label">音频输入价格</span>
|
||||
<span class="ext-price-value">{{ price.input_audio_tokens }}</span>
|
||||
<el-tag v-if="price.temp_input_audio_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_input_audio_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="price.cached_read_tokens">
|
||||
<template v-if="hasSpecificPrice(price.output_audio_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">缓存读取倍率</span>
|
||||
<span class="ext-price-label">音频输出价格</span>
|
||||
<span class="ext-price-value">{{ price.output_audio_tokens }}</span>
|
||||
<el-tag v-if="price.temp_output_audio_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_output_audio_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="hasSpecificPrice(price.cached_read_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">缓存读取价格</span>
|
||||
<span class="ext-price-value">{{ price.cached_read_tokens }}</span>
|
||||
<el-tag v-if="price.temp_cached_read_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_cached_read_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="price.reasoning_tokens">
|
||||
<template v-if="hasSpecificPrice(price.cached_write_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">推理倍率</span>
|
||||
<span class="ext-price-label">缓存写入价格</span>
|
||||
<span class="ext-price-value">{{ price.cached_write_tokens }}</span>
|
||||
<el-tag v-if="price.temp_cached_write_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_cached_write_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="hasSpecificPrice(price.reasoning_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">推理价格</span>
|
||||
<span class="ext-price-value">{{ price.reasoning_tokens }}</span>
|
||||
<el-tag v-if="price.temp_reasoning_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_reasoning_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="price.input_text_tokens">
|
||||
<template v-if="hasSpecificPrice(price.input_text_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">输入文本倍率</span>
|
||||
<span class="ext-price-label">输入文本价格</span>
|
||||
<span class="ext-price-value">{{ price.input_text_tokens }}</span>
|
||||
<el-tag v-if="price.temp_input_text_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_input_text_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="price.output_text_tokens">
|
||||
<template v-if="hasSpecificPrice(price.output_text_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">输出文本倍率</span>
|
||||
<span class="ext-price-label">输出文本价格</span>
|
||||
<span class="ext-price-value">{{ price.output_text_tokens }}</span>
|
||||
<el-tag v-if="price.temp_output_text_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_output_text_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="price.input_image_tokens">
|
||||
<template v-if="hasSpecificPrice(price.input_image_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">输入图片倍率</span>
|
||||
<span class="ext-price-label">输入图片价格</span>
|
||||
<span class="ext-price-value">{{ price.input_image_tokens }}</span>
|
||||
<el-tag v-if="price.temp_input_image_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_input_image_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="price.output_image_tokens">
|
||||
<template v-if="hasSpecificPrice(price.output_image_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">输出图片倍率</span>
|
||||
<span class="ext-price-label">输出图片价格</span>
|
||||
<span class="ext-price-value">{{ price.output_image_tokens }}</span>
|
||||
<el-tag v-if="price.temp_output_image_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_output_image_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="hasSpecificPrice(price.cached_tokens)">
|
||||
<div class="extended-price-item">
|
||||
<span class="ext-price-label">缓存价格</span>
|
||||
<span class="ext-price-value">{{ price.cached_tokens }}</span>
|
||||
<el-tag v-if="price.temp_cached_tokens" type="warning" size="small" effect="light">
|
||||
待审核: {{ price.temp_cached_tokens }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -358,6 +385,84 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000" />
|
||||
:controls="false" placeholder="请输入价格" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="扩展价格" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<el-dropdown trigger="click">
|
||||
<el-button type="primary" plain size="small">
|
||||
设置扩展价格 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<div class="extended-price-dropdown">
|
||||
<div class="dropdown-title">扩展价格设置</div>
|
||||
<div class="dropdown-row">
|
||||
<span>音频输入价格:</span>
|
||||
<el-input-number v-model="row.input_audio_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>音频输出价格:</span>
|
||||
<el-input-number v-model="row.output_audio_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>缓存读取价格:</span>
|
||||
<el-input-number v-model="row.cached_read_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>缓存写入价格:</span>
|
||||
<el-input-number v-model="row.cached_write_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>推理价格:</span>
|
||||
<el-input-number v-model="row.reasoning_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>输入文本价格:</span>
|
||||
<el-input-number v-model="row.input_text_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>输出文本价格:</span>
|
||||
<el-input-number v-model="row.output_text_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>输入图片价格:</span>
|
||||
<el-input-number v-model="row.input_image_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>输出图片价格:</span>
|
||||
<el-input-number v-model="row.output_image_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
<div class="dropdown-row">
|
||||
<span>缓存价格:</span>
|
||||
<el-input-number v-model="row.cached_tokens" :precision="4" :step="0.0001"
|
||||
:controls="false" :min="0" placeholder="请输入价格" />
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div v-if="hasAnyExtendedPrices(row)" class="batch-extended-prices">
|
||||
<div v-if="row.input_audio_tokens" class="batch-price-tag">音频输入: {{ row.input_audio_tokens }}</div>
|
||||
<div v-if="row.output_audio_tokens" class="batch-price-tag">音频输出: {{ row.output_audio_tokens }}</div>
|
||||
<div v-if="row.cached_read_tokens" class="batch-price-tag">缓存读取: {{ row.cached_read_tokens }}</div>
|
||||
<div v-if="row.cached_write_tokens" class="batch-price-tag">缓存写入: {{ row.cached_write_tokens }}</div>
|
||||
<div v-if="row.reasoning_tokens" class="batch-price-tag">推理: {{ row.reasoning_tokens }}</div>
|
||||
<div v-if="row.input_text_tokens" class="batch-price-tag">输入文本: {{ row.input_text_tokens }}</div>
|
||||
<div v-if="row.output_text_tokens" class="batch-price-tag">输出文本: {{ row.output_text_tokens }}</div>
|
||||
<div v-if="row.input_image_tokens" class="batch-price-tag">输入图片: {{ row.input_image_tokens }}</div>
|
||||
<div v-if="row.output_image_tokens" class="batch-price-tag">输出图片: {{ row.output_image_tokens }}</div>
|
||||
<div v-if="row.cached_tokens" class="batch-price-tag">缓存: {{ row.cached_tokens }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格来源" min-width="200" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.price_source" placeholder="请输入价格来源" />
|
||||
@ -434,49 +539,49 @@ dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-divider>扩展价格(可选)</el-divider>
|
||||
<div class="extended-price-header">
|
||||
<h3>扩展价格(可选)</h3>
|
||||
<p class="extended-price-tip">这些价格字段仅适用于支持特定功能的模型,留空表示不使用</p>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="音频输入倍率">
|
||||
<el-input-number v-model="form.input_audio_tokens" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入倍率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="缓存读取倍率">
|
||||
<el-input-number v-model="form.cached_read_tokens" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入倍率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="推理倍率">
|
||||
<el-input-number v-model="form.reasoning_tokens" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入倍率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="输入文本倍率">
|
||||
<el-input-number v-model="form.input_text_tokens" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入倍率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="输出文本倍率">
|
||||
<el-input-number v-model="form.output_text_tokens" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入倍率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="输入图片倍率">
|
||||
<el-input-number v-model="form.input_image_tokens" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入倍率" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="输出图片倍率">
|
||||
<el-input-number v-model="form.output_image_tokens" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入倍率" />
|
||||
</el-form-item>
|
||||
<el-col :span="24">
|
||||
<div class="extended-price-container">
|
||||
<div v-if="selectedExtensionPrices.length === 0" class="no-extensions">
|
||||
暂无扩展价格,点击下方按钮添加
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-for="(type, index) in selectedExtensionPrices" :key="type" class="extension-item">
|
||||
<div class="extension-header">
|
||||
<span class="extension-label">{{ getExtensionLabel(type) }}</span>
|
||||
<el-button type="danger" size="small" circle @click="removeExtension(index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<el-input-number v-model="form[type]" :precision="4" :step="0.0001" style="width: 100%"
|
||||
:controls="false" placeholder="请输入价格" />
|
||||
</div>
|
||||
</template>
|
||||
<el-dropdown @command="addExtension" trigger="click">
|
||||
<el-button type="primary" class="add-extension-btn">
|
||||
添加扩展价格 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<div class="extended-type-dropdown">
|
||||
<div class="dropdown-title">选择扩展价格类型</div>
|
||||
<el-dropdown-item
|
||||
v-for="(label, type) in availableExtensionTypes"
|
||||
:key="type"
|
||||
:disabled="isExtensionSelected(type)"
|
||||
:command="type"
|
||||
>
|
||||
{{ label }}
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="价格来源">
|
||||
@ -500,7 +605,7 @@ import { ref, computed, onMounted, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Edit, Delete, Check, Close, Document, Search } from '@element-plus/icons-vue'
|
||||
import { Edit, Delete, Check, Close, Document, Search, ArrowDown } from '@element-plus/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
user: Object
|
||||
@ -517,7 +622,10 @@ const form = ref({
|
||||
input_price: null,
|
||||
output_price: null,
|
||||
input_audio_tokens: null,
|
||||
output_audio_tokens: null,
|
||||
cached_tokens: null,
|
||||
cached_read_tokens: null,
|
||||
cached_write_tokens: null,
|
||||
reasoning_tokens: null,
|
||||
input_text_tokens: null,
|
||||
output_text_tokens: null,
|
||||
@ -638,6 +746,7 @@ const loadPrices = async () => {
|
||||
const handleEdit = (price) => {
|
||||
editingPrice.value = price
|
||||
form.value = { ...price }
|
||||
setExtensionPricesFromPrice(price)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
@ -682,7 +791,10 @@ const handleAdd = () => {
|
||||
input_price: null,
|
||||
output_price: null,
|
||||
input_audio_tokens: null,
|
||||
output_audio_tokens: null,
|
||||
cached_tokens: null,
|
||||
cached_read_tokens: null,
|
||||
cached_write_tokens: null,
|
||||
reasoning_tokens: null,
|
||||
input_text_tokens: null,
|
||||
output_text_tokens: null,
|
||||
@ -691,6 +803,7 @@ const handleAdd = () => {
|
||||
price_source: '',
|
||||
created_by: ''
|
||||
}
|
||||
resetExtensionPrices()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
@ -711,7 +824,10 @@ const handleQuickEdit = (row) => {
|
||||
input_price: row.input_price,
|
||||
output_price: row.output_price,
|
||||
input_audio_tokens: row.input_audio_tokens,
|
||||
output_audio_tokens: row.output_audio_tokens,
|
||||
cached_tokens: row.cached_tokens,
|
||||
cached_read_tokens: row.cached_read_tokens,
|
||||
cached_write_tokens: row.cached_write_tokens,
|
||||
reasoning_tokens: row.reasoning_tokens,
|
||||
input_text_tokens: row.input_text_tokens,
|
||||
output_text_tokens: row.output_text_tokens,
|
||||
@ -720,6 +836,7 @@ const handleQuickEdit = (row) => {
|
||||
price_source: row.price_source,
|
||||
created_by: props.user.username
|
||||
}
|
||||
setExtensionPricesFromPrice(row)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
@ -793,7 +910,10 @@ const handleSubmitResponse = async (response) => {
|
||||
input_price: null,
|
||||
output_price: null,
|
||||
input_audio_tokens: null,
|
||||
output_audio_tokens: null,
|
||||
cached_tokens: null,
|
||||
cached_read_tokens: null,
|
||||
cached_write_tokens: null,
|
||||
reasoning_tokens: null,
|
||||
input_text_tokens: null,
|
||||
output_text_tokens: null,
|
||||
@ -887,7 +1007,10 @@ const createNewRow = () => ({
|
||||
input_price: null,
|
||||
output_price: null,
|
||||
input_audio_tokens: null,
|
||||
output_audio_tokens: null,
|
||||
cached_tokens: null,
|
||||
cached_read_tokens: null,
|
||||
cached_write_tokens: null,
|
||||
reasoning_tokens: null,
|
||||
input_text_tokens: null,
|
||||
output_text_tokens: null,
|
||||
@ -1226,7 +1349,9 @@ const handleSearch = () => {
|
||||
// 添加检查是否有扩展价格的方法
|
||||
const hasExtendedPrices = (row) => {
|
||||
return row.input_audio_tokens ||
|
||||
row.output_audio_tokens ||
|
||||
row.cached_read_tokens ||
|
||||
row.cached_write_tokens ||
|
||||
row.reasoning_tokens ||
|
||||
row.input_text_tokens ||
|
||||
row.output_text_tokens ||
|
||||
@ -1234,6 +1359,90 @@ const hasExtendedPrices = (row) => {
|
||||
row.output_image_tokens
|
||||
}
|
||||
|
||||
// 添加更细粒度的检查函数
|
||||
const hasAnyExtendedPrices = (row) => {
|
||||
return hasSpecificPrice(row.input_audio_tokens) ||
|
||||
hasSpecificPrice(row.output_audio_tokens) ||
|
||||
hasSpecificPrice(row.cached_tokens) ||
|
||||
hasSpecificPrice(row.cached_read_tokens) ||
|
||||
hasSpecificPrice(row.cached_write_tokens) ||
|
||||
hasSpecificPrice(row.reasoning_tokens) ||
|
||||
hasSpecificPrice(row.input_text_tokens) ||
|
||||
hasSpecificPrice(row.output_text_tokens) ||
|
||||
hasSpecificPrice(row.input_image_tokens) ||
|
||||
hasSpecificPrice(row.output_image_tokens)
|
||||
}
|
||||
|
||||
// 检查具体价格字段是否存在且有效
|
||||
const hasSpecificPrice = (price) => {
|
||||
return price !== null && price !== undefined && price !== ''
|
||||
}
|
||||
|
||||
// 扩展价格类型定义
|
||||
const extensionTypes = {
|
||||
input_audio_tokens: '音频输入价格',
|
||||
output_audio_tokens: '音频输出价格',
|
||||
cached_tokens: '缓存价格',
|
||||
cached_read_tokens: '缓存读取价格',
|
||||
cached_write_tokens: '缓存写入价格',
|
||||
reasoning_tokens: '推理价格',
|
||||
input_text_tokens: '输入文本价格',
|
||||
output_text_tokens: '输出文本价格',
|
||||
input_image_tokens: '输入图片价格',
|
||||
output_image_tokens: '输出图片价格'
|
||||
}
|
||||
|
||||
// 选中的扩展价格类型
|
||||
const selectedExtensionPrices = ref([])
|
||||
|
||||
// 可用的扩展价格类型
|
||||
const availableExtensionTypes = computed(() => {
|
||||
return extensionTypes
|
||||
})
|
||||
|
||||
// 获取扩展类型的标签
|
||||
const getExtensionLabel = (type) => {
|
||||
return extensionTypes[type] || type
|
||||
}
|
||||
|
||||
// 检查扩展类型是否已选中
|
||||
const isExtensionSelected = (type) => {
|
||||
return selectedExtensionPrices.value.includes(type)
|
||||
}
|
||||
|
||||
// 添加扩展价格
|
||||
const addExtension = (type) => {
|
||||
if (!isExtensionSelected(type)) {
|
||||
selectedExtensionPrices.value.push(type)
|
||||
form.value[type] = null
|
||||
}
|
||||
}
|
||||
|
||||
// 移除扩展价格
|
||||
const removeExtension = (index) => {
|
||||
const type = selectedExtensionPrices.value[index]
|
||||
form.value[type] = null
|
||||
selectedExtensionPrices.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 重置扩展价格选择
|
||||
const resetExtensionPrices = () => {
|
||||
selectedExtensionPrices.value = []
|
||||
Object.keys(extensionTypes).forEach(type => {
|
||||
form.value[type] = null
|
||||
})
|
||||
}
|
||||
|
||||
// 从价格对象中设置扩展价格选择
|
||||
const setExtensionPricesFromPrice = (price) => {
|
||||
selectedExtensionPrices.value = []
|
||||
Object.keys(extensionTypes).forEach(type => {
|
||||
if (price[type] !== undefined && price[type] !== null) {
|
||||
selectedExtensionPrices.value.push(type)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadModelTypes()
|
||||
loadPrices()
|
||||
@ -1527,8 +1736,8 @@ onMounted(() => {
|
||||
|
||||
.price-cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
gap: 1.2rem;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
@ -1536,16 +1745,38 @@ onMounted(() => {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.price-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.price-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: linear-gradient(to right, #36d1dc, #5b86e5);
|
||||
}
|
||||
|
||||
.price-card.pending::before {
|
||||
background: linear-gradient(to right, #f7b733, #fc4a1a);
|
||||
}
|
||||
|
||||
.price-card.rejected::before {
|
||||
background: linear-gradient(to right, #ED213A, #93291E);
|
||||
}
|
||||
|
||||
.price-card-header {
|
||||
@ -1557,25 +1788,32 @@ onMounted(() => {
|
||||
.provider-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.provider-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
object-fit: contain;
|
||||
background-color: #f5f7fa;
|
||||
padding: 2px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.provider-name {
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.model-status {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.model-status.pending {
|
||||
@ -1594,46 +1832,52 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.model-info {
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-size: 1.25rem;
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0 0 0.5rem 0;
|
||||
margin: 0 0 0.75rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.model-meta {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
gap: 0.7rem;
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #f9fafc;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
color: #666;
|
||||
color: #606266;
|
||||
min-width: 100px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.extended-prices {
|
||||
@ -1643,14 +1887,15 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.extended-price-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@ -1658,19 +1903,28 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.extended-price-item:hover {
|
||||
background: #f0f2f5;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.ext-price-label {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ext-price-value {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.price-card-footer {
|
||||
@ -1686,8 +1940,8 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.created-by {
|
||||
@ -1695,7 +1949,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.created-at {
|
||||
color: #999;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
@ -1709,6 +1963,7 @@ onMounted(() => {
|
||||
|
||||
:deep(.el-tag) {
|
||||
margin: 0;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
@ -1718,6 +1973,127 @@ onMounted(() => {
|
||||
:deep(.el-icon) {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.extended-price-header {
|
||||
margin: 20px 0 10px 0;
|
||||
border-top: 1px solid #EBEEF5;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.extended-price-header h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.extended-price-tip {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.extended-price-dropdown {
|
||||
padding: 15px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.dropdown-title {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 15px;
|
||||
font-size: 15px;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.dropdown-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.dropdown-row span {
|
||||
flex: 0 0 110px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.dropdown-row .el-input-number {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.batch-extended-prices {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.batch-price-tag {
|
||||
background-color: #ecf5ff;
|
||||
color: #409EFF;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.extended-price-container {
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f9fafc;
|
||||
}
|
||||
|
||||
.no-extensions {
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.extension-item {
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.extension-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.extension-label {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.add-extension-btn {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.extended-type-dropdown {
|
||||
min-width: 220px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item.is-disabled) {
|
||||
color: #C0C4CC;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user