106 lines
4.1 KiB
Vue
106 lines
4.1 KiB
Vue
|
|
<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>
|