index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <template>
  2. <div class="app-container">
  3. <!-- ===== 顶部 KPI 概览卡片 ===== -->
  4. <el-row :gutter="16" class="mb16">
  5. <el-col :span="6" v-for="card in overviewCards" :key="card.label">
  6. <el-card shadow="hover" class="overview-card">
  7. <div class="card-inner">
  8. <div class="card-icon" :style="{ background: card.bg }"><i :class="card.icon"></i></div>
  9. <div class="card-info">
  10. <span class="card-value">{{ card.value }}</span>
  11. <span class="card-label">{{ card.label }}</span>
  12. </div>
  13. </div>
  14. </el-card>
  15. </el-col>
  16. </el-row>
  17. <!-- ===== 搜索与操作栏 ===== -->
  18. <el-card shadow="never" class="mb16 filter-card">
  19. <el-form :model="queryParams" ref="queryForm" :inline="true" size="small">
  20. <el-form-item label="租户名称" prop="tenantName">
  21. <el-input v-model="queryParams.tenantName" placeholder="请输入租户名称" clearable @keyup.enter.native="handleQuery" />
  22. </el-form-item>
  23. <el-form-item label="归属代理" prop="proxyName">
  24. <el-input v-model="queryParams.proxyName" placeholder="请输入代理名称" clearable @keyup.enter.native="handleQuery" />
  25. </el-form-item>
  26. <el-form-item>
  27. <el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
  28. <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
  29. </el-form-item>
  30. </el-form>
  31. </el-card>
  32. <!-- ===== 操作按钮 ===== -->
  33. <el-row :gutter="10" class="mb8">
  34. <el-col :span="1.5">
  35. <el-button type="warning" plain icon="el-icon-download" size="mini" :loading="exportLoading" @click="handleExport">导出</el-button>
  36. </el-col>
  37. <el-col :span="1.5">
  38. <el-button type="primary" plain icon="el-icon-refresh" size="mini" @click="handleRefreshAll">全量统计刷新</el-button>
  39. </el-col>
  40. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  41. </el-row>
  42. <!-- ===== 租户用量汇总表 ===== -->
  43. <el-table border v-loading="loading" :data="list" size="small" style="width:100%" show-summary :summary-method="getSummary">
  44. <el-table-column label="租户名称" align="center" prop="tenantName" min-width="120" fixed="left" />
  45. <el-table-column label="归属代理" align="center" prop="proxyName" min-width="100" />
  46. <!-- AI 模型用量 -->
  47. <el-table-column label="AI Token" align="center" prop="aiTokenTotal" min-width="90" />
  48. <el-table-column label="AI外呼" align="center" prop="aiCallTotal" min-width="70" />
  49. <el-table-column label="AI对话" align="center" prop="aiChatTotal" min-width="70" />
  50. <!-- 通讯 -->
  51. <el-table-column label="外呼次数" align="center" prop="outboundCallCount" min-width="75" />
  52. <el-table-column label="语音分钟" align="center" prop="voiceCallMinutes" min-width="75" />
  53. <el-table-column label="短信量" align="center" prop="smsSentCount" min-width="65" />
  54. <el-table-column label="流量(GB)" align="center" prop="flowUsageGB" min-width="75" />
  55. <!-- 个微 -->
  56. <el-table-column label="个微用户" align="center" prop="wxUserCount" min-width="75" />
  57. <el-table-column label="CID加个微" align="center" prop="cidAddWxCount" min-width="80" />
  58. <el-table-column label="个微绑定" align="center" prop="wxAccountCount" min-width="75" />
  59. <el-table-column label="个微客户" align="center" prop="wxCustomerCount" min-width="75" />
  60. <!-- 企微 -->
  61. <el-table-column label="企微用户" align="center" prop="qwUserCount" min-width="75" />
  62. <el-table-column label="CID加企微" align="center" prop="cidAddQwCount" min-width="80" />
  63. <el-table-column label="企微绑定" align="center" prop="qwAccountCount" min-width="75" />
  64. <!-- 业务模块 -->
  65. <el-table-column label="SOP" align="center" prop="sopCount" min-width="55" />
  66. <el-table-column label="课程" align="center" prop="courseCount" min-width="55" />
  67. <el-table-column label="直播" align="center" prop="liveCount" min-width="55" />
  68. <el-table-column label="商品" align="center" prop="productCount" min-width="55" />
  69. <el-table-column label="订单" align="center" prop="orderCount" min-width="55" />
  70. <!-- 龙虾引擎 -->
  71. <el-table-column label="工作流" align="center" prop="workflowCount" min-width="65" />
  72. <el-table-column label="龙虾模板" align="center" prop="lobsterTemplateCount" min-width="75" />
  73. <el-table-column label="龙虾任务" align="center" prop="lobsterTaskCount" min-width="75" />
  74. <!-- 组织 -->
  75. <el-table-column label="部门" align="center" prop="deptCount" min-width="55" />
  76. <el-table-column label="员工" align="center" prop="employeeCount" min-width="55" />
  77. <el-table-column label="销售账户" align="center" prop="salesAccountCount" min-width="75" />
  78. <el-table-column label="AI Token" align="center" prop="aiTokenUsed" min-width="90" />
  79. <!-- 汇总 -->
  80. <el-table-column label="活跃模块" align="center" prop="activeModuleCount" min-width="75">
  81. <template slot-scope="scope">
  82. <el-tag type="success" size="mini">{{ scope.row.activeModuleCount || 0 }}</el-tag>
  83. </template>
  84. </el-table-column>
  85. <el-table-column label="消费总额" align="center" prop="totalConsumeAmount" width="110" fixed="right">
  86. <template slot-scope="scope">
  87. <span style="color:#1890ff;font-weight:bold">¥{{ scope.row.totalConsumeAmount || '0.00' }}</span>
  88. </template>
  89. </el-table-column>
  90. <el-table-column label="操作" align="center" width="80" fixed="right">
  91. <template slot-scope="scope">
  92. <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
  93. </template>
  94. </el-table-column>
  95. </el-table>
  96. <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
  97. <!-- ===== 租户模型用量详情弹窗 ===== -->
  98. <el-dialog title="租户模型用量详情" :visible.sync="detailVisible" width="720px">
  99. <el-descriptions :column="3" border size="small">
  100. <el-descriptions-item label="租户名称" :span="2">{{ detail.tenantName }}</el-descriptions-item>
  101. <el-descriptions-item label="归属代理">{{ detail.proxyName || '-' }}</el-descriptions-item>
  102. <!-- AI模型 -->
  103. <el-descriptions-item label="AI Token总量">
  104. <span style="color:#1890ff;font-weight:bold">{{ detail.aiTokenTotal || 0 }}</span>
  105. </el-descriptions-item>
  106. <el-descriptions-item label="AI外呼总量">
  107. <span style="color:#722ed1;font-weight:bold">{{ detail.aiCallTotal || 0 }}</span>
  108. </el-descriptions-item>
  109. <el-descriptions-item label="AI对话总量">
  110. <span style="color:#52c41a;font-weight:bold">{{ detail.aiChatTotal || 0 }}</span>
  111. </el-descriptions-item>
  112. <!-- 通讯 -->
  113. <el-descriptions-item label="外呼拨打次数">{{ detail.outboundCallCount || 0 }}</el-descriptions-item>
  114. <el-descriptions-item label="语音通话(分钟)">{{ detail.voiceCallMinutes || 0 }}</el-descriptions-item>
  115. <el-descriptions-item label="短信发送量">{{ detail.smsSentCount || 0 }}</el-descriptions-item>
  116. <el-descriptions-item label="流量(GB)">{{ detail.flowUsageGB || '0.00' }}</el-descriptions-item>
  117. <el-descriptions-item label="SOP数量">{{ detail.sopCount || 0 }}</el-descriptions-item>
  118. <!-- 个微 -->
  119. <el-descriptions-item label="个微用户数">{{ detail.wxUserCount || 0 }}</el-descriptions-item>
  120. <el-descriptions-item label="CID加个微">{{ detail.cidAddWxCount || 0 }}</el-descriptions-item>
  121. <el-descriptions-item label="绑定个微账号">{{ detail.wxAccountCount || 0 }}</el-descriptions-item>
  122. <el-descriptions-item label="个微客户数">{{ detail.wxCustomerCount || 0 }}</el-descriptions-item>
  123. <!-- 企微 -->
  124. <el-descriptions-item label="企微用户数">{{ detail.qwUserCount || 0 }}</el-descriptions-item>
  125. <el-descriptions-item label="CID加企微">{{ detail.cidAddQwCount || 0 }}</el-descriptions-item>
  126. <el-descriptions-item label="绑定企微号数">{{ detail.qwAccountCount || 0 }}</el-descriptions-item>
  127. <el-descriptions-item label="课程数量">{{ detail.courseCount || 0 }}</el-descriptions-item>
  128. <!-- 业务 -->
  129. <el-descriptions-item label="直播次数">{{ detail.liveCount || 0 }}</el-descriptions-item>
  130. <el-descriptions-item label="商品数量">{{ detail.productCount || 0 }}</el-descriptions-item>
  131. <el-descriptions-item label="订单数量">{{ detail.orderCount || 0 }}</el-descriptions-item>
  132. <!-- 龙虾 -->
  133. <el-descriptions-item label="工作流数量">{{ detail.workflowCount || 0 }}</el-descriptions-item>
  134. <el-descriptions-item label="龙虾引擎模板">{{ detail.lobsterTemplateCount || 0 }}</el-descriptions-item>
  135. <el-descriptions-item label="龙虾引擎任务">{{ detail.lobsterTaskCount || 0 }}</el-descriptions-item>
  136. <!-- 组织 -->
  137. <el-descriptions-item label="部门数量">{{ detail.deptCount || 0 }}</el-descriptions-item>
  138. <el-descriptions-item label="员工数量">{{ detail.employeeCount || 0 }}</el-descriptions-item>
  139. <el-descriptions-item label="销售账户数">{{ detail.salesAccountCount || 0 }}</el-descriptions-item>
  140. <el-descriptions-item label="AI Token用量">{{ detail.aiTokenUsed || 0 }}</el-descriptions-item>
  141. <el-descriptions-item label="活跃模块数">
  142. <el-tag type="success" size="mini">{{ detail.activeModuleCount || 0 }}</el-tag>
  143. </el-descriptions-item>
  144. <!-- 汇总 -->
  145. <el-descriptions-item label="消费总额" :span="3">
  146. <span style="color:#1890ff;font-weight:bold;font-size:16px">¥{{ detail.totalConsumeAmount || '0.00' }}</span>
  147. </el-descriptions-item>
  148. </el-descriptions>
  149. </el-dialog>
  150. </div>
  151. </template>
  152. <script>
  153. import { listModuleUsageSummary, getTenantDetail, refreshStatistics, exportModuleUsage } from '@/api/admin/moduleUsage'
  154. export default {
  155. name: 'AdminModelUsage',
  156. data() {
  157. return {
  158. loading: false,
  159. exportLoading: false,
  160. showSearch: true,
  161. list: [],
  162. total: 0,
  163. detailVisible: false,
  164. detail: {},
  165. queryParams: {
  166. pageNum: 1,
  167. pageSize: 10,
  168. tenantName: null,
  169. proxyName: null
  170. },
  171. // KPI 概览卡片
  172. overviewCards: [
  173. { label: '租户总数', value: 0, icon: 'el-icon-office-building', bg: '#e6f7ff' },
  174. { label: '活跃模块总数', value: 0, icon: 'el-icon-data-line', bg: '#f6ffed' },
  175. { label: '消费总额', value: '¥0.00', icon: 'el-icon-wallet', bg: '#fff7e6' },
  176. { label: 'AI调用总量', value: 0, icon: 'el-icon-cpu', bg: '#f9f0ff' }
  177. ]
  178. }
  179. },
  180. created() {
  181. this.getList()
  182. },
  183. methods: {
  184. getList() {
  185. this.loading = true
  186. listModuleUsageSummary(this.queryParams).then(res => {
  187. this.list = res.rows || []
  188. this.total = res.total || 0
  189. this.updateOverview(this.list)
  190. this.loading = false
  191. }).catch(() => { this.loading = false })
  192. },
  193. /** 根据列表数据更新KPI卡片 */
  194. updateOverview(list) {
  195. const totalModules = list.reduce((s, r) => s + (r.activeModuleCount || 0), 0)
  196. const totalConsume = list.reduce((s, r) => s + (parseFloat(r.totalConsumeAmount) || 0), 0)
  197. const totalAiCalls = list.reduce((s, r) => s + (r.aiCallTotal || 0) + (r.aiChatTotal || 0), 0)
  198. this.overviewCards[0].value = this.total
  199. this.overviewCards[1].value = totalModules
  200. this.overviewCards[2].value = '¥' + totalConsume.toFixed(2)
  201. this.overviewCards[3].value = totalAiCalls
  202. },
  203. /** 表格合计行 */
  204. getSummary({ columns, data }) {
  205. const sums = []
  206. columns.forEach((col, idx) => {
  207. if (idx === 0) { sums[idx] = '合计'; return }
  208. const prop = col.property
  209. if (!prop) { sums[idx] = ''; return }
  210. const numFields = ['aiTokenTotal','aiCallTotal','aiChatTotal','outboundCallCount','voiceCallMinutes','smsSentCount','flowUsageGB',
  211. 'wxUserCount','cidAddWxCount','wxAccountCount','wxCustomerCount','qwUserCount','cidAddQwCount','qwAccountCount',
  212. 'sopCount','courseCount','liveCount','productCount','orderCount',
  213. 'workflowCount','lobsterTemplateCount','lobsterTaskCount','deptCount','employeeCount','salesAccountCount','aiTokenUsed','activeModuleCount']
  214. if (numFields.includes(prop)) {
  215. const val = data.reduce((s, r) => s + (r[prop] || 0), 0)
  216. sums[idx] = val
  217. } else if (prop === 'totalConsumeAmount') {
  218. const val = data.reduce((s, r) => s + (parseFloat(r[prop]) || 0), 0)
  219. sums[idx] = '¥' + val.toFixed(2)
  220. } else {
  221. sums[idx] = ''
  222. }
  223. })
  224. return sums
  225. },
  226. handleQuery() {
  227. this.queryParams.pageNum = 1
  228. this.getList()
  229. },
  230. resetQuery() {
  231. this.resetForm('queryForm')
  232. this.handleQuery()
  233. },
  234. handleDetail(row) {
  235. getTenantDetail(row.tenantId).then(res => {
  236. this.detail = res.data || {}
  237. this.detailVisible = true
  238. })
  239. },
  240. handleRefreshAll() {
  241. this.$confirm('确定触发全量统计刷新吗?', '提示', { type: 'warning' }).then(() => {
  242. refreshStatistics().then(() => {
  243. this.$message.success('全量统计任务已触发')
  244. this.getList()
  245. })
  246. })
  247. },
  248. handleExport() {
  249. this.exportLoading = true
  250. exportModuleUsage(this.queryParams).then(response => {
  251. this.download(response.msg)
  252. this.exportLoading = false
  253. }).catch(() => { this.exportLoading = false })
  254. }
  255. }
  256. }
  257. </script>
  258. <style scoped>
  259. .overview-card { cursor: default; }
  260. .card-inner { display: flex; align-items: center; }
  261. .card-icon {
  262. width: 46px; height: 46px; border-radius: 50%;
  263. display: flex; align-items: center; justify-content: center;
  264. margin-right: 14px; font-size: 22px; color: #fff;
  265. }
  266. .card-value { font-size: 22px; font-weight: bold; color: #333; display: block; }
  267. .card-label { font-size: 12px; color: #999; }
  268. .filter-card { padding-bottom: 0; }
  269. .mb16 { margin-bottom: 16px; }
  270. .mb8 { margin-bottom: 8px; }
  271. </style>