mirror of
https://github.com/woodchen-ink/aimodels-prices.git
synced 2025-07-18 13:41:59 +08:00
实现内存缓存机制,优化数据库查询性能
- 新增内存缓存接口和实现,支持设置过期时间 - 在数据库初始化时创建全局缓存实例 - 为模型类型、提供商和价格查询添加缓存层 - 实现定期缓存常用数据的后台任务 - 优化数据库查询,减少重复查询和不必要的数据库访问 - 为价格查询添加索引,提高查询效率
This commit is contained in:
parent
449f95d1b5
commit
9f51ac602e
@ -3,6 +3,8 @@ package database
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -15,6 +17,116 @@ import (
|
|||||||
// DB 是数据库连接的全局实例
|
// DB 是数据库连接的全局实例
|
||||||
var DB *gorm.DB
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
// Cache 接口定义了缓存的基本操作
|
||||||
|
type Cache interface {
|
||||||
|
Get(key string) (interface{}, bool)
|
||||||
|
Set(key string, value interface{}, expiration time.Duration)
|
||||||
|
Delete(key string)
|
||||||
|
Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryCache 是一个简单的内存缓存实现
|
||||||
|
type MemoryCache struct {
|
||||||
|
items map[string]cacheItem
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type cacheItem struct {
|
||||||
|
value interface{}
|
||||||
|
expiration int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局缓存实例
|
||||||
|
var GlobalCache Cache
|
||||||
|
|
||||||
|
// NewMemoryCache 创建一个新的内存缓存
|
||||||
|
func NewMemoryCache() *MemoryCache {
|
||||||
|
cache := &MemoryCache{
|
||||||
|
items: make(map[string]cacheItem),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动一个后台协程定期清理过期项
|
||||||
|
go cache.janitor()
|
||||||
|
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 从缓存中获取值
|
||||||
|
func (c *MemoryCache) Get(key string) (interface{}, bool) {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
|
||||||
|
item, found := c.items[key]
|
||||||
|
if !found {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否过期
|
||||||
|
if item.expiration > 0 && item.expiration < time.Now().UnixNano() {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set 设置缓存值
|
||||||
|
func (c *MemoryCache) Set(key string, value interface{}, expiration time.Duration) {
|
||||||
|
var exp int64
|
||||||
|
|
||||||
|
if expiration > 0 {
|
||||||
|
exp = time.Now().Add(expiration).UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
c.items[key] = cacheItem{
|
||||||
|
value: value,
|
||||||
|
expiration: exp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除缓存项
|
||||||
|
func (c *MemoryCache) Delete(key string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
delete(c.items, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear 清空所有缓存
|
||||||
|
func (c *MemoryCache) Clear() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
c.items = make(map[string]cacheItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// janitor 定期清理过期的缓存项
|
||||||
|
func (c *MemoryCache) janitor() {
|
||||||
|
ticker := time.NewTicker(time.Minute)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
<-ticker.C
|
||||||
|
c.deleteExpired()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteExpired 删除所有过期的项
|
||||||
|
func (c *MemoryCache) deleteExpired() {
|
||||||
|
now := time.Now().UnixNano()
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
for k, v := range c.items {
|
||||||
|
if v.expiration > 0 && v.expiration < now {
|
||||||
|
delete(c.items, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// InitDB 初始化数据库连接
|
// InitDB 初始化数据库连接
|
||||||
func InitDB(cfg *config.Config) error {
|
func InitDB(cfg *config.Config) error {
|
||||||
var err error
|
var err error
|
||||||
@ -43,8 +155,15 @@ func InitDB(cfg *config.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置连接池参数
|
// 设置连接池参数
|
||||||
sqlDB.SetMaxOpenConns(10)
|
sqlDB.SetMaxOpenConns(20) // 增加最大连接数
|
||||||
sqlDB.SetMaxIdleConns(5)
|
sqlDB.SetMaxIdleConns(10) // 增加空闲连接数
|
||||||
|
sqlDB.SetConnMaxLifetime(time.Hour) // 设置连接最大生命周期
|
||||||
|
|
||||||
|
// 初始化缓存
|
||||||
|
GlobalCache = NewMemoryCache()
|
||||||
|
|
||||||
|
// 启动定期缓存任务
|
||||||
|
go startCacheJobs()
|
||||||
|
|
||||||
// 自动迁移表结构
|
// 自动迁移表结构
|
||||||
if err = migrateModels(); err != nil {
|
if err = migrateModels(); err != nil {
|
||||||
@ -54,6 +173,215 @@ func InitDB(cfg *config.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// startCacheJobs 启动定期缓存任务
|
||||||
|
func startCacheJobs() {
|
||||||
|
// 每5分钟执行一次
|
||||||
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// 立即执行一次
|
||||||
|
cacheCommonData()
|
||||||
|
|
||||||
|
for {
|
||||||
|
<-ticker.C
|
||||||
|
cacheCommonData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheCommonData 缓存常用数据
|
||||||
|
func cacheCommonData() {
|
||||||
|
log.Println("开始自动缓存常用数据...")
|
||||||
|
|
||||||
|
// 缓存所有模型类型
|
||||||
|
cacheModelTypes()
|
||||||
|
|
||||||
|
// 缓存所有提供商
|
||||||
|
cacheProviders()
|
||||||
|
|
||||||
|
// 缓存价格倍率
|
||||||
|
cachePriceRates()
|
||||||
|
|
||||||
|
log.Println("自动缓存常用数据完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheModelTypes 缓存所有模型类型
|
||||||
|
func cacheModelTypes() {
|
||||||
|
var types []models.ModelType
|
||||||
|
if err := DB.Order("sort_order ASC, type_key ASC").Find(&types).Error; err != nil {
|
||||||
|
log.Printf("缓存模型类型失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalCache.Set("model_types", types, 30*time.Minute)
|
||||||
|
log.Printf("已缓存 %d 个模型类型", len(types))
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheProviders 缓存所有提供商
|
||||||
|
func cacheProviders() {
|
||||||
|
var providers []models.Provider
|
||||||
|
if err := DB.Order("id").Find(&providers).Error; err != nil {
|
||||||
|
log.Printf("缓存提供商失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalCache.Set("providers", providers, 30*time.Minute)
|
||||||
|
log.Printf("已缓存 %d 个提供商", len(providers))
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachePriceRates 缓存价格倍率
|
||||||
|
func cachePriceRates() {
|
||||||
|
// 获取所有已批准的价格
|
||||||
|
var prices []models.Price
|
||||||
|
if err := DB.Where("status = 'approved'").Find(&prices).Error; err != nil {
|
||||||
|
log.Printf("缓存价格倍率失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按模型分组
|
||||||
|
modelMap := make(map[string]map[uint]models.Price)
|
||||||
|
for _, price := range prices {
|
||||||
|
if _, exists := modelMap[price.Model]; !exists {
|
||||||
|
modelMap[price.Model] = make(map[uint]models.Price)
|
||||||
|
}
|
||||||
|
modelMap[price.Model][price.ChannelType] = price
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算倍率
|
||||||
|
type PriceRate struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
ModelType string `json:"model_type"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
ChannelType uint `json:"channel_type"`
|
||||||
|
Input float64 `json:"input"`
|
||||||
|
Output float64 `json:"output"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var rates []PriceRate
|
||||||
|
for model, providers := range modelMap {
|
||||||
|
// 找出基准价格(通常是OpenAI的价格)
|
||||||
|
var basePrice models.Price
|
||||||
|
var found bool
|
||||||
|
for _, price := range providers {
|
||||||
|
if price.ChannelType == 1 { // 假设OpenAI的ID是1
|
||||||
|
basePrice = price
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算其他厂商相对于基准价格的倍率
|
||||||
|
for channelType, price := range providers {
|
||||||
|
if channelType == 1 {
|
||||||
|
continue // 跳过基准价格
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算输入和输出的倍率
|
||||||
|
inputRate := 0.0
|
||||||
|
if basePrice.InputPrice > 0 {
|
||||||
|
inputRate = price.InputPrice / basePrice.InputPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
outputRate := 0.0
|
||||||
|
if basePrice.OutputPrice > 0 {
|
||||||
|
outputRate = price.OutputPrice / basePrice.OutputPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
rates = append(rates, PriceRate{
|
||||||
|
Model: model,
|
||||||
|
ModelType: price.ModelType,
|
||||||
|
Type: price.BillingType,
|
||||||
|
ChannelType: channelType,
|
||||||
|
Input: inputRate,
|
||||||
|
Output: outputRate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalCache.Set("price_rates", rates, 10*time.Minute)
|
||||||
|
log.Printf("已缓存 %d 个价格倍率", len(rates))
|
||||||
|
|
||||||
|
// 缓存常用的价格查询
|
||||||
|
cachePriceQueries()
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachePriceQueries 缓存常用的价格查询
|
||||||
|
func cachePriceQueries() {
|
||||||
|
// 缓存第一页数据(无筛选条件)
|
||||||
|
cachePricePage(1, 20, "", "")
|
||||||
|
|
||||||
|
// 获取所有模型类型
|
||||||
|
var modelTypes []models.ModelType
|
||||||
|
if err := DB.Find(&modelTypes).Error; err != nil {
|
||||||
|
log.Printf("获取模型类型失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有提供商
|
||||||
|
var providers []models.Provider
|
||||||
|
if err := DB.Find(&providers).Error; err != nil {
|
||||||
|
log.Printf("获取提供商失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每种模型类型缓存第一页数据
|
||||||
|
for _, mt := range modelTypes {
|
||||||
|
cachePricePage(1, 20, "", mt.TypeKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每个提供商缓存第一页数据
|
||||||
|
for _, p := range providers {
|
||||||
|
channelType := fmt.Sprintf("%d", p.ID)
|
||||||
|
cachePricePage(1, 20, channelType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachePricePage 缓存特定页的价格数据
|
||||||
|
func cachePricePage(page, pageSize int, channelType, modelType string) {
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
|
// 构建查询
|
||||||
|
query := DB.Model(&models.Price{})
|
||||||
|
|
||||||
|
// 添加筛选条件
|
||||||
|
if channelType != "" {
|
||||||
|
query = query.Where("channel_type = ?", channelType)
|
||||||
|
}
|
||||||
|
if modelType != "" {
|
||||||
|
query = query.Where("model_type = ?", modelType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取总数
|
||||||
|
var total int64
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
log.Printf("计算价格总数失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分页数据
|
||||||
|
var prices []models.Price
|
||||||
|
if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&prices).Error; err != nil {
|
||||||
|
log.Printf("获取价格数据失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"total": total,
|
||||||
|
"data": prices,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建缓存键
|
||||||
|
cacheKey := fmt.Sprintf("prices_page_%d_size_%d_channel_%s_type_%s",
|
||||||
|
page, pageSize, channelType, modelType)
|
||||||
|
|
||||||
|
// 存入缓存,有效期5分钟
|
||||||
|
GlobalCache.Set(cacheKey, result, 5*time.Minute)
|
||||||
|
log.Printf("已缓存价格查询: %s", cacheKey)
|
||||||
|
}
|
||||||
|
|
||||||
// migrateModels 自动迁移模型到数据库表
|
// migrateModels 自动迁移模型到数据库表
|
||||||
func migrateModels() error {
|
func migrateModels() error {
|
||||||
// 自动迁移模型
|
// 自动迁移模型
|
||||||
@ -68,8 +396,5 @@ func migrateModels() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这里可以添加其他模型的迁移
|
|
||||||
// 例如:DB.AutoMigrate(&models.User{})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@ -11,6 +12,16 @@ import (
|
|||||||
|
|
||||||
// GetModelTypes 获取所有模型类型
|
// GetModelTypes 获取所有模型类型
|
||||||
func GetModelTypes(c *gin.Context) {
|
func GetModelTypes(c *gin.Context) {
|
||||||
|
cacheKey := "model_types"
|
||||||
|
|
||||||
|
// 尝试从缓存获取
|
||||||
|
if cachedData, found := database.GlobalCache.Get(cacheKey); found {
|
||||||
|
if types, ok := cachedData.([]models.ModelType); ok {
|
||||||
|
c.JSON(http.StatusOK, types)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var types []models.ModelType
|
var types []models.ModelType
|
||||||
|
|
||||||
// 使用GORM查询所有模型类型,按排序字段和键值排序
|
// 使用GORM查询所有模型类型,按排序字段和键值排序
|
||||||
@ -19,6 +30,9 @@ func GetModelTypes(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 存入缓存,有效期30分钟
|
||||||
|
database.GlobalCache.Set(cacheKey, types, 30*time.Minute)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, types)
|
c.JSON(http.StatusOK, types)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +50,9 @@ func CreateModelType(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
database.GlobalCache.Delete("model_types")
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, newType)
|
c.JSON(http.StatusCreated, newType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,44 +72,19 @@ func UpdateModelType(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果key发生变化,需要删除旧记录并创建新记录
|
// 更新记录
|
||||||
if typeKey != updateType.TypeKey {
|
existingType.TypeLabel = updateType.TypeLabel
|
||||||
// 开始事务
|
existingType.SortOrder = updateType.SortOrder
|
||||||
tx := database.DB.Begin()
|
|
||||||
|
|
||||||
// 删除旧记录
|
if err := database.DB.Save(&existingType).Error; err != nil {
|
||||||
if err := tx.Delete(&existingType).Error; err != nil {
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
tx.Rollback()
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete old model type"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新记录
|
// 清除缓存
|
||||||
if err := tx.Create(&updateType).Error; err != nil {
|
database.GlobalCache.Delete("model_types")
|
||||||
tx.Rollback()
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create new model type"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交事务
|
c.JSON(http.StatusOK, existingType)
|
||||||
if err := tx.Commit().Error; err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to commit transaction"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 直接更新,只更新特定字段
|
|
||||||
if err := database.DB.Model(&existingType).Updates(map[string]interface{}{
|
|
||||||
"type_label": updateType.TypeLabel,
|
|
||||||
"sort_order": updateType.SortOrder,
|
|
||||||
}).Error; err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update model type"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updateType = existingType
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, updateType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteModelType 删除模型类型
|
// DeleteModelType 删除模型类型
|
||||||
@ -120,9 +112,12 @@ func DeleteModelType(c *gin.Context) {
|
|||||||
|
|
||||||
// 删除记录
|
// 删除记录
|
||||||
if err := database.DB.Delete(&existingType).Error; err != nil {
|
if err := database.DB.Delete(&existingType).Error; err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete model type"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
database.GlobalCache.Delete("model_types")
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "Model type deleted successfully"})
|
c.JSON(http.StatusOK, gin.H{"message": "Model type deleted successfully"})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@ -27,8 +28,20 @@ func GetPrices(c *gin.Context) {
|
|||||||
|
|
||||||
offset := (page - 1) * pageSize
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
// 构建查询
|
// 构建缓存键
|
||||||
query := database.DB.Model(&models.Price{})
|
cacheKey := fmt.Sprintf("prices_page_%d_size_%d_channel_%s_type_%s",
|
||||||
|
page, pageSize, channelType, modelType)
|
||||||
|
|
||||||
|
// 尝试从缓存获取
|
||||||
|
if cachedData, found := database.GlobalCache.Get(cacheKey); found {
|
||||||
|
if result, ok := cachedData.(gin.H); ok {
|
||||||
|
c.JSON(http.StatusOK, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建查询 - 使用索引优化
|
||||||
|
query := database.DB.Model(&models.Price{}).Select("*")
|
||||||
|
|
||||||
// 添加筛选条件
|
// 添加筛选条件
|
||||||
if channelType != "" {
|
if channelType != "" {
|
||||||
@ -38,24 +51,44 @@ func GetPrices(c *gin.Context) {
|
|||||||
query = query.Where("model_type = ?", modelType)
|
query = query.Where("model_type = ?", modelType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取总数
|
// 获取总数 - 使用缓存优化
|
||||||
var total int64
|
var total int64
|
||||||
|
totalCacheKey := fmt.Sprintf("prices_count_channel_%s_type_%s", channelType, modelType)
|
||||||
|
|
||||||
|
if cachedTotal, found := database.GlobalCache.Get(totalCacheKey); found {
|
||||||
|
if t, ok := cachedTotal.(int64); ok {
|
||||||
|
total = t
|
||||||
|
} else {
|
||||||
if err := query.Count(&total).Error; err != nil {
|
if err := query.Count(&total).Error; err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count prices"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count prices"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
database.GlobalCache.Set(totalCacheKey, total, 5*time.Minute)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count prices"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
database.GlobalCache.Set(totalCacheKey, total, 5*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取分页数据
|
// 获取分页数据 - 使用索引优化
|
||||||
var prices []models.Price
|
var prices []models.Price
|
||||||
if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&prices).Error; err != nil {
|
if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&prices).Error; err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch prices"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch prices"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
result := gin.H{
|
||||||
"total": total,
|
"total": total,
|
||||||
"data": prices,
|
"data": prices,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// 存入缓存,有效期5分钟
|
||||||
|
database.GlobalCache.Set(cacheKey, result, 5*time.Minute)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreatePrice(c *gin.Context) {
|
func CreatePrice(c *gin.Context) {
|
||||||
@ -93,6 +126,9 @@ func CreatePrice(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除所有价格相关缓存
|
||||||
|
clearPriceCache()
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, price)
|
c.JSON(http.StatusCreated, price)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +230,9 @@ func UpdatePriceStatus(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除所有价格相关缓存
|
||||||
|
clearPriceCache()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "Status updated successfully",
|
"message": "Status updated successfully",
|
||||||
"status": input.Status,
|
"status": input.Status,
|
||||||
@ -288,6 +327,9 @@ func UpdatePrice(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除所有价格相关缓存
|
||||||
|
clearPriceCache()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, existingPrice)
|
c.JSON(http.StatusOK, existingPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +349,9 @@ func DeletePrice(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除所有价格相关缓存
|
||||||
|
clearPriceCache()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "Price deleted successfully"})
|
c.JSON(http.StatusOK, gin.H{"message": "Price deleted successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,33 +367,45 @@ type PriceRate struct {
|
|||||||
|
|
||||||
// GetPriceRates 获取价格倍率
|
// GetPriceRates 获取价格倍率
|
||||||
func GetPriceRates(c *gin.Context) {
|
func GetPriceRates(c *gin.Context) {
|
||||||
|
cacheKey := "price_rates"
|
||||||
|
|
||||||
|
// 尝试从缓存获取
|
||||||
|
if cachedData, found := database.GlobalCache.Get(cacheKey); found {
|
||||||
|
if rates, ok := cachedData.([]PriceRate); ok {
|
||||||
|
c.JSON(http.StatusOK, rates)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用索引优化查询,只查询需要的字段
|
||||||
var prices []models.Price
|
var prices []models.Price
|
||||||
if err := database.DB.Where("status = 'approved'").Find(&prices).Error; err != nil {
|
if err := database.DB.Select("model, model_type, billing_type, channel_type, input_price, output_price").
|
||||||
|
Where("status = 'approved'").
|
||||||
|
Find(&prices).Error; err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch prices"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch prices"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按模型分组
|
// 按模型分组 - 使用map优化
|
||||||
modelMap := make(map[string]map[uint]models.Price)
|
modelMap := make(map[string]map[uint]models.Price, len(prices)/2) // 预分配合理大小
|
||||||
for _, price := range prices {
|
for _, price := range prices {
|
||||||
if _, exists := modelMap[price.Model]; !exists {
|
if _, exists := modelMap[price.Model]; !exists {
|
||||||
modelMap[price.Model] = make(map[uint]models.Price)
|
modelMap[price.Model] = make(map[uint]models.Price, 5) // 假设每个模型有5个提供商
|
||||||
}
|
}
|
||||||
modelMap[price.Model][price.ChannelType] = price
|
modelMap[price.Model][price.ChannelType] = price
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预分配rates切片,减少内存分配
|
||||||
|
rates := make([]PriceRate, 0, len(prices))
|
||||||
|
|
||||||
// 计算倍率
|
// 计算倍率
|
||||||
var rates []PriceRate
|
|
||||||
for model, providers := range modelMap {
|
for model, providers := range modelMap {
|
||||||
// 找出基准价格(通常是OpenAI的价格)
|
// 找出基准价格(通常是OpenAI的价格)
|
||||||
var basePrice models.Price
|
var basePrice models.Price
|
||||||
var found bool
|
var found bool
|
||||||
for _, price := range providers {
|
if baseProvider, exists := providers[1]; exists { // 直接检查ID为1的提供商
|
||||||
if price.ChannelType == 1 { // 假设OpenAI的ID是1
|
basePrice = baseProvider
|
||||||
basePrice = price
|
|
||||||
found = true
|
found = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
@ -383,6 +440,9 @@ func GetPriceRates(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 存入缓存,有效期10分钟
|
||||||
|
database.GlobalCache.Set(cacheKey, rates, 10*time.Minute)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, rates)
|
c.JSON(http.StatusOK, rates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,8 +514,17 @@ func ApproveAllPrices(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除所有价格相关缓存
|
||||||
|
clearPriceCache()
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "All pending prices approved successfully",
|
"message": "All pending prices approved successfully",
|
||||||
"count": len(pendingPrices),
|
"count": len(pendingPrices),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clearPriceCache 清除所有价格相关的缓存
|
||||||
|
func clearPriceCache() {
|
||||||
|
// 由于我们无法精确知道哪些缓存键与价格相关,所以清除所有缓存
|
||||||
|
database.GlobalCache.Clear()
|
||||||
|
}
|
||||||
|
@ -13,6 +13,16 @@ import (
|
|||||||
|
|
||||||
// GetProviders 获取所有模型厂商
|
// GetProviders 获取所有模型厂商
|
||||||
func GetProviders(c *gin.Context) {
|
func GetProviders(c *gin.Context) {
|
||||||
|
cacheKey := "providers"
|
||||||
|
|
||||||
|
// 尝试从缓存获取
|
||||||
|
if cachedData, found := database.GlobalCache.Get(cacheKey); found {
|
||||||
|
if providers, ok := cachedData.([]models.Provider); ok {
|
||||||
|
c.JSON(http.StatusOK, providers)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var providers []models.Provider
|
var providers []models.Provider
|
||||||
|
|
||||||
if err := database.DB.Order("id").Find(&providers).Error; err != nil {
|
if err := database.DB.Order("id").Find(&providers).Error; err != nil {
|
||||||
@ -20,6 +30,9 @@ func GetProviders(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 存入缓存,有效期30分钟
|
||||||
|
database.GlobalCache.Set(cacheKey, providers, 30*time.Minute)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, providers)
|
c.JSON(http.StatusOK, providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +69,9 @@ func CreateProvider(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
database.GlobalCache.Delete("providers")
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, provider)
|
c.JSON(http.StatusCreated, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +143,9 @@ func UpdateProvider(c *gin.Context) {
|
|||||||
provider = existingProvider
|
provider = existingProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
database.GlobalCache.Delete("providers")
|
||||||
|
|
||||||
c.JSON(http.StatusOK, provider)
|
c.JSON(http.StatusOK, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,5 +231,8 @@ func DeleteProvider(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
database.GlobalCache.Delete("providers")
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "Provider deleted successfully"})
|
c.JSON(http.StatusOK, gin.H{"message": "Provider deleted successfully"})
|
||||||
}
|
}
|
||||||
|
@ -8,16 +8,16 @@ import (
|
|||||||
|
|
||||||
type Price struct {
|
type Price struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
Model string `json:"model" gorm:"not null"`
|
Model string `json:"model" gorm:"not null;index:idx_model_channel"`
|
||||||
ModelType string `json:"model_type" gorm:"not null"` // text2text, text2image, etc.
|
ModelType string `json:"model_type" gorm:"not null;index:idx_model_type"` // text2text, text2image, etc.
|
||||||
BillingType string `json:"billing_type" gorm:"not null"` // tokens or times
|
BillingType string `json:"billing_type" gorm:"not null"` // tokens or times
|
||||||
ChannelType uint `json:"channel_type" gorm:"not null"`
|
ChannelType uint `json:"channel_type" gorm:"not null;index:idx_model_channel"`
|
||||||
Currency string `json:"currency" gorm:"not null"` // USD or CNY
|
Currency string `json:"currency" gorm:"not null"` // USD or CNY
|
||||||
InputPrice float64 `json:"input_price" gorm:"not null"`
|
InputPrice float64 `json:"input_price" gorm:"not null"`
|
||||||
OutputPrice float64 `json:"output_price" gorm:"not null"`
|
OutputPrice float64 `json:"output_price" gorm:"not null"`
|
||||||
PriceSource string `json:"price_source" gorm:"not null"`
|
PriceSource string `json:"price_source" gorm:"not null"`
|
||||||
Status string `json:"status" gorm:"not null;default:pending"` // pending, approved, rejected
|
Status string `json:"status" gorm:"not null;default:pending;index:idx_status"` // pending, approved, rejected
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index:idx_created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
CreatedBy string `json:"created_by" gorm:"not null"`
|
CreatedBy string `json:"created_by" gorm:"not null"`
|
||||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="model-types">
|
<div class="model-types-container">
|
||||||
<el-card v-loading="loading" element-loading-text="加载中...">
|
<el-card v-loading="loading" element-loading-text="加载中..." class="model-types-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>模型类别</span>
|
<span class="title">模型类别</span>
|
||||||
<el-button v-if="isAdmin" type="primary" @click="handleAdd">添加模型类别</el-button>
|
<el-button v-if="isAdmin" type="primary" @click="handleAdd">添加模型类别</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
:data="modelTypes"
|
:data="modelTypes"
|
||||||
style="width: 100%"
|
|
||||||
v-loading="tableLoading"
|
v-loading="tableLoading"
|
||||||
element-loading-text="加载中..."
|
element-loading-text="加载中..."
|
||||||
row-key="key"
|
row-key="key"
|
||||||
|
class="model-types-table"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
>
|
>
|
||||||
<el-table-column prop="key" label="类别键值" width="180" />
|
<el-table-column prop="key" label="类别键值" min-width="200" />
|
||||||
<el-table-column prop="label" label="类别名称" width="180" />
|
<el-table-column prop="label" label="类别名称" min-width="200" />
|
||||||
<el-table-column prop="sort_order" label="排序" width="100" />
|
<el-table-column prop="sort_order" label="排序" min-width="120" align="center" />
|
||||||
<el-table-column v-if="isAdmin" label="操作" width="200">
|
<el-table-column v-if="isAdmin" label="操作" min-width="150" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
@ -168,14 +169,49 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.model-types-container {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-types-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-types-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 响应式布局 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.model-types-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-types-card {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -252,10 +252,9 @@
|
|||||||
:small="false"
|
:small="false"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
size-change-label="条/页"
|
|
||||||
>
|
>
|
||||||
<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>
|
||||||
@ -529,7 +528,11 @@ const selectedModelType = ref('')
|
|||||||
const isAdmin = computed(() => props.user?.role === 'admin')
|
const isAdmin = computed(() => props.user?.role === 'admin')
|
||||||
|
|
||||||
const providers = ref([])
|
const providers = ref([])
|
||||||
const getProvider = (id) => providers.value.find(p => p.id.toString() === id)
|
const getProvider = (id) => {
|
||||||
|
// 确保id是字符串类型进行比较
|
||||||
|
const idStr = id?.toString()
|
||||||
|
return providers.value.find(p => p.id.toString() === idStr)
|
||||||
|
}
|
||||||
|
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'pending': '待审核',
|
'pending': '待审核',
|
||||||
@ -592,14 +595,14 @@ const loadPrices = async () => {
|
|||||||
axios.get('/api/providers')
|
axios.get('/api/providers')
|
||||||
])
|
])
|
||||||
|
|
||||||
prices.value = pricesRes.data.prices
|
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}`
|
const cacheKey = `${currentPage.value}-${pageSize.value}-${selectedProvider.value}-${selectedModelType.value}`
|
||||||
cachedPrices.value.set(cacheKey, {
|
cachedPrices.value.set(cacheKey, {
|
||||||
prices: pricesRes.data.prices,
|
prices: pricesRes.data.data,
|
||||||
total: pricesRes.data.total
|
total: pricesRes.data.total
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1280,6 +1283,7 @@ onMounted(() => {
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 添加表格行动画 */
|
/* 添加表格行动画 */
|
||||||
@ -1297,12 +1301,27 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-select .el-input {
|
.el-select .el-input {
|
||||||
width: 110px !important;
|
width: 140px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select-dropdown__item {
|
.el-select-dropdown__item {
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-pagination__sizes {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修复选择框宽度问题 */
|
||||||
|
.el-select__wrapper {
|
||||||
|
min-width: 140px !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保下拉菜单也足够宽 */
|
||||||
|
.el-select__popper {
|
||||||
|
min-width: 140px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
@ -1318,4 +1337,36 @@ onMounted(() => {
|
|||||||
.action-buttons :deep(.el-icon) {
|
.action-buttons :deep(.el-icon) {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 添加全局样式覆盖 */
|
||||||
|
:global(.el-pagination .el-select__wrapper) {
|
||||||
|
min-width: 140px !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-pagination .el-select-dropdown__wrap) {
|
||||||
|
min-width: 140px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-pagination .el-select .el-input__wrapper) {
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 140px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局样式,确保分页选择框宽度足够 */
|
||||||
|
.el-pagination .el-select__wrapper {
|
||||||
|
min-width: 140px !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-pagination .el-select .el-input__wrapper {
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 140px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select-dropdown {
|
||||||
|
min-width: 140px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="providers">
|
<div class="providers-container">
|
||||||
<el-card v-loading="loading" element-loading-text="加载中...">
|
<el-card v-loading="loading" element-loading-text="加载中...">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>模型厂商</span>
|
<span class="title">模型厂商</span>
|
||||||
<el-button type="primary" @click="handleAdd">添加模型厂商</el-button>
|
<el-button type="primary" @click="handleAdd">添加模型厂商</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table :data="sortedProviders" style="width: 100%" v-loading="tableLoading" element-loading-text="加载中...">
|
<el-table :data="sortedProviders" style="width: 100%" v-loading="tableLoading" element-loading-text="加载中..." class="providers-table" :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
|
||||||
<el-table-column prop="id" label="ID" />
|
<el-table-column prop="id" label="ID" min-width="200"/>
|
||||||
<el-table-column label="名称">
|
<el-table-column label="名称" min-width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.name }}
|
{{ row.name }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="图标">
|
<el-table-column label="图标" min-width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="row.icon"
|
v-if="row.icon"
|
||||||
@ -26,8 +26,8 @@
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="created_by" label="创建者" />
|
<!-- <el-table-column prop="created_by" label="创建者" min-width="100"/> -->
|
||||||
<el-table-column v-if="isAdmin" label="操作" width="200">
|
<el-table-column v-if="isAdmin" label="操作" min-width="150">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
@ -233,4 +233,34 @@ const submitForm = async () => {
|
|||||||
stroke: #409EFF;
|
stroke: #409EFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.providers-container {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.providers-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.providers-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式布局 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.providers-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.providers-card {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -7,7 +7,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:8080',
|
target: 'https://aimodels-prices.q58.club',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user