Files
X-Financial/web/src/components/commercial/CommercialAdminConsole.vue
caoxiaozhu 787bc3a481 feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
2026-07-17 14:14:08 +08:00

106 lines
4.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<section class="commercial-section commercial-admin-section" aria-labelledby="commercial-admin-title">
<header class="commercial-section-head">
<div>
<span>Platform administration</span>
<h3 id="commercial-admin-title">商业配置与事实台账</h3>
<p>配置采用版本化更新用量和成本采用不可变事件所有操作提交前都必须二次确认</p>
</div>
<span :class="['commercial-admin-access', canManage ? 'allowed' : 'denied']">
<i :class="canManage ? 'mdi mdi-shield-check-outline' : 'mdi mdi-shield-lock-outline'" aria-hidden="true"></i>
{{ canManage ? '平台管理员' : '只读' }}
</span>
</header>
<div v-if="!canManage" class="commercial-state-card permission" role="status">
<i class="mdi mdi-lock-outline" aria-hidden="true"></i>
<div><strong>当前页面为只读模式</strong><span>只有平台管理员且已选择目标租户时才可修改套餐订阅权益及事实台账</span></div>
</div>
<template v-else>
<div v-if="message" class="commercial-operation-feedback success" role="status" aria-live="polite">
<i class="mdi mdi-check-circle-outline" aria-hidden="true"></i>{{ message }}
</div>
<div v-if="error" class="commercial-operation-feedback danger" role="alert">
<i class="mdi mdi-alert-circle-outline" aria-hidden="true"></i>{{ error.message || '操作失败,请刷新后重试。' }}
</div>
<div class="commercial-admin-groups">
<article>
<header><i class="mdi mdi-source-branch" aria-hidden="true"></i><div><strong>版本化配置</strong><span>套餐订阅与权益</span></div></header>
<div>
<button v-for="action in configurationActions" :key="action.kind" type="button" class="commercial-action-button" :disabled="busy" @click="openAction(action.kind)">
<span>{{ action.label }}</span><i class="mdi mdi-chevron-right" aria-hidden="true"></i>
</button>
</div>
</article>
<article>
<header><i class="mdi mdi-database-lock-outline" aria-hidden="true"></i><div><strong>不可变事实</strong><span>用量与平台内部成本</span></div></header>
<div>
<button v-for="action in meteringActions" :key="action.kind" type="button" class="commercial-action-button" :disabled="busy" @click="openAction(action.kind)">
<span>{{ action.label }}</span><i class="mdi mdi-chevron-right" aria-hidden="true"></i>
</button>
</div>
</article>
</div>
</template>
<CommercialMutationDialog
:open="dialogOpen"
:kind="selectedAction"
:account="account"
:preset="actionPreset"
:busy="busy"
@close="dialogOpen = false"
@submit="handleSubmit"
/>
</section>
</template>
<script setup>
import { ref } from 'vue'
import CommercialMutationDialog from './CommercialMutationDialog.vue'
import { COMMERCIAL_ACTIONS } from './commercialWorkspaceModel.js'
const props = defineProps({
canManage: { type: Boolean, default: false },
account: { type: Object, default: null },
busy: { type: Boolean, default: false },
error: { type: Object, default: null },
message: { type: String, default: '' },
execute: { type: Function, required: true }
})
const configurationActions = [
'createPlan',
'activatePlan',
'createSubscription',
'activateSubscription',
'transitionSubscription',
'upsertEntitlement',
'activateEntitlement'
].map((kind) => ({ kind, ...COMMERCIAL_ACTIONS[kind] }))
const meteringActions = ['recordUsage', 'recordCost']
.map((kind) => ({ kind, ...COMMERCIAL_ACTIONS[kind] }))
const dialogOpen = ref(false)
const selectedAction = ref('')
const actionPreset = ref({})
function openAction(kind, preset = {}) {
selectedAction.value = kind
actionPreset.value = { ...preset }
dialogOpen.value = true
}
async function handleSubmit(command, reportError) {
try {
await props.execute(command.kind, command.form)
dialogOpen.value = false
} catch (error) {
reportError(error)
}
}
defineExpose({ openAction })
</script>