diff --git a/backend/internal/repo/channel_repo.go b/backend/internal/repo/channel_repo.go index 5dbc9dc..73d71a8 100644 --- a/backend/internal/repo/channel_repo.go +++ b/backend/internal/repo/channel_repo.go @@ -183,6 +183,26 @@ func (r *ChannelRepository) RoutableModelIDs(ctx context.Context) (map[string]st return out, err } +// ChannelNamesByModel returns model_id -> bound channel display names in one +// JOIN query — used by the admin models table to annotate each row with the +// channel(s) it was synced from. +func (r *ChannelRepository) ChannelNamesByModel(ctx context.Context) (map[string][]string, error) { + var rows []struct { + ModelID string `gorm:"column:model_id"` + Name string `gorm:"column:name"` + } + err := r.db.WithContext(ctx). + Table("channel_models"). + Select("channel_models.model_id, channels.name"). + Joins("JOIN channels ON channels.id = channel_models.channel_id"). + Scan(&rows).Error + out := make(map[string][]string, len(rows)) + for _, row := range rows { + out[row.ModelID] = append(out[row.ModelID], row.Name) + } + return out, err +} + // ChannelIDsByModel lists channel ids bound to a model (any enabled state). func (r *ChannelRepository) ChannelIDsByModel(ctx context.Context, modelID string) ([]string, error) { var ids []string diff --git a/backend/internal/service/admin_read.go b/backend/internal/service/admin_read.go index 2e9c081..7d80729 100644 --- a/backend/internal/service/admin_read.go +++ b/backend/internal/service/admin_read.go @@ -85,6 +85,7 @@ func (s *AdminReadService) ModelsView(ctx context.Context) ([]map[string]any, er return nil, err } routable, _ := s.channels.RoutableModelIDs(ctx) + channelNames, _ := s.channels.ChannelNamesByModel(ctx) out := make([]map[string]any, 0, len(items)) for _, item := range items { _, bound := routable[item.ID] @@ -94,6 +95,7 @@ func (s *AdminReadService) ModelsView(ctx context.Context) ([]map[string]any, er "type": item.Type, "name": item.Name, "provider": item.Provider, + "channels": channelNames[item.ID], // 配价即上架(无启停开关):全档位有价 + 绑定了启用渠道才算可售。 "sellable": bound && ModelFullyPriced(&item), "ratios": repo.JSONStrings(item.Ratios), diff --git a/frontend/src/components/canvas/CanvasNodeView.vue b/frontend/src/components/canvas/CanvasNodeView.vue index 0d335d5..3149c06 100644 --- a/frontend/src/components/canvas/CanvasNodeView.vue +++ b/frontend/src/components/canvas/CanvasNodeView.vue @@ -288,7 +288,7 @@ defineExpose({ startEdit, commitNote }) /* 笔记 = 白色卡片 */ .cv-node--note { - background: #fff; + background: var(--surface-solid); border-color: rgb(24 24 27 / 0.1); cursor: text; } @@ -416,7 +416,7 @@ html.dark .cv-node--note { width: 16px; height: 16px; border-radius: 50%; - background: #fff; + background: var(--surface-solid); border: 2px solid #18181b; box-shadow: 0 1px 4px rgb(0 0 0 / 0.25); transition: transform 0.15s ease, box-shadow 0.15s ease; diff --git a/frontend/src/layouts/PublicLayout.vue b/frontend/src/layouts/PublicLayout.vue index 51771cd..ef8c2f7 100644 --- a/frontend/src/layouts/PublicLayout.vue +++ b/frontend/src/layouts/PublicLayout.vue @@ -340,7 +340,7 @@ html.dark .nav-inner { box-shadow: 0 8px 30px -14px rgb(0 0 0 / 0.5); } font-size: 13px; font-weight: 700; color: var(--fg); - background: #fff; + background: var(--surface-solid); transition: background 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease; } .name-btn:hover, .name-btn.open { @@ -373,7 +373,7 @@ html.dark .nav-inner { box-shadow: 0 8px 30px -14px rgb(0 0 0 / 0.5); } width: 16rem; padding: 0.5rem; border-radius: 1rem; - background: #fff; + background: var(--surface-solid); border: 1px solid rgb(24 24 27 / 0.08); box-shadow: 0 2px 6px rgb(24 24 27 / 0.05), 0 24px 48px -12px rgb(24 24 27 / 0.22); z-index: 50; diff --git a/frontend/src/views/AboutView.vue b/frontend/src/views/AboutView.vue index 0e44cd9..b6f58b9 100644 --- a/frontend/src/views/AboutView.vue +++ b/frontend/src/views/AboutView.vue @@ -10,24 +10,31 @@ const powers = [ { icon: 't2v', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', tint: 'linear-gradient(135deg,#38bdf8,#52525b)' }, { icon: 'i2v', title: '图生视频', desc: '给张参考图,让静态画面动起来。', tint: 'linear-gradient(135deg,#18181b,#3f3f46)' }, ] + +const contactCards = computed(() => { + const c = contact.value || {} + const list = [] + if (c.shop) list.push({ label: '商店', value: '前往商店', href: c.shop }) + if (c.email) list.push({ label: '邮箱', value: c.email, href: `mailto:${c.email}` }) + if (c.qq) list.push({ label: 'QQ', value: c.qq, href: c.qq_link || '' }) + if (c.qq_group) list.push({ label: 'QQ 群', value: c.qq_group, href: c.qq_group_link || '' }) + return list +}) diff --git a/frontend/src/views/BannedWordHitsView.vue b/frontend/src/views/BannedWordHitsView.vue index 49ddfd5..ba70f58 100644 --- a/frontend/src/views/BannedWordHitsView.vue +++ b/frontend/src/views/BannedWordHitsView.vue @@ -113,8 +113,8 @@ function goPage(n) { diff --git a/frontend/src/views/BannedWordsView.vue b/frontend/src/views/BannedWordsView.vue index 968dea0..5be09c7 100644 --- a/frontend/src/views/BannedWordsView.vue +++ b/frontend/src/views/BannedWordsView.vue @@ -200,11 +200,11 @@ onMounted(load) .act { display: inline-flex; align-items: center; justify-content: center; width: 1.9rem; height: 1.9rem; border-radius: 0.5rem; - color: rgb(255 255 255 / 0.7); background: rgb(255 255 255 / 0.04); - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); + color: var(--fg-2); background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s; } -.act:hover { background: rgb(255 255 255 / 0.1); color: white; } +.act:hover { background: var(--hover); color: var(--fg); } .act.danger { color: rgb(253 164 175); background: rgb(244 63 94 / 0.12); box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.3); } .act.danger:hover { color: white; background: rgb(244 63 94 / 0.25); } .btn-soft.danger { @@ -217,9 +217,9 @@ onMounted(load) background: rgb(244 63 94 / 0.25); } .chk { accent-color: rgb(244 63 94); width: 0.9rem; height: 0.9rem; cursor: pointer; } -.pg { min-width: 1.75rem; padding: 0.3rem 0.55rem; font-size: 0.72rem; font-weight: 500; text-align: center; border-radius: 0.45rem; color: rgb(255 255 255 / 0.7); background: rgb(255 255 255 / 0.04); box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); transition: background 0.15s, color 0.15s; } -.pg:hover:not(.pg-on) { background: rgb(255 255 255 / 0.1); color: white; } -.pg-on { background: rgb(255 255 255 / 0.92); color: rgb(15 23 42); box-shadow: none; } +.pg { min-width: 1.75rem; padding: 0.3rem 0.55rem; font-size: 0.72rem; font-weight: 500; text-align: center; border-radius: 0.45rem; color: var(--fg-2); background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s; } +.pg:hover:not(.pg-on) { background: var(--hover); color: var(--fg); } +.pg-on { background: var(--surface-solid); color: rgb(15 23 42); box-shadow: none; } .fade-enter-active, .fade-leave-active { transition: opacity 0.15s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } diff --git a/frontend/src/views/CanvasView.vue b/frontend/src/views/CanvasView.vue index 0a33dd6..49e05e3 100644 --- a/frontend/src/views/CanvasView.vue +++ b/frontend/src/views/CanvasView.vue @@ -1142,7 +1142,7 @@ const saveLabel = computed(() => ({ box-shadow: 0 4px 14px rgb(229 72 77 / 0.25); } .cv-btn--accent { - background: #fff; + background: var(--surface-solid); border-color: rgb(24 24 27 / 0.28); color: #18181b; } @@ -1193,7 +1193,7 @@ const saveLabel = computed(() => ({ padding: 6px; border-radius: 16px; border: 1px solid rgb(24 24 27 / 0.08); - background: rgb(255 255 255 / 0.92); + background: var(--surface-solid); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); box-shadow: 0 1px 2px rgb(24 24 27 / 0.04), 0 12px 32px -12px rgb(24 24 27 / 0.2); @@ -1221,7 +1221,7 @@ const saveLabel = computed(() => ({ padding: 6px; border-radius: 18px; border: 1px solid rgb(24 24 27 / 0.08); - background: rgb(255 255 255 / 0.92); + background: var(--surface-solid); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); box-shadow: 0 1px 2px rgb(24 24 27 / 0.04), 0 12px 32px -12px rgb(24 24 27 / 0.2); diff --git a/frontend/src/views/CdksView.vue b/frontend/src/views/CdksView.vue index 85e5001..b6a038f 100644 --- a/frontend/src/views/CdksView.vue +++ b/frontend/src/views/CdksView.vue @@ -327,12 +327,12 @@ const pageNumbers = computed(() => { display: inline-flex; align-items: center; justify-content: center; width: 1.9rem; height: 1.9rem; border-radius: 0.5rem; - color: rgb(255 255 255 / 0.7); - background: rgb(255 255 255 / 0.04); - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); + color: var(--fg-2); + background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s; } -.act:hover { background: rgb(255 255 255 / 0.1); color: white; } +.act:hover { background: var(--hover); color: var(--fg); } .act.danger { color: rgb(253 164 175); background: rgb(244 63 94 / 0.12); @@ -344,13 +344,13 @@ const pageNumbers = computed(() => { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.35rem 0.7rem; font-size: 0.72rem; border-radius: 0.55rem; - color: rgb(255 255 255 / 0.65); - background: rgb(255 255 255 / 0.05); - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06); + color: var(--fg-2); + background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s, box-shadow 0.15s; } -.fp:hover { background: rgb(255 255 255 / 0.09); color: white; } -.fp-on { background: rgb(255 255 255 / 0.92); color: rgb(15 23 42); box-shadow: none; } +.fp:hover { background: var(--hover); color: var(--fg); } +.fp-on { background: var(--surface-solid); color: rgb(15 23 42); box-shadow: none; } .fp-emerald { background: rgb(16 185 129 / 0.22); color: rgb(110 231 183); @@ -369,14 +369,14 @@ const pageNumbers = computed(() => { font-weight: 500; text-align: center; border-radius: 0.45rem; - color: rgb(255 255 255 / 0.7); - background: rgb(255 255 255 / 0.04); - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); + color: var(--fg-2); + background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s; } -.pg:hover:not(.pg-on) { background: rgb(255 255 255 / 0.1); color: white; } +.pg:hover:not(.pg-on) { background: var(--hover); color: var(--fg); } .pg-on { - background: rgb(255 255 255 / 0.92); + background: var(--surface-solid); color: rgb(15 23 42); box-shadow: none; } diff --git a/frontend/src/views/ChannelsView.vue b/frontend/src/views/ChannelsView.vue index f63a013..fa7d18c 100644 --- a/frontend/src/views/ChannelsView.vue +++ b/frontend/src/views/ChannelsView.vue @@ -314,20 +314,20 @@ onMounted(load) diff --git a/frontend/src/views/ConcurrencyView.vue b/frontend/src/views/ConcurrencyView.vue index 433f554..9dbdce6 100644 --- a/frontend/src/views/ConcurrencyView.vue +++ b/frontend/src/views/ConcurrencyView.vue @@ -135,11 +135,11 @@ onMounted(load) .act { display: inline-flex; align-items: center; justify-content: center; width: 1.9rem; height: 1.9rem; border-radius: 0.5rem; - color: rgb(255 255 255 / 0.7); background: rgb(255 255 255 / 0.04); - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); + color: var(--fg-2); background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s; } -.act:hover { background: rgb(255 255 255 / 0.1); color: white; } +.act:hover { background: var(--hover); color: var(--fg); } .act.danger { color: rgb(253 164 175); background: rgb(244 63 94 / 0.12); box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.3); } .act.danger:hover { color: white; background: rgb(244 63 94 / 0.25); } .fade-enter-active, .fade-leave-active { transition: opacity 0.15s ease; } diff --git a/frontend/src/views/ConfigView.vue b/frontend/src/views/ConfigView.vue index 98f2347..9104669 100644 --- a/frontend/src/views/ConfigView.vue +++ b/frontend/src/views/ConfigView.vue @@ -661,7 +661,7 @@ html.dark .hint b { color: rgb(253 164 175); } /* rose — visible warning, no width: calc(1.3rem - 4px); height: calc(1.3rem - 4px); border-radius: 9999px; - background: white; + background: var(--surface-solid); box-shadow: 0 1px 2px rgb(15 23 42 / 0.2); transition: transform 0.18s ease; } diff --git a/frontend/src/views/InviteView.vue b/frontend/src/views/InviteView.vue index 600f6e9..3e9d90f 100644 --- a/frontend/src/views/InviteView.vue +++ b/frontend/src/views/InviteView.vue @@ -238,7 +238,7 @@ function toast(m) { .inv-k { margin-top: 8px; font-size: 12.5px; font-weight: 600; color: #a1a1aa; } .steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; } -.step { display: flex; align-items: center; gap: 14px; padding: 16px 18px; background: #fff; border: 1px solid rgb(24 24 27 / .07); border-radius: 16px; } +.step { display: flex; align-items: center; gap: 14px; padding: 16px 18px; background: var(--surface-solid); border: 1px solid rgb(24 24 27 / .07); border-radius: 16px; } .step-n { flex-shrink: 0; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: #f4f4f5; color: #18181b; font-size: 14px; font-weight: 800; } .step-t { font-size: 14px; font-weight: 800; color: var(--fg); } .step-d { margin-top: 2px; font-size: 12px; color: var(--fg-3); } @@ -259,9 +259,9 @@ function toast(m) { box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); transition: background 0.15s, color 0.15s, box-shadow 0.15s; } -.pg:hover:not(.pg-on) { background: rgb(255 255 255 / 0.1); color: white; } +.pg:hover:not(.pg-on) { background: var(--hover); color: var(--fg); } .pg-on { - background: rgb(255 255 255 / 0.92); + background: var(--surface-solid); color: rgb(15 23 42); box-shadow: none; } diff --git a/frontend/src/views/LogsView.vue b/frontend/src/views/LogsView.vue index 63da281..3fb27a1 100644 --- a/frontend/src/views/LogsView.vue +++ b/frontend/src/views/LogsView.vue @@ -360,14 +360,14 @@ const sourcePill = (s) => ({ padding: 0.35rem 0.7rem; font-size: 0.72rem; border-radius: 0.55rem; - color: rgb(255 255 255 / 0.65); - background: rgb(255 255 255 / 0.05); - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06); + color: var(--fg-2); + background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s, box-shadow 0.15s; } -.fp:hover { background: rgb(255 255 255 / 0.09); color: white; } +.fp:hover { background: var(--hover); color: var(--fg); } .fp-on { - background: rgb(255 255 255 / 0.92); + background: var(--surface-solid); color: rgb(15 23 42); box-shadow: none; } @@ -418,10 +418,10 @@ const sourcePill = (s) => ({ focus indicator without being noisy). --- */ .log-table { border-collapse: separate; border-spacing: 0; } .log-row td { - border-bottom: 1px solid rgb(255 255 255 / 0.04); + border-bottom: 1px solid var(--hairline); transition: background-color 0.15s ease, box-shadow 0.15s ease; } -.log-row:hover td { background: rgb(255 255 255 / 0.025); } +.log-row:hover td { background: var(--surface-2); } .log-row:hover td:first-child { box-shadow: inset 2px 0 0 rgb(253 186 116 / 0.55); } @@ -435,14 +435,14 @@ const sourcePill = (s) => ({ font-weight: 500; text-align: center; border-radius: 0.45rem; - color: rgb(255 255 255 / 0.7); - background: rgb(255 255 255 / 0.04); - box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08); + color: var(--fg-2); + background: var(--surface-2); + box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s, color 0.15s, box-shadow 0.15s; } -.pg:hover:not(.pg-on) { background: rgb(255 255 255 / 0.1); color: white; } +.pg:hover:not(.pg-on) { background: var(--hover); color: var(--fg); } .pg-on { - background: rgb(255 255 255 / 0.92); + background: var(--surface-solid); color: rgb(15 23 42); box-shadow: none; } diff --git a/frontend/src/views/ModelsView.vue b/frontend/src/views/ModelsView.vue index 1ae3c9e..1fa0bdb 100644 --- a/frontend/src/views/ModelsView.vue +++ b/frontend/src/views/ModelsView.vue @@ -117,25 +117,23 @@ onMounted(loadModels) 还没有模型,先到「渠道管理」添加渠道,保存后会自动同步上游模型 - +
- + - - - + + - + - - - + + @@ -143,103 +141,65 @@ onMounted(loadModels) - + - + - + - + - + - - - @@ -279,96 +239,39 @@ onMounted(loadModels) diff --git a/frontend/src/views/OverviewView.vue b/frontend/src/views/OverviewView.vue index 942cbe9..f4d54b3 100644 --- a/frontend/src/views/OverviewView.vue +++ b/frontend/src/views/OverviewView.vue @@ -378,10 +378,10 @@ onUnmounted(() => clearInterval(timer)) .ov-title { font-size: 20px; font-weight: 800; letter-spacing: -0.03em; color: var(--fg); } .ov-title--sm { font-size: 16px; } .ov-sub { margin-top: 2px; font-size: 12px; color: var(--fg-3); } -.ov-btn { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 14px; border-radius: 999px; border: 1px solid rgb(24 24 27 / 0.12); background: #fff; font-size: 13px; font-weight: 600; color: var(--fg); cursor: pointer; transition: border-color .15s, box-shadow .15s; } +.ov-btn { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 14px; border-radius: 999px; border: 1px solid rgb(24 24 27 / 0.12); background: var(--surface-solid); font-size: 13px; font-weight: 600; color: var(--fg); cursor: pointer; transition: border-color .15s, box-shadow .15s; } .ov-btn:hover { border-color: rgb(24 24 27 / 0.3); box-shadow: 0 4px 12px rgb(24 24 27 / 0.08); } -.panel { background: #fff; border: 1px solid rgb(24 24 27 / 0.07); border-radius: 18px; box-shadow: 0 1px 2px rgb(24 24 27 / 0.03), 0 10px 28px -16px rgb(24 24 27 / 0.12); } +.panel { background: var(--surface-solid); border: 1px solid rgb(24 24 27 / 0.07); border-radius: 18px; box-shadow: 0 1px 2px rgb(24 24 27 / 0.03), 0 10px 28px -16px rgb(24 24 27 / 0.12); } .panel-h { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px 0; } .panel-h h3 { font-size: 14px; font-weight: 800; color: var(--fg); letter-spacing: -0.01em; } .more { font-size: 12px; font-weight: 600; color: var(--fg-3); text-decoration: none; } @@ -405,7 +405,7 @@ onUnmounted(() => clearInterval(timer)) .delta--up { color: #15803d; } .delta--down { color: #dc2626; } .delta--flat { color: var(--fg-3); } .strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } -.chip { display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: #fff; border: 1px solid rgb(24 24 27 / 0.07); border-radius: 16px; } +.chip { display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: var(--surface-solid); border: 1px solid rgb(24 24 27 / 0.07); border-radius: 16px; } .chip-ic { width: 38px; height: 38px; border-radius: 12px; display: grid; place-items: center; background: #f4f4f5; color: #18181b; flex-shrink: 0; } .chip-num { font-size: 20px; font-weight: 800; letter-spacing: -0.03em; color: var(--fg); font-variant-numeric: tabular-nums; line-height: 1.1; } .chip-num small { font-size: 13px; font-weight: 600; color: var(--fg-3); } @@ -447,7 +447,7 @@ onUnmounted(() => clearInterval(timer)) .seg { display: inline-flex; padding: 3px; border-radius: 999px; background: #f0f0f2; } .seg button { padding: 5px 14px; border: 0; border-radius: 999px; background: transparent; font-size: 12px; font-weight: 700; color: var(--fg-3); cursor: pointer; transition: all .15s; } -.seg button.on { background: #fff; color: var(--fg); box-shadow: 0 1px 3px rgb(24 24 27 / 0.12); } +.seg button.on { background: var(--surface-solid); color: var(--fg); box-shadow: 0 1px 3px rgb(24 24 27 / 0.12); } .bar-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 6px; font-size: 13px; color: var(--fg); } .bar-top > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 534a5d7..1922421 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -218,253 +218,185 @@ async function recharge() {
模型 类型 定价能力权重生图次数配置成功次数 上架 操作
-
-
{{ m.id }}
- {{ m.alias }} +
{{ m.alias || m.name || m.id }}
+
+ {{ (m.channels || []).length ? m.channels.join('、') : (m.provider === 'channel' ? '渠道导入' : (m.provider || '—')) }} · 权重 {{ m.weight || 0 }}
-
{{ m.provider === 'channel' ? '渠道导入' : (m.provider || '—') }}
- + {{ TYPE_LABEL[m.type] || m.type }} - -
- - {{ r }} - 普通 - {{ points(m.prices?.[r]) }} - 代理 - {{ points(m.prices_agent?.[r] ?? m.prices?.[r]) }} +
+ + {{ r }}{{ points(m.prices?.[r]) }}{{ points(m.prices_agent?.[r] ?? m.prices?.[r]) }} —
-
- - {{ r }} - 普通 - {{ points(m.prices?.[r]) }} - 代理 - {{ points(m.prices_agent?.[r] ?? m.prices?.[r]) }} +
+ + {{ r }}{{ points(m.prices?.[r]) }}{{ points(m.prices_agent?.[r] ?? m.prices?.[r]) }} - - /s - 普通 - {{ points(m.duration_prices.per_second) }} - 代理 - {{ points(m.duration_prices_agent?.per_second ?? m.duration_prices.per_second) }} + + /s{{ points(m.duration_prices.per_second) }}{{ points(m.duration_prices_agent?.per_second ?? m.duration_prices.per_second) }} - - {{ d }} - 普通 - +{{ points(m.duration_prices?.[d]) }} - 代理 - +{{ points(m.duration_prices_agent?.[d] ?? m.duration_prices?.[d]) }} + + {{ d }}+{{ points(m.duration_prices?.[d]) }}+{{ points(m.duration_prices_agent?.[d] ?? m.duration_prices?.[d]) }} —
-
- - {{ m.durations[0] }}–{{ m.durations[m.durations.length - 1] }} - {{ m.durations[0] }} - - {{ r }} - - {{ r }} - - 首尾帧 {{ Math.min(m.max_reference_images, 2) }} - - 参考图 {{ refImages(m) }} - 参考图 {{ refImages(m) }} - 参考图 - +
+ {{ m.durations[0] }}–{{ m.durations[m.durations.length - 1] }} + {{ m.durations[0] }} + {{ r }} + 首尾帧{{ Math.min(m.max_reference_images, 2) }} + 参考图{{ refImages(m) }} + 参考图{{ refImages(m) }} + 参考图 - - {{ r.replace(':', '×') }} + {{ r }} —
- {{ m.weight || 0 }} - + :class="m.generation_count > 0 ? 'text-white/85 font-medium' : 'text-white/25'"> {{ (m.generation_count || 0).toLocaleString('en-US') }}