mirror of
https://github.com/woodchen-ink/aimodels-prices.git
synced 2025-07-18 13:41:59 +08:00
Replace "supplier" terminology with "model vendor" across frontend and backend
This commit is contained in:
parent
7873fab16f
commit
1d16333e11
@ -51,7 +51,7 @@ func createTables() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建供应商表
|
// 创建模型厂商表
|
||||||
if _, err := DB.Exec(models.CreateProviderTableSQL()); err != nil {
|
if _, err := DB.Exec(models.CreateProviderTableSQL()); err != nil {
|
||||||
log.Printf("Failed to create provider table: %v", err)
|
log.Printf("Failed to create provider table: %v", err)
|
||||||
return err
|
return err
|
||||||
|
@ -49,7 +49,7 @@ func CreatePrice(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证供应商ID是否存在
|
// 验证模型厂商ID是否存在
|
||||||
db := c.MustGet("db").(*sql.DB)
|
db := c.MustGet("db").(*sql.DB)
|
||||||
var providerExists bool
|
var providerExists bool
|
||||||
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists)
|
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists)
|
||||||
@ -157,7 +157,7 @@ func UpdatePrice(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证供应商ID是否存在
|
// 验证模型厂商ID是否存在
|
||||||
db := c.MustGet("db").(*sql.DB)
|
db := c.MustGet("db").(*sql.DB)
|
||||||
var providerExists bool
|
var providerExists bool
|
||||||
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists)
|
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM provider WHERE id = ?)", price.ChannelType).Scan(&providerExists)
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"aimodels-prices/models"
|
"aimodels-prices/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetProviders 获取所有供应商
|
// GetProviders 获取所有模型厂商
|
||||||
func GetProviders(c *gin.Context) {
|
func GetProviders(c *gin.Context) {
|
||||||
db := c.MustGet("db").(*sql.DB)
|
db := c.MustGet("db").(*sql.DB)
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
@ -38,7 +38,7 @@ func GetProviders(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, providers)
|
c.JSON(http.StatusOK, providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateProvider 创建供应商
|
// CreateProvider 创建模型厂商
|
||||||
func CreateProvider(c *gin.Context) {
|
func CreateProvider(c *gin.Context) {
|
||||||
var provider models.Provider
|
var provider models.Provider
|
||||||
if err := c.ShouldBindJSON(&provider); err != nil {
|
if err := c.ShouldBindJSON(&provider); err != nil {
|
||||||
@ -80,7 +80,7 @@ func CreateProvider(c *gin.Context) {
|
|||||||
c.JSON(http.StatusCreated, provider)
|
c.JSON(http.StatusCreated, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateProvider 更新供应商
|
// UpdateProvider 更新模型厂商
|
||||||
func UpdateProvider(c *gin.Context) {
|
func UpdateProvider(c *gin.Context) {
|
||||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -106,7 +106,7 @@ func UpdateProvider(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取更新后的供应商信息
|
// 获取更新后的模型厂商信息
|
||||||
err = db.QueryRow(`
|
err = db.QueryRow(`
|
||||||
SELECT id, name, icon, created_at, updated_at, created_by
|
SELECT id, name, icon, created_at, updated_at, created_by
|
||||||
FROM provider WHERE id = ?`, id).Scan(
|
FROM provider WHERE id = ?`, id).Scan(
|
||||||
@ -120,7 +120,7 @@ func UpdateProvider(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, provider)
|
c.JSON(http.StatusOK, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateProviderStatus 更新供应商状态
|
// UpdateProviderStatus 更新模型厂商状态
|
||||||
func UpdateProviderStatus(c *gin.Context) {
|
func UpdateProviderStatus(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
var input struct {
|
var input struct {
|
||||||
@ -174,7 +174,7 @@ func UpdateProviderStatus(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteProvider 删除供应商
|
// DeleteProvider 删除模型厂商
|
||||||
func DeleteProvider(c *gin.Context) {
|
func DeleteProvider(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
db := c.MustGet("db").(*sql.DB)
|
db := c.MustGet("db").(*sql.DB)
|
||||||
|
@ -70,7 +70,7 @@ func main() {
|
|||||||
prices.PUT("/:id/status", middleware.AuthRequired(), middleware.AdminRequired(), handlers.UpdatePriceStatus)
|
prices.PUT("/:id/status", middleware.AuthRequired(), middleware.AdminRequired(), handlers.UpdatePriceStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 供应商相关路由
|
// 模型厂商相关路由
|
||||||
providers := api.Group("/providers")
|
providers := api.Group("/providers")
|
||||||
{
|
{
|
||||||
providers.GET("", handlers.GetProviders)
|
providers.GET("", handlers.GetProviders)
|
||||||
|
@ -11,7 +11,7 @@ type Provider struct {
|
|||||||
CreatedBy string `json:"created_by"`
|
CreatedBy string `json:"created_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateProviderTableSQL 返回创建供应商表的 SQL
|
// CreateProviderTableSQL 返回创建模型厂商表的 SQL
|
||||||
func CreateProviderTableSQL() string {
|
func CreateProviderTableSQL() string {
|
||||||
return `
|
return `
|
||||||
CREATE TABLE IF NOT EXISTS provider (
|
CREATE TABLE IF NOT EXISTS provider (
|
||||||
|
@ -22,7 +22,7 @@ func SetupRouter() *gin.Engine {
|
|||||||
auth.POST("/logout", handlers.Logout)
|
auth.POST("/logout", handlers.Logout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 供应商相关路由
|
// 模型厂商相关路由
|
||||||
providers := r.Group("/providers")
|
providers := r.Group("/providers")
|
||||||
{
|
{
|
||||||
providers.GET("", handlers.GetProviders)
|
providers.GET("", handlers.GetProviders)
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="description" content="专业的AI模型价格管理系统,支持多供应商、多币种的价格管理,提供标准的API接口。">
|
<meta name="description" content="专业的AI模型价格管理系统,支持多模型厂商、多币种的价格管理,提供标准的API接口。">
|
||||||
<meta name="keywords" content="AI模型,价格管理,API接口,OpenAI,Azure,Anthropic">
|
<meta name="keywords" content="AI模型,价格管理,API接口,OpenAI,Azure,Anthropic">
|
||||||
<meta name="author" content="AI Models Prices Team">
|
<meta name="author" content="AI Models Prices Team">
|
||||||
<title>AI模型价格</title>
|
<title>AI模型价格</title>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<div class="nav-buttons">
|
<div class="nav-buttons">
|
||||||
<el-button @click="$router.push('/prices')" :type="$route.path === '/prices' ? 'primary' : ''">价格列表</el-button>
|
<el-button @click="$router.push('/prices')" :type="$route.path === '/prices' ? 'primary' : ''">价格列表</el-button>
|
||||||
<el-button @click="$router.push('/providers')" :type="$route.path === '/providers' ? 'primary' : ''">供应商列表</el-button>
|
<el-button @click="$router.push('/providers')" :type="$route.path === '/providers' ? 'primary' : ''">模型厂商</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-buttons">
|
<div class="auth-buttons">
|
||||||
|
@ -8,11 +8,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h2>项目简介</h2>
|
<h2>项目简介</h2>
|
||||||
<p>这是一个专门用于管理AI模型价格的系统,支持多供应商、多币种的价格管理,并提供标准的API接口供其他系统调用。</p>
|
<p>这是一个专门用于管理AI模型价格的系统,支持多模型厂商、多币种的价格管理,并提供标准的API接口供其他系统调用。</p>
|
||||||
|
|
||||||
<h2>主要功能</h2>
|
<h2>主要功能</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>供应商管理:添加、编辑和删除AI模型供应商</li>
|
<li>模型厂商管理:添加、编辑和删除AI模型模型厂商</li>
|
||||||
<li>价格管理:设置和更新各个模型的价格</li>
|
<li>价格管理:设置和更新各个模型的价格</li>
|
||||||
<li>多币种支持:支持USD和CNY两种货币</li>
|
<li>多币种支持:支持USD和CNY两种货币</li>
|
||||||
<li>审核流程:价格变更需要管理员审核</li>
|
<li>审核流程:价格变更需要管理员审核</li>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>model: 模型名称</li>
|
<li>model: 模型名称</li>
|
||||||
<li>type: 计费类型(tokens/times)</li>
|
<li>type: 计费类型(tokens/times)</li>
|
||||||
<li>channel_type: 供应商ID</li>
|
<li>channel_type: 模型厂商ID</li>
|
||||||
<li>input: 输入价格倍率</li>
|
<li>input: 输入价格倍率</li>
|
||||||
<li>output: 输出价格倍率</li>
|
<li>output: 输出价格倍率</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -83,7 +83,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-collapse-item>
|
</el-collapse-item>
|
||||||
|
|
||||||
<el-collapse-item title="获取供应商列表">
|
<el-collapse-item title="获取模型厂商">
|
||||||
<div class="api-doc">
|
<div class="api-doc">
|
||||||
<div class="api-url">
|
<div class="api-url">
|
||||||
<span class="method">GET</span>
|
<span class="method">GET</span>
|
||||||
@ -93,7 +93,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<p>获取所有供应商信息</p>
|
<p>获取所有模型厂商信息</p>
|
||||||
<h4>响应示例:</h4>
|
<h4>响应示例:</h4>
|
||||||
<pre>
|
<pre>
|
||||||
[
|
[
|
||||||
@ -219,7 +219,7 @@ onMounted(() => {
|
|||||||
// 添加页面元信息
|
// 添加页面元信息
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'AI模型价格 - 首页',
|
title: 'AI模型价格 - 首页',
|
||||||
description: '专业的AI模型价格管理系统,支持多供应商、多币种的价格管理,提供标准的API接口。',
|
description: '专业的AI模型价格管理系统,支持多模型厂商、多币种的价格管理,提供标准的API接口。',
|
||||||
keywords: 'AI模型,价格管理,API接口,OpenAI,Azure,Anthropic'
|
keywords: 'AI模型,价格管理,API接口,OpenAI,Azure,Anthropic'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +151,31 @@
|
|||||||
<div class="batch-toolbar">
|
<div class="batch-toolbar">
|
||||||
<el-button type="primary" @click="addRow">添加行</el-button>
|
<el-button type="primary" @click="addRow">添加行</el-button>
|
||||||
<el-button type="danger" @click="removeSelectedRows" :disabled="!selectedRows.length">删除选中行</el-button>
|
<el-button type="danger" @click="removeSelectedRows" :disabled="!selectedRows.length">删除选中行</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-popover
|
||||||
|
placement="bottom"
|
||||||
|
:width="400"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button type="success">从表格导入</el-button>
|
||||||
|
</template>
|
||||||
|
<div class="import-popover">
|
||||||
|
<p class="import-tip">请粘贴表格数据(支持从Excel复制),每行格式为:</p>
|
||||||
|
<p class="import-format">模型名称 计费类型 厂商 货币 输入价格 输出价格</p>
|
||||||
|
<el-input
|
||||||
|
v-model="importText"
|
||||||
|
type="textarea"
|
||||||
|
:rows="8"
|
||||||
|
placeholder="例如:
|
||||||
|
dall-e-2 按Token收费 OpenAI 美元 16.000000 16.000000
|
||||||
|
dall-e-3 按Token收费 OpenAI 美元 40.000000 40.000000"
|
||||||
|
/>
|
||||||
|
<div class="import-actions">
|
||||||
|
<el-button type="primary" @click="handleImport">导入</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
:data="batchForms"
|
:data="batchForms"
|
||||||
@ -573,6 +598,62 @@ const submitBatchForms = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加导入相关的状态
|
||||||
|
const importText = ref('')
|
||||||
|
|
||||||
|
// 处理导入
|
||||||
|
const handleImport = () => {
|
||||||
|
if (!importText.value.trim()) {
|
||||||
|
ElMessage.warning('请先粘贴数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = importText.value.trim().split('\n')
|
||||||
|
const newRows = lines.map(line => {
|
||||||
|
const [model, billingType, providerName, currency, inputPrice, outputPrice] = line.trim().split(/\s+/)
|
||||||
|
|
||||||
|
// 查找模型厂商ID
|
||||||
|
const provider = providers.value.find(p => p.name === providerName)
|
||||||
|
if (!provider) {
|
||||||
|
ElMessage.warning(`未找到模型厂商:${providerName}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理计费类型
|
||||||
|
let billing_type = 'tokens'
|
||||||
|
if (billingType.includes('Token')) {
|
||||||
|
billing_type = 'tokens'
|
||||||
|
} else if (billingType.includes('次')) {
|
||||||
|
billing_type = 'times'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理货币
|
||||||
|
let currencyCode = 'USD'
|
||||||
|
if (currency.includes('美元')) {
|
||||||
|
currencyCode = 'USD'
|
||||||
|
} else if (currency.includes('人民币') || currency.includes('CNY')) {
|
||||||
|
currencyCode = 'CNY'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
model,
|
||||||
|
billing_type,
|
||||||
|
channel_type: provider.id.toString(),
|
||||||
|
currency: currencyCode,
|
||||||
|
input_price: parseFloat(inputPrice),
|
||||||
|
output_price: parseFloat(outputPrice),
|
||||||
|
price_source: '官方',
|
||||||
|
created_by: props.user?.username || ''
|
||||||
|
}
|
||||||
|
}).filter(row => row !== null)
|
||||||
|
|
||||||
|
if (newRows.length > 0) {
|
||||||
|
batchForms.value = [...batchForms.value, ...newRows]
|
||||||
|
importText.value = ''
|
||||||
|
ElMessage.success(`成功导入 ${newRows.length} 条数据`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadPrices()
|
await loadPrices()
|
||||||
try {
|
try {
|
||||||
@ -580,7 +661,7 @@ onMounted(async () => {
|
|||||||
providers.value = data
|
providers.value = data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load providers:', error)
|
console.error('Failed to load providers:', error)
|
||||||
ElMessage.error('加载供应商数据失败')
|
ElMessage.error('加载模型厂商数据失败')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -683,4 +764,31 @@ onMounted(async () => {
|
|||||||
:deep(.el-select) {
|
:deep(.el-select) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.import-popover {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-tip {
|
||||||
|
margin: 0;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-format {
|
||||||
|
margin: 0;
|
||||||
|
color: #409EFF;
|
||||||
|
font-size: 13px;
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -3,8 +3,8 @@
|
|||||||
<el-card>
|
<el-card>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>供应商列表</span>
|
<span>模型厂商</span>
|
||||||
<el-button type="primary" @click="handleAdd">添加供应商</el-button>
|
<el-button type="primary" @click="handleAdd">添加模型厂商</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
const isAdmin = computed(() => props.user?.role === 'admin')
|
const isAdmin = computed(() => props.user?.role === 'admin')
|
||||||
|
|
||||||
// 按ID排序的供应商列表
|
// 按ID排序的模型厂商
|
||||||
const sortedProviders = computed(() => {
|
const sortedProviders = computed(() => {
|
||||||
return [...providers.value].sort((a, b) => a.id - b.id)
|
return [...providers.value].sort((a, b) => a.id - b.id)
|
||||||
})
|
})
|
||||||
@ -103,14 +103,14 @@ onMounted(() => {
|
|||||||
|
|
||||||
const dialogTitle = computed(() => {
|
const dialogTitle = computed(() => {
|
||||||
if (editingProvider.value) {
|
if (editingProvider.value) {
|
||||||
return '编辑供应商'
|
return '编辑模型厂商'
|
||||||
}
|
}
|
||||||
return '添加供应商'
|
return '添加模型厂商'
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleEdit = (provider) => {
|
const handleEdit = (provider) => {
|
||||||
if (!isAdmin.value) {
|
if (!isAdmin.value) {
|
||||||
ElMessage.warning('只有管理员可以编辑供应商信息')
|
ElMessage.warning('只有管理员可以编辑模型厂商信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
editingProvider.value = provider
|
editingProvider.value = provider
|
||||||
@ -120,12 +120,12 @@ const handleEdit = (provider) => {
|
|||||||
|
|
||||||
const handleDelete = (provider) => {
|
const handleDelete = (provider) => {
|
||||||
if (!isAdmin.value) {
|
if (!isAdmin.value) {
|
||||||
ElMessage.warning('只有管理员可以删除供应商')
|
ElMessage.warning('只有管理员可以删除模型厂商')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
'确定要删除这个供应商吗?',
|
'确定要删除这个模型厂商吗?',
|
||||||
'警告',
|
'警告',
|
||||||
{
|
{
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
@ -162,10 +162,10 @@ const submitForm = async () => {
|
|||||||
try {
|
try {
|
||||||
if (editingProvider.value) {
|
if (editingProvider.value) {
|
||||||
if (!isAdmin.value) {
|
if (!isAdmin.value) {
|
||||||
ElMessage.error('只有管理员可以编辑供应商信息')
|
ElMessage.error('只有管理员可以编辑模型厂商信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 管理员编辑供应商
|
// 管理员编辑模型厂商
|
||||||
const { data } = await axios.put(`/api/providers/${editingProvider.value.id}`, form.value)
|
const { data } = await axios.put(`/api/providers/${editingProvider.value.id}`, form.value)
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
ElMessage.error(data.error)
|
ElMessage.error(data.error)
|
||||||
@ -177,7 +177,7 @@ const submitForm = async () => {
|
|||||||
}
|
}
|
||||||
ElMessage.success('更新成功')
|
ElMessage.success('更新成功')
|
||||||
} else {
|
} else {
|
||||||
// 创建新供应商
|
// 创建新模型厂商
|
||||||
const { data } = await axios.post('/api/providers', form.value)
|
const { data } = await axios.post('/api/providers', form.value)
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
ElMessage.error(data.error)
|
ElMessage.error(data.error)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user