fix: merge 30 general improvements from release branch
Bug fixes: - Detached context for GetAccountConcurrencyBatch (prevent all-zero on request cancel) - Filter soft-deleted users in GetByGroupID - Stripe CSP policy (allow Stripe.js in script-src and frame-src) - WebSearch API key validation on save - RECHARGING status in payment result success check - Windows test fixes (logger Sync deadlock, config path escaping) Feature enhancements: - Webhook multi-instance dispatch (extractOutTradeNo + GetWebhookProvider) - EasyPay mobile H5 payment (device param + PayURL2) - SSE error propagation in WebSearch emulation - AccountStatsCost DTO field for admin usage logs - Plans sort by sort_order instead of created_at - UsageMapHook for streaming response usage data - apicompat Instructions field passthrough - EffectiveLoadFactor for ops concurrency/metrics - Usage billing RETURNING balance for notify system - BulkUpdate mixed channel warning with details - println to slog migration in auth cache - Wire ProviderSet cleanup - CI cache-dependency-path optimization Frontend: - Refund eligibility check per provider (canRequestRefund) - Plan sort_order editing - Dead code cleanup (simulate_claude_max, client_affinity) - GroupsView platform switch guard - channels features_config API type - UsageView account_stats_cost export
This commit is contained in:
@@ -3253,6 +3253,7 @@ const editForm = reactive({
|
||||
fallback_group_id_on_invalid_request: null as number | null,
|
||||
// OpenAI Messages 调度配置(仅 openai 平台使用)
|
||||
allow_messages_dispatch: false,
|
||||
default_mapped_model: '',
|
||||
opus_mapped_model: editMessagesDispatchDefaults.opus_mapped_model,
|
||||
sonnet_mapped_model: editMessagesDispatchDefaults.sonnet_mapped_model,
|
||||
haiku_mapped_model: editMessagesDispatchDefaults.haiku_mapped_model,
|
||||
@@ -3732,6 +3733,19 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => editForm.platform,
|
||||
(newVal) => {
|
||||
if (!['anthropic', 'antigravity'].includes(newVal)) {
|
||||
editForm.fallback_group_id_on_invalid_request = null
|
||||
}
|
||||
if (newVal !== 'openai') {
|
||||
editForm.allow_messages_dispatch = false
|
||||
editForm.default_mapped_model = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 点击外部关闭账号搜索下拉框
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
@@ -495,7 +495,7 @@ const exportToExcel = async () => {
|
||||
log.cache_read_cost?.toFixed(6) || '0.000000', log.cache_creation_cost?.toFixed(6) || '0.000000',
|
||||
log.rate_multiplier?.toPrecision(4) || '1.00', (log.account_rate_multiplier ?? 1).toPrecision(4),
|
||||
log.total_cost?.toFixed(6) || '0.000000', log.actual_cost?.toFixed(6) || '0.000000',
|
||||
(log.total_cost * (log.account_rate_multiplier ?? 1)).toFixed(6), log.first_token_ms ?? '', log.duration_ms,
|
||||
((log.account_stats_cost ?? log.total_cost) * (log.account_rate_multiplier ?? 1)).toFixed(6), log.first_token_ms ?? '', log.duration_ms,
|
||||
log.request_id || '', log.user_agent || '', log.ip_address || ''
|
||||
])
|
||||
if (rows.length) {
|
||||
|
||||
@@ -117,6 +117,7 @@ function getPlanNameClass(groupId: number): string {
|
||||
return group ? platformTextClass(group.platform) : 'text-gray-900 dark:text-white'
|
||||
}
|
||||
|
||||
|
||||
// ==================== Plans ====================
|
||||
|
||||
const plansLoading = ref(false)
|
||||
@@ -133,6 +134,7 @@ const planColumns = computed((): Column[] => [
|
||||
{ key: 'price', label: t('payment.admin.price') },
|
||||
{ key: 'validity_days', label: t('payment.admin.validityDays') },
|
||||
{ key: 'for_sale', label: t('payment.admin.forSale') },
|
||||
{ key: 'sort_order', label: t('payment.admin.sortOrder') },
|
||||
{ key: 'actions', label: t('common.actions') },
|
||||
])
|
||||
|
||||
@@ -157,6 +159,7 @@ function openPlanEdit(plan: SubscriptionPlan | null) {
|
||||
showPlanDialog.value = true
|
||||
}
|
||||
|
||||
|
||||
/** Quick toggle for_sale from the list */
|
||||
async function toggleForSale(plan: SubscriptionPlan) {
|
||||
try {
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
<div><label class="input-label">{{ t('payment.admin.validityDays') }} <span class="text-red-500">*</span></label><input v-model.number="planForm.validity_days" type="number" min="1" class="input" required /></div>
|
||||
<div><label class="input-label">{{ t('payment.admin.validityUnit') }} <span class="text-red-500">*</span></label><Select v-model="planForm.validity_unit" :options="validityUnitOptions" /></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="input-label">{{ t('payment.admin.sortOrder') }}</label><input v-model.number="planForm.sort_order" type="number" min="0" class="input" /></div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('payment.admin.features') }}</label>
|
||||
<textarea v-model="planFeaturesText" rows="3" class="input" :placeholder="t('payment.admin.featuresPlaceholder')"></textarea>
|
||||
@@ -102,7 +105,7 @@ const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const saving = ref(false)
|
||||
const planForm = reactive({ name: '', group_id: null as number | null, description: '', price: 0, original_price: 0, validity_days: 30, validity_unit: 'days', for_sale: true })
|
||||
const planForm = reactive({ name: '', group_id: null as number | null, description: '', price: 0, original_price: 0, validity_days: 30, validity_unit: 'days', sort_order: 0, for_sale: true })
|
||||
const planFeaturesText = ref('')
|
||||
|
||||
const validityUnitOptions = computed(() => [
|
||||
@@ -130,10 +133,10 @@ const selectedGroupInfo = computed(() => {
|
||||
watch(() => props.show, (visible) => {
|
||||
if (!visible) return
|
||||
if (props.plan) {
|
||||
Object.assign(planForm, { name: props.plan.name, group_id: props.plan.group_id, description: props.plan.description, price: props.plan.price, original_price: props.plan.original_price || 0, validity_days: props.plan.validity_days, validity_unit: props.plan.validity_unit || 'days', for_sale: props.plan.for_sale })
|
||||
Object.assign(planForm, { name: props.plan.name, group_id: props.plan.group_id, description: props.plan.description, price: props.plan.price, original_price: props.plan.original_price || 0, validity_days: props.plan.validity_days, validity_unit: props.plan.validity_unit || 'days', sort_order: props.plan.sort_order || 0, for_sale: props.plan.for_sale })
|
||||
planFeaturesText.value = (props.plan.features || []).join('\n')
|
||||
} else {
|
||||
Object.assign(planForm, { name: '', group_id: null, description: '', price: 0, original_price: 0, validity_days: 30, validity_unit: 'days', for_sale: true })
|
||||
Object.assign(planForm, { name: '', group_id: null, description: '', price: 0, original_price: 0, validity_days: 30, validity_unit: 'days', sort_order: 0, for_sale: true })
|
||||
planFeaturesText.value = ''
|
||||
}
|
||||
})
|
||||
@@ -149,6 +152,7 @@ function buildPlanPayload() {
|
||||
original_price: planForm.original_price || 0,
|
||||
validity_days: planForm.validity_days,
|
||||
validity_unit: planForm.validity_unit,
|
||||
sort_order: planForm.sort_order,
|
||||
for_sale: planForm.for_sale,
|
||||
features,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user