|
@@ -0,0 +1,713 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="app-container billing-page">
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="使用说明:先选租户,再配置方案与收费项;英文编码为系统识别码,已配中文释义。"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="mb16"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-card shadow="never" class="mb16">
|
|
|
|
|
+ <div slot="header">
|
|
|
|
|
+ <span>租户选择</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form :inline="true" label-width="90px">
|
|
|
|
|
+ <el-form-item label="租户">
|
|
|
|
|
+ <el-select v-model="currentTenantId" placeholder="请选择租户" filterable style="width: 360px" @change="handleTenantChange">
|
|
|
|
|
+ <el-option v-for="item in tenantOptions" :key="item.id" :label="item.tenantName + '(' + item.tenantCode + ')'" :value="item.id" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tabs v-model="activeTab" type="border-card">
|
|
|
|
|
+ <el-tab-pane label="计费方案" name="plan">
|
|
|
|
|
+ <el-row :gutter="16">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
|
+ <div slot="header"><span>创建/发布方案</span></div>
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="方案编码建议固定(如 STANDARD),版本号递增;发布后租户绑定该版本生效。"
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="mb12"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-form ref="planForm" :model="planForm" :rules="planRules" label-width="100px">
|
|
|
|
|
+ <el-form-item label="方案编码" prop="planCode"><el-input v-model="planForm.planCode" placeholder="例如:STANDARD" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="方案名称" prop="planName"><el-input v-model="planForm.planName" placeholder="例如:标准方案" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="版本号" prop="version"><el-input-number v-model="planForm.version" :min="1" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="备注"><el-input v-model="planForm.remark" type="textarea" :rows="2" placeholder="可填写适用范围、价格说明" /></el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="submitCreatePlan">创建草稿</el-button>
|
|
|
|
|
+ <el-button type="success" plain @click="submitPublishPlan">发布</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="16">
|
|
|
|
|
+ <el-card shadow="never" class="mb16">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span>收费项配置</span>
|
|
|
|
|
+ <el-button size="mini" type="primary" plain @click="addPlanItemRow">新增收费项</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="说明:单价按“每计费单位多少元”;Token单位表示“多少Token折算1个计费单位”。"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="mb12"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table :data="planItemRows" border class="charge-item-table">
|
|
|
|
|
+ <el-table-column label="收费项" min-width="210" show-overflow-tooltip>
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="scope.row.itemCode"
|
|
|
|
|
+ filterable
|
|
|
|
|
+ placeholder="请选择收费项"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ @change="handleChargeItemChange(scope.row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option-group
|
|
|
|
|
+ v-for="group in chargeItemGroupOptions"
|
|
|
|
|
+ :key="group.label"
|
|
|
|
|
+ :label="group.label"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in group.options"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.label"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-option-group>
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="计费单位" width="110">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-input v-model="scope.row.unit" disabled />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="单价(元)" width="140">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="4" :step="0.01" :controls="false" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="Token换算单位" width="140" show-overflow-tooltip>
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-input :value="displayTokenUnit(scope.row.tokenUnit)" disabled />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="最小计费单位" width="130">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-input :value="displayMinCharge(scope.row.minChargeUnit)" disabled />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="启用" width="70">
|
|
|
|
|
+ <template slot-scope="scope"><el-switch v-model="scope.row.enabledFlag" /></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="70" align="center">
|
|
|
|
|
+ <template slot-scope="scope"><el-button type="text" @click="removePlanItemRow(scope.$index)">删除</el-button></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <div class="mt12">
|
|
|
|
|
+ <el-button type="primary" @click="submitSaveItems">保存收费项</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span>流量阶梯配置</span>
|
|
|
|
|
+ <el-button size="mini" type="primary" plain @click="addFlowTierRow">新增阶梯</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="说明:预存金额命中区间后,流量按对应单价计费;上限为空表示无上限。"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="mb12"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table :data="flowTierRows" border class="flow-tier-table">
|
|
|
|
|
+ <el-table-column label="预存下限(元)" width="165">
|
|
|
|
|
+ <template slot-scope="scope"><el-input-number v-model="scope.row.minPrepayAmount" :min="0" :precision="2" :controls="false" /></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="预存上限(元)" width="165">
|
|
|
|
|
+ <template slot-scope="scope"><el-input-number v-model="scope.row.maxPrepayAmount" :min="0" :precision="2" :controls="false" /></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="流量单价(元)" width="150">
|
|
|
|
|
+ <template slot-scope="scope"><el-input-number v-model="scope.row.unitPrice" :min="0" :precision="4" :step="0.01" :controls="false" /></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="排序号" width="100">
|
|
|
|
|
+ <template slot-scope="scope"><el-input-number v-model="scope.row.sortNo" :min="0" :controls="false" /></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="80" align="center">
|
|
|
|
|
+ <template slot-scope="scope"><el-button type="text" @click="removeFlowTierRow(scope.$index)">删除</el-button></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <div class="mt12">
|
|
|
|
|
+ <el-button type="primary" @click="submitSaveFlowTiers">保存流量阶梯</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tab-pane label="租户绑定与钱包" name="tenantWallet">
|
|
|
|
|
+ <el-row :gutter="16">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
|
+ <div slot="header"><span>租户计费绑定</span></div>
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="说明:先绑定方案,再决定“预付费/后付费”和“AI/非AI”类型。"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="mb12"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-form ref="bindFormRef" :model="bindForm" :rules="bindRules" label-width="110px">
|
|
|
|
|
+ <el-form-item label="租户ID"><el-input v-model="bindForm.tenantId" disabled /></el-form-item>
|
|
|
|
|
+ <el-form-item label="计费模式" prop="billingMode">
|
|
|
|
|
+ <el-select v-model="bindForm.billingMode">
|
|
|
|
|
+ <el-option label="预付费 PREPAID" value="PREPAID" />
|
|
|
|
|
+ <el-option label="后付费 POSTPAID" value="POSTPAID" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="租户类型" prop="tenantType">
|
|
|
|
|
+ <el-select v-model="bindForm.tenantType">
|
|
|
|
|
+ <el-option label="非AI NON_AI" value="NON_AI" />
|
|
|
|
|
+ <el-option label="AI" value="AI" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="方案编码" prop="planCode"><el-input v-model="bindForm.planCode" placeholder="例如:STANDARD" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="方案版本" prop="planVersion"><el-input-number v-model="bindForm.planVersion" :min="1" /></el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="submitBindPlan">绑定方案</el-button>
|
|
|
|
|
+ <el-button plain @click="submitChangeBillingMode">仅改计费模式</el-button>
|
|
|
|
|
+ <el-button plain @click="submitChangeTenantType">仅改租户类型</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-card shadow="never" class="mb16">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span>钱包信息</span>
|
|
|
|
|
+ <el-button size="mini" type="primary" plain @click="loadWallet">刷新</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-descriptions :column="1" border>
|
|
|
|
|
+ <el-descriptions-item label="租户ID">{{ walletInfo.tenantId || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="可用余额(元)">{{ walletInfo.balanceAmount || 0 }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="累计充值">{{ walletInfo.totalRecharge || 0 }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="累计消费">{{ walletInfo.totalCost || 0 }}</el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
|
+ <div slot="header"><span>钱包充值</span></div>
|
|
|
|
|
+ <el-form ref="rechargeFormRef" :model="rechargeForm" :rules="rechargeRules" label-width="100px">
|
|
|
|
|
+ <el-form-item label="租户ID"><el-input v-model="rechargeForm.tenantId" disabled /></el-form-item>
|
|
|
|
|
+ <el-form-item label="充值金额" prop="amount"><el-input-number v-model="rechargeForm.amount" :min="0.01" :precision="2" :step="100" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="业务单号"><el-input v-model="rechargeForm.bizNo" placeholder="例如:线下打款单号" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="备注"><el-input v-model="rechargeForm.remark" placeholder="例如:3月续费充值" /></el-form-item>
|
|
|
|
|
+ <el-form-item><el-button type="primary" @click="submitRecharge">提交充值</el-button></el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tab-pane label="事件上报与账单" name="billing">
|
|
|
|
|
+ <el-row :gutter="16">
|
|
|
|
|
+ <el-col :span="10">
|
|
|
|
|
+ <el-card shadow="never" class="mb16">
|
|
|
|
|
+ <div slot="header"><span>用量事件上报</span></div>
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="说明:此功能主要用于联调/演示,生产场景通常由业务系统自动上报。"
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="mb12"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-form ref="eventFormRef" :model="eventForm" :rules="eventRules" label-width="120px">
|
|
|
|
|
+ <el-form-item label="事件ID" prop="eventId"><el-input v-model="eventForm.eventId" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="租户ID"><el-input v-model="eventForm.tenantId" disabled /></el-form-item>
|
|
|
|
|
+ <el-form-item label="事件类型" prop="eventType">
|
|
|
|
|
+ <el-select v-model="eventForm.eventType">
|
|
|
|
|
+ <el-option label="流量 (FLOW)" value="FLOW" />
|
|
|
|
|
+ <el-option label="通话 (CALL)" value="CALL" />
|
|
|
|
|
+ <el-option label="SOP Token (TOKEN_SOP)" value="TOKEN_SOP" />
|
|
|
|
|
+ <el-option label="AI回复Token (TOKEN_AI_REPLY)" value="TOKEN_AI_REPLY" />
|
|
|
|
|
+ <el-option label="加微 (ADD_WECHAT)" value="ADD_WECHAT" />
|
|
|
|
|
+ <el-option label="开户 (OPEN_ACCOUNT)" value="OPEN_ACCOUNT" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="子类型"><el-input v-model="eventForm.subType" placeholder="例如:外呼 CALL_OUT、呼入 CALL_IN" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="业务ID"><el-input v-model="eventForm.bizId" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="用量值" prop="usageValue"><el-input-number v-model="eventForm.usageValue" :min="0" :precision="6" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="用量单位"><el-input v-model="eventForm.usageUnit" placeholder="例如:KB / SECOND / TOKEN / 次" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="发生时间"><el-date-picker v-model="eventForm.occurredAt" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="是否AI外呼">
|
|
|
|
|
+ <el-switch v-model="eventForm.isAiCallFlag" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="submitReportEvent">上报并计费</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
|
+ <div slot="header"><span>生成账单</span></div>
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="说明:账单会汇总“未入账明细”,建议先确认时间范围再生成。"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ class="mb12"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-form ref="statementFormRef" :model="statementForm" :rules="statementRules" label-width="110px">
|
|
|
|
|
+ <el-form-item label="租户ID"><el-input v-model="statementForm.tenantId" disabled /></el-form-item>
|
|
|
|
|
+ <el-form-item label="账期类型" prop="periodType">
|
|
|
|
|
+ <el-select v-model="statementForm.periodType">
|
|
|
|
|
+ <el-option label="按月 (MONTH)" value="MONTH" />
|
|
|
|
|
+ <el-option label="按周 (WEEK)" value="WEEK" />
|
|
|
|
|
+ <el-option label="自定义 (CUSTOM)" value="CUSTOM" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="开始时间" prop="periodStart">
|
|
|
|
|
+ <el-date-picker v-model="statementForm.periodStart" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="结束时间" prop="periodEnd">
|
|
|
|
|
+ <el-date-picker v-model="statementForm.periodEnd" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="submitGenerateStatement">生成账单</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="14">
|
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span>计费明细</span>
|
|
|
|
|
+ <el-button size="mini" type="primary" plain @click="loadBillingDetails">刷新</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table :data="detailList" border height="650">
|
|
|
|
|
+ <el-table-column label="事件ID" prop="eventId" min-width="140" />
|
|
|
|
|
+ <el-table-column label="类型" prop="eventType" width="120" />
|
|
|
|
|
+ <el-table-column label="子类型" prop="subType" width="120" />
|
|
|
|
|
+ <el-table-column label="用量" prop="usageValue" width="110" />
|
|
|
|
|
+ <el-table-column label="金额" prop="amount" width="110" />
|
|
|
|
|
+ <el-table-column label="发生时间" prop="occurredAt" min-width="160" />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+ </el-tabs>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import {
|
|
|
|
|
+ createFeePlan,
|
|
|
|
|
+ saveFeePlanItems,
|
|
|
|
|
+ saveFlowTiers,
|
|
|
|
|
+ publishFeePlan,
|
|
|
|
|
+ bindTenantPlan,
|
|
|
|
|
+ changeTenantBillingMode,
|
|
|
|
|
+ changeTenantType,
|
|
|
|
|
+ getTenantWallet,
|
|
|
|
|
+ rechargeTenantWallet,
|
|
|
|
|
+ reportUsageEvent,
|
|
|
|
|
+ listBillingDetails,
|
|
|
|
|
+ generateStatement
|
|
|
|
|
+} from '@/api/saas/billing'
|
|
|
|
|
+import { tenantList } from '@/api/tenant/tenant'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'SaasBilling',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ activeTab: 'plan',
|
|
|
|
|
+ tenantOptions: [],
|
|
|
|
|
+ currentTenantId: null,
|
|
|
|
|
+ walletInfo: {},
|
|
|
|
|
+ detailList: [],
|
|
|
|
|
+ chargeItemGroupOptions: [
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '流量类',
|
|
|
|
|
+ options: [
|
|
|
|
|
+ { value: 'FLOW_POSTPAID', label: '流量后付费' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '通话类',
|
|
|
|
|
+ options: [
|
|
|
|
|
+ { value: 'CALL_OUT', label: '外呼通话' },
|
|
|
|
|
+ { value: 'CALL_IN', label: '呼入通话' },
|
|
|
|
|
+ { value: 'AI_CALL', label: 'AI外呼附加费' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Token类',
|
|
|
|
|
+ options: [
|
|
|
|
|
+ { value: 'SOP_TOKEN', label: 'SOP Token' },
|
|
|
|
|
+ { value: 'AI_REPLY_TOKEN', label: 'AI回复Token' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '开户类',
|
|
|
|
|
+ options: [
|
|
|
|
|
+ { value: 'OPEN_ACCOUNT_NON_AI', label: '开户费-非AI' },
|
|
|
|
|
+ { value: 'OPEN_ACCOUNT_AI', label: '开户费-AI' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '其他',
|
|
|
|
|
+ options: [
|
|
|
|
|
+ { value: 'ADD_WECHAT', label: '加微数量' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ chargeItemMetaMap: {
|
|
|
|
|
+ FLOW_POSTPAID: { unit: 'GB', tokenUnit: null, minChargeUnit: 1, suggestedUnitPrice: 0.2 },
|
|
|
|
|
+ CALL_OUT: { unit: 'MIN', tokenUnit: null, minChargeUnit: 1, suggestedUnitPrice: 0.3 },
|
|
|
|
|
+ CALL_IN: { unit: 'MIN', tokenUnit: null, minChargeUnit: 1, suggestedUnitPrice: 0.2 },
|
|
|
|
|
+ AI_CALL: { unit: 'MIN', tokenUnit: null, minChargeUnit: 1, suggestedUnitPrice: 0.15 },
|
|
|
|
|
+ SOP_TOKEN: { unit: 'TOKEN', tokenUnit: 100000, minChargeUnit: 1, suggestedUnitPrice: 1 },
|
|
|
|
|
+ AI_REPLY_TOKEN: { unit: 'TOKEN', tokenUnit: 100000, minChargeUnit: 1, suggestedUnitPrice: 1 },
|
|
|
|
|
+ ADD_WECHAT: { unit: 'COUNT', tokenUnit: null, minChargeUnit: 1, suggestedUnitPrice: 0.5 },
|
|
|
|
|
+ OPEN_ACCOUNT_NON_AI: { unit: 'TIME', tokenUnit: null, minChargeUnit: 1, suggestedUnitPrice: 1000 },
|
|
|
|
|
+ OPEN_ACCOUNT_AI: { unit: 'TIME', tokenUnit: null, minChargeUnit: 1, suggestedUnitPrice: 3000 }
|
|
|
|
|
+ },
|
|
|
|
|
+ planForm: {
|
|
|
|
|
+ planCode: 'STANDARD',
|
|
|
|
|
+ planName: '标准方案',
|
|
|
|
|
+ version: 1,
|
|
|
|
|
+ remark: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ planRules: {
|
|
|
|
|
+ planCode: [{ required: true, message: '请输入方案编码', trigger: 'blur' }],
|
|
|
|
|
+ planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }],
|
|
|
|
|
+ version: [{ required: true, message: '请输入版本号', trigger: 'change' }]
|
|
|
|
|
+ },
|
|
|
|
|
+ planItemRows: [
|
|
|
|
|
+ { itemCode: 'FLOW_POSTPAID', unit: 'KB', unitPrice: 0.2, tokenUnit: 100000, minChargeUnit: 1, enabledFlag: true },
|
|
|
|
|
+ { itemCode: 'CALL_OUT', unit: 'MIN', unitPrice: 0.3, tokenUnit: 100000, minChargeUnit: 1, enabledFlag: true },
|
|
|
|
|
+ { itemCode: 'CALL_IN', unit: 'MIN', unitPrice: 0.2, tokenUnit: 100000, minChargeUnit: 1, enabledFlag: true }
|
|
|
|
|
+ ],
|
|
|
|
|
+ flowTierRows: [
|
|
|
|
|
+ { minPrepayAmount: 0, maxPrepayAmount: 100000, unitPrice: 0.1, sortNo: 1 },
|
|
|
|
|
+ { minPrepayAmount: 100000, maxPrepayAmount: 200000, unitPrice: 0.08, sortNo: 2 }
|
|
|
|
|
+ ],
|
|
|
|
|
+ bindForm: {
|
|
|
|
|
+ tenantId: null,
|
|
|
|
|
+ tenantType: 'NON_AI',
|
|
|
|
|
+ billingMode: 'PREPAID',
|
|
|
|
|
+ planCode: 'STANDARD',
|
|
|
|
|
+ planVersion: 1
|
|
|
|
|
+ },
|
|
|
|
|
+ originalBindForm: {
|
|
|
|
|
+ tenantType: 'NON_AI',
|
|
|
|
|
+ billingMode: 'PREPAID'
|
|
|
|
|
+ },
|
|
|
|
|
+ bindRules: {
|
|
|
|
|
+ billingMode: [{ required: true, message: '请选择计费模式', trigger: 'change' }],
|
|
|
|
|
+ tenantType: [{ required: true, message: '请选择租户类型', trigger: 'change' }],
|
|
|
|
|
+ planCode: [{ required: true, message: '请输入方案编码', trigger: 'blur' }],
|
|
|
|
|
+ planVersion: [{ required: true, message: '请输入方案版本', trigger: 'change' }]
|
|
|
|
|
+ },
|
|
|
|
|
+ rechargeForm: {
|
|
|
|
|
+ tenantId: null,
|
|
|
|
|
+ amount: 1000,
|
|
|
|
|
+ bizNo: '',
|
|
|
|
|
+ remark: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ rechargeRules: {
|
|
|
|
|
+ amount: [{ required: true, message: '请输入充值金额', trigger: 'change' }]
|
|
|
|
|
+ },
|
|
|
|
|
+ eventForm: {
|
|
|
|
|
+ eventId: '',
|
|
|
|
|
+ tenantId: null,
|
|
|
|
|
+ eventType: 'FLOW',
|
|
|
|
|
+ subType: '',
|
|
|
|
|
+ bizId: '',
|
|
|
|
|
+ usageValue: 1,
|
|
|
|
|
+ usageUnit: 'KB',
|
|
|
|
|
+ occurredAt: '',
|
|
|
|
|
+ isAiCallFlag: false
|
|
|
|
|
+ },
|
|
|
|
|
+ eventRules: {
|
|
|
|
|
+ eventId: [{ required: true, message: '请输入事件ID', trigger: 'blur' }],
|
|
|
|
|
+ eventType: [{ required: true, message: '请选择事件类型', trigger: 'change' }],
|
|
|
|
|
+ usageValue: [{ required: true, message: '请输入用量值', trigger: 'change' }]
|
|
|
|
|
+ },
|
|
|
|
|
+ statementForm: {
|
|
|
|
|
+ tenantId: null,
|
|
|
|
|
+ periodType: 'MONTH',
|
|
|
|
|
+ periodStart: '',
|
|
|
|
|
+ periodEnd: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ statementRules: {
|
|
|
|
|
+ periodType: [{ required: true, message: '请选择账期类型', trigger: 'change' }],
|
|
|
|
|
+ periodStart: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
|
|
|
|
+ periodEnd: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+ this.loadTenantOptions()
|
|
|
|
|
+ this.eventForm.eventId = this.buildEventId()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ buildEventId() {
|
|
|
|
|
+ return 'EVT_' + new Date().getTime()
|
|
|
|
|
+ },
|
|
|
|
|
+ loadTenantOptions() {
|
|
|
|
|
+ tenantList({}).then(res => {
|
|
|
|
|
+ this.tenantOptions = res.rows || res.data || []
|
|
|
|
|
+ if (this.tenantOptions.length > 0) {
|
|
|
|
|
+ this.currentTenantId = this.tenantOptions[0].id
|
|
|
|
|
+ this.syncTenantIdToForms()
|
|
|
|
|
+ this.originalBindForm = {
|
|
|
|
|
+ tenantType: this.bindForm.tenantType,
|
|
|
|
|
+ billingMode: this.bindForm.billingMode
|
|
|
|
|
+ }
|
|
|
|
|
+ this.loadWallet()
|
|
|
|
|
+ this.loadBillingDetails()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ syncTenantIdToForms() {
|
|
|
|
|
+ this.bindForm.tenantId = this.currentTenantId
|
|
|
|
|
+ this.rechargeForm.tenantId = this.currentTenantId
|
|
|
|
|
+ this.eventForm.tenantId = this.currentTenantId
|
|
|
|
|
+ this.statementForm.tenantId = this.currentTenantId
|
|
|
|
|
+ },
|
|
|
|
|
+ handleTenantChange() {
|
|
|
|
|
+ this.syncTenantIdToForms()
|
|
|
|
|
+ this.originalBindForm = {
|
|
|
|
|
+ tenantType: this.bindForm.tenantType,
|
|
|
|
|
+ billingMode: this.bindForm.billingMode
|
|
|
|
|
+ }
|
|
|
|
|
+ this.loadWallet()
|
|
|
|
|
+ this.loadBillingDetails()
|
|
|
|
|
+ },
|
|
|
|
|
+ addPlanItemRow() {
|
|
|
|
|
+ this.planItemRows.push({ itemCode: '', unit: '', unitPrice: 0, tokenUnit: 100000, minChargeUnit: 1, enabledFlag: true })
|
|
|
|
|
+ },
|
|
|
|
|
+ handleChargeItemChange(row) {
|
|
|
|
|
+ const meta = this.chargeItemMetaMap[row.itemCode]
|
|
|
|
|
+ if (!meta) return
|
|
|
|
|
+ row.unit = meta.unit
|
|
|
|
|
+ row.tokenUnit = meta.tokenUnit
|
|
|
|
|
+ row.minChargeUnit = meta.minChargeUnit
|
|
|
|
|
+ row.unitPrice = meta.suggestedUnitPrice
|
|
|
|
|
+ },
|
|
|
|
|
+ displayTokenUnit(tokenUnit) {
|
|
|
|
|
+ return tokenUnit == null ? '-' : tokenUnit
|
|
|
|
|
+ },
|
|
|
|
|
+ displayMinCharge(minChargeUnit) {
|
|
|
|
|
+ return minChargeUnit == null ? '-' : minChargeUnit
|
|
|
|
|
+ },
|
|
|
|
|
+ removePlanItemRow(index) {
|
|
|
|
|
+ this.planItemRows.splice(index, 1)
|
|
|
|
|
+ },
|
|
|
|
|
+ addFlowTierRow() {
|
|
|
|
|
+ this.flowTierRows.push({ minPrepayAmount: 0, maxPrepayAmount: null, unitPrice: 0, sortNo: this.flowTierRows.length + 1 })
|
|
|
|
|
+ },
|
|
|
|
|
+ removeFlowTierRow(index) {
|
|
|
|
|
+ this.flowTierRows.splice(index, 1)
|
|
|
|
|
+ },
|
|
|
|
|
+ submitCreatePlan() {
|
|
|
|
|
+ this.$refs.planForm.validate(valid => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+ createFeePlan(this.planForm).then(() => {
|
|
|
|
|
+ this.msgSuccess('方案创建成功')
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitSaveItems() {
|
|
|
|
|
+ if (!this.planForm.planCode || !this.planForm.version) {
|
|
|
|
|
+ this.msgError('请先填写方案编码和版本')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ planCode: this.planForm.planCode,
|
|
|
|
|
+ version: this.planForm.version,
|
|
|
|
|
+ items: this.planItemRows.map(item => ({
|
|
|
|
|
+ itemCode: item.itemCode,
|
|
|
|
|
+ unit: item.unit,
|
|
|
|
|
+ unitPrice: item.unitPrice,
|
|
|
|
|
+ tokenUnit: item.tokenUnit,
|
|
|
|
|
+ minChargeUnit: item.minChargeUnit,
|
|
|
|
|
+ enabled: item.enabledFlag ? 1 : 0
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ saveFeePlanItems(payload).then(() => {
|
|
|
|
|
+ this.msgSuccess('收费项保存成功')
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitSaveFlowTiers() {
|
|
|
|
|
+ if (!this.planForm.planCode || !this.planForm.version) {
|
|
|
|
|
+ this.msgError('请先填写方案编码和版本')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ planCode: this.planForm.planCode,
|
|
|
|
|
+ version: this.planForm.version,
|
|
|
|
|
+ tiers: this.flowTierRows
|
|
|
|
|
+ }
|
|
|
|
|
+ saveFlowTiers(payload).then(() => {
|
|
|
|
|
+ this.msgSuccess('流量阶梯保存成功')
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitPublishPlan() {
|
|
|
|
|
+ publishFeePlan(this.planForm.planCode, this.planForm.version).then(() => {
|
|
|
|
|
+ this.msgSuccess('方案发布成功')
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitBindPlan() {
|
|
|
|
|
+ if (!this.currentTenantId) {
|
|
|
|
|
+ this.msgError('请先选择租户')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$refs.bindFormRef.validate(valid => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+ bindTenantPlan(this.bindForm).then(() => {
|
|
|
|
|
+ this.msgSuccess('绑定方案成功')
|
|
|
|
|
+ this.originalBindForm.tenantType = this.bindForm.tenantType
|
|
|
|
|
+ this.originalBindForm.billingMode = this.bindForm.billingMode
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitChangeBillingMode() {
|
|
|
|
|
+ if (this.bindForm.billingMode === this.originalBindForm.billingMode) {
|
|
|
|
|
+ this.$message.warning('计费模式未变更,无需提交')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ changeTenantBillingMode({
|
|
|
|
|
+ tenantId: this.bindForm.tenantId,
|
|
|
|
|
+ billingMode: this.bindForm.billingMode
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ this.msgSuccess('计费模式修改成功')
|
|
|
|
|
+ this.originalBindForm.billingMode = this.bindForm.billingMode
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitChangeTenantType() {
|
|
|
|
|
+ if (this.bindForm.tenantType === this.originalBindForm.tenantType) {
|
|
|
|
|
+ this.$message.warning('租户类型未变更,无需提交')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ changeTenantType({
|
|
|
|
|
+ tenantId: this.bindForm.tenantId,
|
|
|
|
|
+ tenantType: this.bindForm.tenantType
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ this.msgSuccess('租户类型修改成功')
|
|
|
|
|
+ this.originalBindForm.tenantType = this.bindForm.tenantType
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ loadWallet() {
|
|
|
|
|
+ if (!this.currentTenantId) return
|
|
|
|
|
+ getTenantWallet(this.currentTenantId).then(res => {
|
|
|
|
|
+ this.walletInfo = res.data || {}
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitRecharge() {
|
|
|
|
|
+ if (!this.currentTenantId) {
|
|
|
|
|
+ this.msgError('请先选择租户')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$refs.rechargeFormRef.validate(valid => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+ rechargeTenantWallet(this.rechargeForm).then(() => {
|
|
|
|
|
+ this.msgSuccess('充值成功')
|
|
|
|
|
+ this.loadWallet()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitReportEvent() {
|
|
|
|
|
+ if (!this.currentTenantId) {
|
|
|
|
|
+ this.msgError('请先选择租户')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$refs.eventFormRef.validate(valid => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ eventId: this.eventForm.eventId,
|
|
|
|
|
+ tenantId: this.eventForm.tenantId,
|
|
|
|
|
+ eventType: this.eventForm.eventType,
|
|
|
|
|
+ subType: this.eventForm.subType,
|
|
|
|
|
+ bizId: this.eventForm.bizId,
|
|
|
|
|
+ usageValue: this.eventForm.usageValue,
|
|
|
|
|
+ usageUnit: this.eventForm.usageUnit,
|
|
|
|
|
+ occurredAt: this.eventForm.occurredAt,
|
|
|
|
|
+ extJson: {
|
|
|
|
|
+ isAiCall: this.eventForm.isAiCallFlag
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ reportUsageEvent(payload).then(res => {
|
|
|
|
|
+ const result = res.data || {}
|
|
|
|
|
+ this.msgSuccess('上报成功,计费金额:' + (result.amount || 0))
|
|
|
|
|
+ this.eventForm.eventId = this.buildEventId()
|
|
|
|
|
+ this.loadWallet()
|
|
|
|
|
+ this.loadBillingDetails()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ loadBillingDetails() {
|
|
|
|
|
+ if (!this.currentTenantId) return
|
|
|
|
|
+ listBillingDetails({ tenantId: this.currentTenantId }).then(res => {
|
|
|
|
|
+ this.detailList = res.rows || []
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ submitGenerateStatement() {
|
|
|
|
|
+ if (!this.currentTenantId) {
|
|
|
|
|
+ this.msgError('请先选择租户')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$refs.statementFormRef.validate(valid => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+ generateStatement(this.statementForm).then(res => {
|
|
|
|
|
+ const data = res.data || {}
|
|
|
|
|
+ this.msgSuccess('账单生成成功:' + (data.statementNo || '-'))
|
|
|
|
|
+ this.loadBillingDetails()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.billing-page .mb16 {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+.billing-page .mb12 {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.billing-page .mt12 {
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.billing-page .card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+}
|
|
|
|
|
+.billing-page .charge-item-table ::v-deep .el-input-number {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.billing-page .charge-item-table ::v-deep .el-input-number .el-input__inner {
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ padding-left: 10px;
|
|
|
|
|
+ padding-right: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.billing-page .flow-tier-table ::v-deep .el-input-number {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.billing-page .flow-tier-table ::v-deep .el-input-number .el-input__inner {
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ padding-left: 10px;
|
|
|
|
|
+ padding-right: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+
|