feat: 按策划需求改进管理后台

This commit is contained in:
yys 2026-04-23 11:11:08 +08:00
parent 63f251de66
commit ba675ce0e0
31 changed files with 537 additions and 179 deletions

View File

@ -74,3 +74,52 @@ export function intStringToNumber(digits) {
if (isNaN(n)) return undefined
return n
}
/** 有符号积分:供可输入负数的整数字段,范围 [-INT_MAX, INT_MAX] */
export function sanitizeSignedIntDigits(raw) {
let t = String(raw == null ? '' : raw).replace(/,/g, '')
t = t.trim()
if (t === '' || t === '+') {
return ''
}
if (t === '-') {
return '-'
}
const neg = t.startsWith('-')
let rest = neg ? t.slice(1) : t
rest = rest.replace(/\D/g, '')
if (rest === '') {
return neg ? '-' : ''
}
let n = parseInt(rest, 10)
if (isNaN(n)) {
return neg ? '-' : ''
}
n = Math.min(INT_MAX, Math.max(0, n))
return (neg ? '-' : '') + String(n)
}
export function formatSignedIntDisplay(s) {
if (s == null || s === '') {
return ''
}
if (s === '-') {
return '-'
}
const n = parseInt(String(s).replace(/,/g, ''), 10)
if (isNaN(n)) {
return ''
}
return n.toLocaleString('en-US')
}
export function signedIntStringToNumber(s) {
if (s == null || s === '' || s === '-') {
return undefined
}
const n = parseInt(String(s).replace(/,/g, ''), 10)
if (isNaN(n)) {
return undefined
}
return n
}

View File

@ -46,11 +46,6 @@
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
plain
@ -59,23 +54,31 @@
@click="handleExport"
v-hasPermi="['ai:balanceChangeRecord:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="recordList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键ID" align="center" prop="id" />
<el-table-column label="用户ID" align="center" prop="uuid" />
<el-table-column label="操作类型" align="center" prop="type" :formatter="formatType" />
<el-table v-loading="loading" :data="recordList">
<el-table-column label="主键ID" align="center" prop="id" width="80"/>
<el-table-column label="用户ID" align="center" prop="uuid" width="100" show-overflow-tooltip />
<el-table-column label="用户昵称" align="center" prop="nickname" width="150" show-overflow-tooltip />
<el-table-column label="关联订单号" align="center" prop="orderNo" width="180" show-overflow-tooltip />
<el-table-column label="操作类型" align="center" width="100" prop="type" :formatter="formatType" />
<el-table-column
label="变更积分"
align="center"
prop="changeAmount"
:formatter="formatChangeAmount"
width="120"
/>
<el-table-column label="变更后积分" align="center" prop="resultAmount" />
<el-table-column label="操作时间" align="center" prop="createTime" />
<el-table-column
label="变更后积分"
align="center"
prop="resultAmount"
:formatter="formatResultAmount"
width="120"
/>
<el-table-column label="操作时间" align="center" prop="createTime" width="150" />
<el-table-column label="备注" align="center" prop="remark" />
</el-table>
@ -146,17 +149,17 @@ export default {
dateRange: [],
//
typeMap: {
0: "充值",
1: "返佣",
2: "充值赠送",
3: "体验金赠送",
4: "体验金回收",
5: "一键换衣",
6: "图生图2",
7: "一键换脸",
8: "快捷生图",
// 0: "",
// 1: "",
// 2: "",
// 3: "",
// 4: "",
// 5: "",
// 6: "2",
// 7: "",
// 8: "",
9: "快捷生视频",
10: "退款",
// 10: "退",
11: "系统操作",
12: "团队下发",
13: "团队收回"
@ -184,6 +187,7 @@ export default {
pageNum: 1,
pageSize: 10,
type: null,
uuid: null,
userId: null
},
//
@ -200,14 +204,39 @@ export default {
this.getList();
},
methods: {
formatChangeAmount(row, column) {
const value = row.changeAmount;
// 0
if (typeof value === "number" && value > 0) {
return `+${value}`;
/** 西式千分位en-US积分为整数 */
formatPointsWestern(value) {
if (value === null || value === undefined || value === "") {
return "";
}
// 0
return value;
const n = Number(value);
if (Number.isNaN(n)) {
return String(value);
}
return n.toLocaleString("en-US", { maximumFractionDigits: 0 });
},
formatChangeAmount(row) {
const value = row.changeAmount;
if (value === null || value === undefined || value === "") {
return "";
}
const n = Number(value);
if (Number.isNaN(n)) {
return String(value);
}
const formatted = Math.abs(n).toLocaleString("en-US", {
maximumFractionDigits: 0
});
if (n > 0) {
return `+${formatted}`;
}
if (n < 0) {
return `-${formatted}`;
}
return "0";
},
formatResultAmount(row) {
return this.formatPointsWestern(row.resultAmount);
},
handleTypeChange(val) {
this.queryParams.type = val;
@ -320,9 +349,7 @@ export default {
handleExport() {
this.download(
"ai/balance-change-record/export",
{
...this.queryParams
},
this.addDateRange({ ...this.queryParams }, this.dateRange),
`record_${new Date().getTime()}.xlsx`
);
}

View File

@ -1,10 +1,10 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="部门ID" prop="deptId">
<el-form-item label="团队ID" prop="deptId">
<el-input
v-model="queryParams.deptId"
placeholder="请输入部门ID"
placeholder="请输入团队ID"
clearable
@keyup.enter.native="handleQuery"
/>
@ -64,7 +64,7 @@
<el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" />
<el-table-column label="部门ID" align="center" prop="deptId" />
<el-table-column label="团队ID" align="center" prop="deptId" />
<el-table-column label="视频模型列表JSON(label+value)" align="center" prop="modelParm" />
<el-table-column label="Byte project加密" align="center" prop="project" />
<el-table-column label="Byte API Key加密" align="center" prop="byteApiKey" />
@ -99,8 +99,8 @@
<!-- 添加或修改团队部门对应火山引擎配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="部门ID" prop="deptId">
<el-input v-model="form.deptId" placeholder="请输入部门ID" />
<el-form-item label="团队ID" prop="deptId">
<el-input v-model="form.deptId" placeholder="请输入团队ID" />
</el-form-item>
<el-form-item label="视频模型列表JSON(label+value)" prop="modelParm">
<el-input v-model="form.modelParm" type="textarea" placeholder="请输入内容" />
@ -159,7 +159,7 @@ export default {
//
rules: {
deptId: [
{ required: true, message: "部门ID不能为空", trigger: "blur" }
{ required: true, message: "团队ID不能为空", trigger: "blur" }
],
}
}
@ -217,7 +217,7 @@ export default {
handleAdd() {
this.reset()
this.open = true
this.title = "添加团队(部门)对应火山引擎配置"
this.title = "添加团队(团队)对应火山引擎配置"
},
/** 修改按钮操作 */
handleUpdate(row) {
@ -226,7 +226,7 @@ export default {
getConfig(id).then(response => {
this.form = response.data
this.open = true
this.title = "修改团队(部门)对应火山引擎配置"
this.title = "修改团队(团队)对应火山引擎配置"
})
},
/** 提交按钮 */
@ -252,7 +252,7 @@ export default {
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids
this.$modal.confirm('是否确认删除团队(部门)对应火山引擎配置编号为"' + ids + '"的数据项?').then(function() {
this.$modal.confirm('是否确认删除团队(团队)对应火山引擎配置编号为"' + ids + '"的数据项?').then(function() {
return delConfig(ids)
}).then(() => {
this.getList()

View File

@ -1,16 +1,16 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<el-form-item label="部门名称" prop="deptName">
<el-form-item label="团队名称" prop="deptName">
<el-input
v-model="queryParams.deptName"
placeholder="请输入部门名称"
placeholder="请输入团队名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
@ -56,7 +56,7 @@
:default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
<el-table-column prop="deptName" label="团队名称" width="260"></el-table-column>
<el-table-column prop="orderNum" label="排序" width="200"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
@ -100,15 +100,15 @@
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col :span="24" v-if="form.parentId !== 0">
<el-form-item label="上级部门" prop="parentId">
<treeselect v-model="form.parentId" :options="deptOptions" :normalizer="normalizer" placeholder="选择上级部门" />
<el-form-item label="上级团队" prop="parentId">
<treeselect v-model="form.parentId" :options="deptOptions" :normalizer="normalizer" placeholder="选择上级团队" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入部门名称" />
<el-form-item label="团队名称" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入团队名称" />
</el-form-item>
</el-col>
<el-col :span="12">
@ -136,7 +136,7 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门状态">
<el-form-item label="团队状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_normal_disable"
@ -188,7 +188,7 @@
</div>
<el-button type="text" icon="el-icon-plus" @click="addModelParamRow">添加模型</el-button>
<p class="model-parm-hint">
保存为 JSON 写入库表 model_parm门户视频生成按用户所属二级部门读取
保存为 JSON 写入库表 model_parm门户视频生成按用户所属二级团队读取
留空或未配置时使用配置文件 portal.video.models
</p>
</div>
@ -256,10 +256,10 @@ export default {
modelParamRows: [{ label: '', value: '' }],
rules: {
parentId: [
{ required: true, message: "上级部门不能为空", trigger: "blur" }
{ required: true, message: "上级团队不能为空", trigger: "blur" }
],
deptName: [
{ required: true, message: "部门名称不能为空", trigger: "blur" }
{ required: true, message: "团队名称不能为空", trigger: "blur" }
],
orderNum: [
{ required: true, message: "显示排序不能为空", trigger: "blur" }
@ -348,7 +348,7 @@ export default {
this.form.parentId = row.deptId
}
this.open = true
this.title = "添加部门"
this.title = "添加团队"
listDept().then(response => {
this.deptOptions = this.handleTree(response.data, "deptId")
})
@ -400,7 +400,7 @@ export default {
this.form = response.data
this.syncModelRowsFromForm()
this.open = true
this.title = "修改部门"
this.title = "修改团队"
listDeptExcludeChild(row.deptId).then(response => {
this.deptOptions = this.handleTree(response.data, "deptId")
if (this.deptOptions.length == 0) {

View File

@ -9,7 +9,7 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="部门名称" prop="deptName">
<el-form-item label="团队名称" prop="deptName">
<el-input
v-model="queryParams.deptName"
placeholder="支持模糊搜索"
@ -55,14 +55,22 @@
<el-table v-loading="loading" :data="groupChargeOrderList">
<el-table-column label="订单号" align="center" prop="orderNum" width="200" show-overflow-tooltip />
<el-table-column label="部门名称" align="center" prop="deptName" width="150" show-overflow-tooltip />
<el-table-column label="团队名称" align="center" prop="deptName" width="150" show-overflow-tooltip />
<el-table-column label="类型" align="center" width="120">
<template slot-scope="scope">
<span>{{ orderTypeLabel(scope.row.orderType) }}</span>
</template>
</el-table-column>
<el-table-column label="金额(元)" align="center" prop="money" width="120" />
<el-table-column label="积分" align="center" prop="amount" width="120" />
<el-table-column label="金额(元)" align="center" prop="money" width="120">
<template slot-scope="scope">
<span>{{ formatTableMoney(scope.row.money) }}</span>
</template>
</el-table-column>
<el-table-column label="积分" align="center" prop="amount" width="120">
<template slot-scope="scope">
<span>{{ formatTableAmount(scope.row.amount) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="100">
<template slot-scope="scope">
<span>{{ statusLabel(scope.row.status) }}</span>
@ -176,6 +184,28 @@ export default {
this.getList()
},
methods: {
/** 列表金额:西式千分位,最多两位小数 */
formatTableMoney(value) {
if (value === null || value === undefined || value === "") {
return "—"
}
const n = Number(value)
if (Number.isNaN(n)) {
return "—"
}
return n.toLocaleString("en-US", { maximumFractionDigits: 2, minimumFractionDigits: 0 })
},
/** 列表积分:西式千分位整数 */
formatTableAmount(value) {
if (value === null || value === undefined || value === "") {
return "—"
}
const n = Number(value)
if (Number.isNaN(n)) {
return "—"
}
return n.toLocaleString("en-US", { maximumFractionDigits: 0 })
},
orderTypeLabel(orderType) {
const n = orderType !== undefined && orderType !== null ? Number(orderType) : null
const hit = this.orderTypeOptions.find(o => o.value === n)

View File

@ -29,11 +29,6 @@
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
@ -42,8 +37,6 @@
@click="handleAdd"
v-hasPermi="['ai:manager:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
@ -53,19 +46,6 @@
@click="handleUpdate"
v-hasPermi="['ai:manager:edit']"
>修改</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['ai:manager:remove']"
>删除</el-button>
</el-col>-->
<el-col :span="1.5">
<el-button
type="warning"
plain
@ -74,8 +54,11 @@
@click="handleExport"
v-hasPermi="['ai:manager:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
</el-row>
<el-table v-loading="loading" :data="managerList" @selection-change="handleSelectionChange">
@ -93,9 +76,9 @@
></el-switch>
</template>
</el-table-column>
<!--
<el-table-column label="提示词" align="center" prop="prompt">
<template slot-scope="scope">
<!-- 核心 popover ref触发元素绑定 v-popover -->
<el-popover
ref="promptPopover"
placement="top"
@ -103,7 +86,6 @@
trigger="hover"
:content="scope.row.prompt || '无提示词'"
></el-popover>
<!-- 触发元素绑定 v-popover popover ref -->
<div
class="text-ellipsis-two-lines"
v-popover:promptPopover
@ -111,6 +93,7 @@
>{{ scope.row.prompt}}</div>
</template>
</el-table-column>
-->
<el-table-column label="类型" align="center" prop="type">
<template slot-scope="scope">
<dict-tag :options="dict.type.ai_function_type" :value="scope.row.type" />
@ -119,13 +102,13 @@
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
<!--<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="tagManager(scope.row)"
v-hasPermi="['ai:manager:edit']"
>标签管理</el-button>
>标签管理</el-button>-->
<el-button
size="mini"
type="text"

View File

@ -13,6 +13,7 @@
<el-input
v-model="queryParams.thirdPartyOrderNum"
placeholder="支持模糊搜索"
style="width: 240px"
clearable
@keyup.enter.native="handleQuery"
/>
@ -34,7 +35,7 @@
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 140px">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 207px">
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
@ -50,13 +51,9 @@
/>
</el-form-item>
<el-form-item>
&nbsp;&nbsp;&nbsp;&nbsp;
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
plain
@ -65,19 +62,23 @@
@click="handleExport"
v-hasPermi="['/ai/video/order/']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="orderList">
<el-table-column label="团队名称" align="center" prop="deptName" width="140" show-overflow-tooltip />
<el-table-column label="用户名称" align="center" prop="userName" width="120" show-overflow-tooltip />
<el-table-column label="订单号" align="center" prop="orderNum" width="200" show-overflow-tooltip />
<el-table-column label="三方订单号" align="center" prop="thirdPartyOrderNum" width="200" show-overflow-tooltip />
<el-table-column label="积分" align="center" prop="amount" width="100" />
<el-table-column label="tokens" align="center" prop="totalUsage" width="100">
<el-table-column label="消耗积分" align="center" prop="amount" width="120">
<template slot-scope="scope">
<span>{{ scope.row.totalUsage != null && scope.row.totalUsage !== '' ? scope.row.totalUsage : '—' }}</span>
<span>{{ formatThousands(scope.row.amount) }}</span>
</template>
</el-table-column>
<el-table-column label="使用tokens" align="center" prop="totalUsage" width="120">
<template slot-scope="scope">
<span>{{ formatThousands(scope.row.totalUsage) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="100">
@ -85,7 +86,7 @@
<span>{{ statusLabel(scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column label="结果预览" min-width="200">
<el-table-column label="结果预览" min-width="185">
<template slot-scope="scope">
<div v-if="extractVideoUrl(scope.row.result)">
<video
@ -172,6 +173,17 @@ export default {
this.getList()
},
methods: {
/** 千位分节en-US整数空为 — */
formatThousands(value) {
if (value === null || value === undefined || value === "") {
return "—"
}
const n = Number(value)
if (Number.isNaN(n)) {
return String(value)
}
return n.toLocaleString("en-US", { maximumFractionDigits: 0 })
},
initDefaultDateRange() {
this.dateRange = this.buildLastMonthDateRange()
},

View File

@ -10,10 +10,10 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="部门名称" prop="deptName">
<el-form-item label="团队名称" prop="deptName">
<el-input
v-model="queryParams.deptName"
placeholder="请输入部门名称"
placeholder="请输入团队名称"
clearable
style="width: 140px"
@keyup.enter.native="handleQuery"
@ -38,11 +38,6 @@
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
plain
@ -51,21 +46,33 @@
@click="handleExport"
v-hasPermi="['ai:record:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="recordList">
<el-table-column label="ID" align="center" prop="id" width="100"/>
<el-table-column label="关联订单号" align="center" prop="relationOrderNo" width="200" show-overflow-tooltip />
<el-table-column label="部门名称" align="center" prop="deptName" width="150" show-overflow-tooltip />
<el-table-column label="团队名称" align="center" prop="deptName" width="150" show-overflow-tooltip />
<el-table-column label="操作类型" align="center" width="120">
<template slot-scope="scope">
<span>{{ typeLabel(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="变更积分" align="center" prop="changeAmount" width="100" />
<el-table-column label="变更后积分" align="center" prop="resultAmount" width="100" />
<el-table-column
label="变更积分"
align="center"
prop="changeAmount"
width="120"
:formatter="formatChangeAmount"
/>
<el-table-column
label="变更后积分"
align="center"
prop="resultAmount"
width="120"
:formatter="formatResultAmount"
/>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template slot-scope="scope">
@ -116,6 +123,38 @@ export default {
this.getList()
},
methods: {
/** 西式千分位,积分为整数 */
formatPointsWestern(value) {
if (value === null || value === undefined || value === "") {
return ""
}
const n = Number(value)
if (Number.isNaN(n)) {
return String(value)
}
return n.toLocaleString("en-US", { maximumFractionDigits: 0 })
},
formatChangeAmount(row) {
const value = row.changeAmount
if (value === null || value === undefined || value === "") {
return ""
}
const n = Number(value)
if (Number.isNaN(n)) {
return String(value)
}
const formatted = Math.abs(n).toLocaleString("en-US", { maximumFractionDigits: 0 })
if (n > 0) {
return `+${formatted}`
}
if (n < 0) {
return `-${formatted}`
}
return "0"
},
formatResultAmount(row) {
return this.formatPointsWestern(row.resultAmount)
},
typeLabel(type) {
const n = type !== undefined && type !== null ? Number(type) : null
const hit = this.typeOptions.find(item => item.value === n)

View File

@ -116,7 +116,11 @@
<span>{{ parseTime(scope.row.loginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="剩余积分" align="center" prop="balance" />
<el-table-column label="剩余积分" align="center" prop="balance" width="120">
<template slot-scope="scope">
<span>{{ formatThousands(scope.row.balance) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="460">
<template slot-scope="scope">
<el-button
@ -439,6 +443,17 @@ export default {
this.getList();
},
methods: {
/** 千位分节en-US整数空为 — */
formatThousands(value) {
if (value === null || value === undefined || value === "") {
return "—";
}
const n = Number(value);
if (Number.isNaN(n)) {
return String(value);
}
return n.toLocaleString("en-US", { maximumFractionDigits: 0 });
},
loadDeptTree() {
listDept().then(res => {
this.deptOptions = this.handleTree(res.data, "deptId");

View File

@ -218,7 +218,7 @@
<!-- </el-collapse-item>-->
<!-- <el-collapse-item title="v3.8.7 - 2023-12-08">-->
<!-- <ol>-->
<!-- <li>操作日志记录部门名称</li>-->
<!-- <li>操作日志记录团队名称</li>-->
<!-- <li>全局数据存储用户编号</li>-->
<!-- <li>新增编程式判断资源访问权限</li>-->
<!-- <li>操作日志列表新增IP地址查询</li>-->

View File

@ -35,7 +35,7 @@
</el-table-column>
<el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
<el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="部门名称" align="center" prop="deptName" />
<el-table-column label="团队名称" align="center" prop="deptName" />
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
<el-table-column label="浏览器" align="center" prop="browser" />

View File

@ -33,8 +33,12 @@
<el-table-column label="类型" width="90">
<template slot-scope="s">{{ typeLabel(s.row.orderType) }}</template>
</el-table-column>
<el-table-column label="金额" prop="money" width="90" />
<el-table-column label="积分" prop="amount" width="90" />
<el-table-column label="金额" prop="money" width="110" align="right">
<template slot-scope="s"><span>{{ formatMoney(s.row.money) }}</span></template>
</el-table-column>
<el-table-column label="积分" prop="amount" width="110" align="right">
<template slot-scope="s"><span>{{ formatAmount(s.row.amount) }}</span></template>
</el-table-column>
<el-table-column label="时间" width="160"><template slot-scope="s">{{ parseTime(s.row.createTime) }}</template></el-table-column>
<el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
</el-table>
@ -58,6 +62,18 @@ export default {
},
created() { this.getList() },
methods: {
formatMoney(value) {
if (value == null || value === '') return '—'
const n = Number(value)
if (Number.isNaN(n)) return '—'
return n.toLocaleString('en-US', { maximumFractionDigits: 2, minimumFractionDigits: 0 })
},
formatAmount(value) {
if (value == null || value === '') return '—'
const n = Number(value)
if (Number.isNaN(n)) return '—'
return n.toLocaleString('en-US', { maximumFractionDigits: 0 })
},
typeLabel(t) {
const m = { 0: '充值', 1: '退款', 2: '手动修改' }
return m[t] != null ? m[t] : '—'

View File

@ -34,8 +34,12 @@
<span>{{ typeLabel(s.row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="变更积分" prop="changeAmount" width="100" />
<el-table-column label="变更后积分" prop="resultAmount" width="100" />
<el-table-column label="变更积分" prop="changeAmount" width="120" align="right">
<template slot-scope="s"><span>{{ formatChangeAmount(s.row) }}</span></template>
</el-table-column>
<el-table-column label="变更后积分" prop="resultAmount" width="120" align="right">
<template slot-scope="s"><span>{{ formatResultAmount(s.row) }}</span></template>
</el-table-column>
<el-table-column label="时间" width="160"><template slot-scope="s">{{ parseTime(s.row.createTime) }}</template></el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
@ -65,6 +69,33 @@ export default {
},
created() { this.getList() },
methods: {
formatPointsWestern(value) {
if (value === null || value === undefined || value === '') {
return ''
}
const n = Number(value)
if (Number.isNaN(n)) {
return String(value)
}
return n.toLocaleString('en-US', { maximumFractionDigits: 0 })
},
formatChangeAmount(row) {
const value = row.changeAmount
if (value === null || value === undefined || value === '') {
return ''
}
const n = Number(value)
if (Number.isNaN(n)) {
return String(value)
}
const formatted = Math.abs(n).toLocaleString('en-US', { maximumFractionDigits: 0 })
if (n > 0) return `+${formatted}`
if (n < 0) return `-${formatted}`
return '0'
},
formatResultAmount(row) {
return this.formatPointsWestern(row.resultAmount)
},
typeLabel(type) {
const n = type !== undefined && type !== null ? Number(type) : null
const hit = this.typeOptions.find(item => item.value === n)

View File

@ -18,7 +18,7 @@
</el-row>
<el-row :gutter="16" style="margin-top:16px">
<el-col :span="8">
<el-card shadow="hover"><div class="metric-title">AI用户数实时</div><div class="metric-val">{{ info.aiUserCount }}</div></el-card>
<el-card shadow="hover"><div class="metric-title">AI用户数实时</div><div class="metric-val">{{ aiUserCountText }}</div></el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover"><div class="metric-title"> 7 日消耗积分 <span class="hint">5 分钟缓存</span></div><div class="metric-val">{{ info.last7DaysConsumeScore }}</div></el-card>
@ -44,6 +44,17 @@ export default {
created() {
this.load()
},
computed: {
aiUserCountText() {
const cur = this.info.aiUserCount
const max = this.info.aiUserMaxCount
const left = cur != null && cur !== '' ? cur : 0
if (max == null || max <= 0) {
return left + '/无限制'
}
return left + '/' + max
}
},
methods: {
load() {
this.loading = true

View File

@ -48,7 +48,7 @@
<el-table-column label="主键ID" prop="id" width="80" />
<el-table-column label="账号" prop="username" />
<el-table-column label="昵称" prop="nickname" />
<el-table-column label="部门" prop="deptName" />
<el-table-column label="团队" prop="deptName" />
<el-table-column label="手机" prop="phone" width="120" />
<el-table-column label="状态" width="80">
<template slot-scope="scope">

View File

@ -50,7 +50,7 @@
</el-table-column>
<el-table-column prop="balance" label="剩余积分" width="120" align="right">
<template slot-scope="scope">
<span>{{ scope.row.balance != null ? scope.row.balance : '—' }}</span>
<span>{{ formatDeptBalance(scope.row.balance) }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
@ -177,14 +177,14 @@
:max="999999"
:precision="0"
controls-position="right"
placeholder="0=不限制"
placeholder="0 表示不限制"
style="width: 100%"
:disabled="isFirstLevelEditForm"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<p class="model-parm-hint" style="margin: 0; padding-top: 8px">&nbsp;&nbsp;限制本团队下启用状态账号数量0 或不填表示不限制</p>
<p class="model-parm-hint" style="margin: 0; padding-top: 8px">&nbsp;&nbsp;限制本团队下启用状态账号数量0 表示不限制</p>
</el-col>
</el-row>
</el-form>
@ -322,15 +322,12 @@
>
<el-form ref="editScoreFormRef" :model="editScoreForm" :rules="editScoreRules" label-width="88px">
<el-form-item label="积分" prop="score">
<el-input-number
v-model="editScoreForm.score"
:precision="0"
:step="1"
:min="-100000000"
:max="100000000"
controls-position="right"
class="edit-score-input-number"
placeholder="正数增加负数扣减不能为0"
<el-input
:value="editScoreScoreDisplay"
class="edit-score-input"
clearable
placeholder="正数增加负数扣减不能为0如 9,999,999 或 -1,000,000"
@input="onEditScoreInput"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
@ -380,7 +377,7 @@
color: #e6a23c;
font-size: 12px;
}
.edit-score-input-number {
.edit-score-input {
width: 100%;
}
</style>
@ -395,7 +392,10 @@ import {
formatMoneyWesternFinal,
sanitizeIntDigits,
formatIntWesternDisplay,
intStringToNumber
intStringToNumber,
sanitizeSignedIntDigits,
formatSignedIntDisplay,
signedIntStringToNumber
} from "@/utils/westernNumberFormat"
import Treeselect from "@riophae/vue-treeselect"
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
@ -454,6 +454,7 @@ export default {
]
},
editScoreOpen: false,
editScoreScoreDisplay: "",
editScoreForm: {
deptId: undefined,
deptName: "",
@ -531,6 +532,16 @@ export default {
message: "请输入正确的手机号码",
trigger: "blur"
}
],
maxUserCount: [
{ required: true, message: "账号上限不能为空", trigger: "change" },
{
type: "number",
min: 0,
max: 999999,
message: "账号上限须为 0999999 的整数0 表示不限制",
trigger: "change"
}
]
}
}
@ -544,6 +555,22 @@ export default {
}
},
methods: {
/** 列表「剩余积分」:西式千分位,与业务积分展示一致;空为 — */
formatDeptBalance(value) {
if (value == null || value === "") {
return "—"
}
const n = Number(value)
if (Number.isNaN(n)) {
return "—"
}
return n.toLocaleString("en-US", { maximumFractionDigits: 2, minimumFractionDigits: 0 })
},
onEditScoreInput(val) {
const s = sanitizeSignedIntDigits(val)
this.editScoreScoreDisplay = s === "" ? "" : s === "-" ? "-" : formatSignedIntDisplay(s)
this.editScoreForm.score = signedIntStringToNumber(s)
},
isFirstLevelRow(row) {
if (!row) {
return false
@ -711,6 +738,7 @@ export default {
})
},
resetEditScore() {
this.editScoreScoreDisplay = ""
this.editScoreForm = {
deptId: undefined,
deptName: "",
@ -727,6 +755,7 @@ export default {
if (!this.isFirstLevelRow(row) && !this.isSecondLevelRow(row)) {
return
}
this.editScoreScoreDisplay = ""
this.editScoreForm = {
deptId: row.deptId,
deptName: row.deptName,
@ -807,7 +836,7 @@ export default {
leader: undefined,
phone: undefined,
email: undefined,
maxUserCount: undefined,
maxUserCount: 0,
status: "0"
}
this.originalForm = {}
@ -832,6 +861,7 @@ export default {
}
this.form.parentId = row.deptId
}
this.form.maxUserCount = 0
this.open = true
this.title = "添加二级团队"
listDept().then(response => {
@ -850,8 +880,11 @@ export default {
handleUpdate(row) {
this.reset()
getDept(row.deptId).then(response => {
this.form = response.data
this.form = { ...response.data }
this.originalForm = { ...response.data }
if (this.form.maxUserCount == null) {
this.form.maxUserCount = 0
}
this.open = true
this.title = "修改团队"
listDeptExcludeChild(row.deptId).then(response => {

View File

@ -298,11 +298,11 @@ export default {
},
{
value: "3",
label: "本部门数据权限"
label: "本团队数据权限"
},
{
value: "4",
label: "本部门及以下数据权限"
label: "本团队及以下数据权限"
},
{
value: "5",

View File

@ -6,7 +6,7 @@
<pane size="16">
<el-col>
<div class="head-container">
<el-input v-model="deptName" placeholder="请输入部门名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px" />
<el-input v-model="deptName" placeholder="请输入团队名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px" />
</div>
<div class="head-container">
<el-tree :data="deptOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current @node-click="handleNodeClick" />
@ -61,7 +61,7 @@
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
<el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
<el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
<el-table-column label="团队" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
<el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
<template slot-scope="scope">
@ -236,7 +236,7 @@ export default {
enabledDeptOptions: undefined,
//
open: false,
//
//
deptName: undefined,
//
initPassword: undefined,

View File

@ -24,7 +24,7 @@
<div class="pull-right">{{ user.email }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属部门
<svg-icon icon-class="tree" />所属团队
<div class="pull-right" v-if="user.dept">{{ user.dept.deptName }} / {{ postGroup }}</div>
</li>
<li class="list-group-item">

View File

@ -6,7 +6,7 @@
<div class="view-tabs">
<a-tabs v-model="activeTab" @change="handleTabChange" type="card">
<a-tab-pane key="personal" title="个人" />
<a-tab-pane key="department" title="部门" />
<a-tab-pane key="department" title="团队" />
</a-tabs>
</div>
<!-- 查询区域 -->
@ -303,7 +303,7 @@ export default {
beginTime: '',
endTime: ''
},
activeTab: 'personal', // 'personal', 'department'
activeTab: 'personal', // 'personal', 'department'
pagination: {
total: 0,
pageNum: 1,
@ -331,7 +331,7 @@ export default {
pageSize: this.pagination.pageSize
}
console.log("this.activeTab", this.activeTab)
// personal = , department = dept=true
// personal = , department = dept=true
if (this.activeTab === 'department') {
params.dept = true
}

View File

@ -47,13 +47,13 @@ public class AiChargeRefundOrderController extends BaseController
* 导出团队部门充值退款订单列表
*/
@PreAuthorize("@ss.hasPermi('ai:groupChargeOrder:export')")
@Log(title = "团队(部门)充值退款订单", businessType = BusinessType.EXPORT)
@Log(title = "团队充值退款订单", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, AiChargeRefundOrder aiChargeRefundOrder)
{
List<AiChargeRefundOrder> list = aiChargeRefundOrderService.selectAiChargeRefundOrderList(aiChargeRefundOrder);
ExcelUtil<AiChargeRefundOrder> util = new ExcelUtil<AiChargeRefundOrder>(AiChargeRefundOrder.class);
util.exportExcel(response, list, "团队(部门)充值退款订单数据");
util.exportExcel(response, list, "团队充值退款订单数据");
}
/**

View File

@ -47,20 +47,20 @@ public class AiDeptArkConfigController extends BaseController
}
/**
* 导出团队部门对应火山引擎配置列表
* 导出团队对应火山引擎配置列表
*/
@PreAuthorize("@ss.hasPermi('ai:config:export')")
@Log(title = "团队(部门)对应火山引擎配置", businessType = BusinessType.EXPORT)
@Log(title = "团队对应火山引擎配置", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, AiDeptArkConfig aiDeptArkConfig)
{
List<AiDeptArkConfig> list = aiDeptArkConfigService.selectAiDeptArkConfigList(aiDeptArkConfig);
ExcelUtil<AiDeptArkConfig> util = new ExcelUtil<AiDeptArkConfig>(AiDeptArkConfig.class);
util.exportExcel(response, list, "团队(部门)对应火山引擎配置数据");
util.exportExcel(response, list, "团队对应火山引擎配置数据");
}
/**
* 获取团队部门对应火山引擎配置详细信息
* 获取团队对应火山引擎配置详细信息
*/
@PreAuthorize("@ss.hasPermi('ai:config:query')")
@GetMapping(value = "/{id}")
@ -73,7 +73,7 @@ public class AiDeptArkConfigController extends BaseController
* 新增团队部门对应火山引擎配置
*/
@PreAuthorize("@ss.hasPermi('ai:config:add')")
@Log(title = "团队(部门)对应火山引擎配置", businessType = BusinessType.INSERT)
@Log(title = "团队对应火山引擎配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody AiDeptArkConfig aiDeptArkConfig)
{
@ -84,7 +84,7 @@ public class AiDeptArkConfigController extends BaseController
* 修改团队部门对应火山引擎配置
*/
@PreAuthorize("@ss.hasPermi('ai:config:edit')")
@Log(title = "团队(部门)对应火山引擎配置", businessType = BusinessType.UPDATE)
@Log(title = "团队对应火山引擎配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody AiDeptArkConfig aiDeptArkConfig)
{
@ -95,7 +95,7 @@ public class AiDeptArkConfigController extends BaseController
* 删除团队部门对应火山引擎配置
*/
@PreAuthorize("@ss.hasPermi('ai:config:remove')")
@Log(title = "团队(部门)对应火山引擎配置", businessType = BusinessType.DELETE)
@Log(title = "团队对应火山引擎配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{

View File

@ -1,6 +1,7 @@
package com.ruoyi.web.controller.ai;
import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import com.ruoyi.ai.domain.vo.AiBalanceChangeRecordListVo;
import com.ruoyi.ai.service.IAiBalanceChangeRecordService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
/**
* 余额使用记录Controller
@ -37,7 +39,10 @@ public class AiBalanceChangeRecordController extends BaseController
{
startPage();
List<AiBalanceChangeRecord> list = aiBalanceChangeRecordService.selectAiBalanceChangeRecordList(aiBalanceChangeRecord);
return getDataTable(list);
List<AiBalanceChangeRecordListVo> rows = list.stream().map(AiBalanceChangeRecordListVo::from).collect(Collectors.toList());
TableDataInfo rsp = getDataTable(list);
rsp.setRows(rows);
return rsp;
}
/**
@ -49,8 +54,9 @@ public class AiBalanceChangeRecordController extends BaseController
public void export(HttpServletResponse response, AiBalanceChangeRecord aiBalanceChangeRecord)
{
List<AiBalanceChangeRecord> list = aiBalanceChangeRecordService.selectAiBalanceChangeRecordList(aiBalanceChangeRecord);
ExcelUtil<AiBalanceChangeRecord> util = new ExcelUtil<AiBalanceChangeRecord>(AiBalanceChangeRecord.class);
util.exportExcel(response, list, "余额使用记录数据");
List<AiBalanceChangeRecordListVo> rows = list.stream().map(AiBalanceChangeRecordListVo::from).collect(Collectors.toList());
ExcelUtil<AiBalanceChangeRecordListVo> util = new ExcelUtil<AiBalanceChangeRecordListVo>(AiBalanceChangeRecordListVo.class);
util.exportExcel(response, rows, "余额使用记录数据");
}
/**

View File

@ -98,21 +98,21 @@ public class AiDeptController extends BaseController
}
@PreAuthorize("@ss.hasPermi('ai:dept:remove')")
@Log(title = "AI部门管理", businessType = BusinessType.DELETE)
@Log(title = "AI团队管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{deptId}")
public AjaxResult remove(@PathVariable Long deptId)
{
if (deptService.hasChildByDeptId(deptId))
{
return warn("存在下级部门,不允许删除");
return warn("存在下级团队,不允许删除");
}
if (deptService.checkDeptExistUser(deptId))
{
return warn("部门存在系统用户,不允许删除");
return warn("团队存在系统用户,不允许删除");
}
if (aiUserService.countAiUserByDeptId(deptId) > 0)
{
return warn("部门存在 AI 用户,不允许删除");
return warn("团队存在 AI 用户,不允许删除");
}
deptService.checkDeptDataScope(deptId);
return toAjax(deptService.deleteDeptById(deptId));

View File

@ -167,9 +167,9 @@ public class AiUserController extends BaseController {
/**
* 用户余额回收至部门积分池
*/
@ApiOperation("用户积分回收至部门")
@ApiOperation("用户积分回收至团队")
@PreAuthorize("@ss.hasPermi('ai:user:deptScoreReclaim')")
@Log(title = "AI用户部门积分回收", businessType = BusinessType.UPDATE)
@Log(title = "AI用户团队积分回收", businessType = BusinessType.UPDATE)
@PutMapping("/dept-score/reclaim")
public AjaxResult reclaimDeptScore(@Valid @RequestBody AiUserDeptScoreRequest request) {
deptUserScoreTransferService.reclaimDeptScore(request);

View File

@ -1,15 +1,14 @@
package com.ruoyi.ai.domain;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import java.math.BigDecimal;
/**
* 余额使用记录对象 ai_balance_change_record
*
@ -36,7 +35,6 @@ public class AiBalanceChangeRecord extends BaseEntity {
/**
* 关联订单号
*/
@Excel(name = "关联订单号")
private String orderNo;
/**
@ -45,34 +43,30 @@ public class AiBalanceChangeRecord extends BaseEntity {
private Long userId;
/**
* 用户ID
* 用户对外标识查询结果来自关联 ai_user.user_id
*/
@TableField(exist = false)
private String uuid;
/**
* 操作类型0-充值 1-返佣 2-充值赠送 3-体验金赠送 4-体验金回收 5-图生图 6-一键换脸 7-快捷生图 8-快捷生视频 9-退款 10-系统操作 12-部门下放积分 13-回收至部门
* 操作类型
*/
@Excel(name = "操作类型")
private Integer type;
/**
* 变更积分
*/
@Excel(name = "变更积分")
private BigDecimal changeAmount;
/**
* 变更后金额
* 变更后积分
*/
@Excel(name = "变更后积分")
private BigDecimal resultAmount;
/**
* 用户昵称
* 用户昵称关联查询
*/
@TableField(exist = false)
@Excel(name = "用户昵称")
private String nickname;
}

View File

@ -0,0 +1,65 @@
package com.ruoyi.ai.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import com.ruoyi.ai.handler.BalanceChangeAmountExcelHandler;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
/**
* 余额使用记录列表 / 导出展示 ORM 实体分离避免在实体上堆展示字段与 Excel 注解
*/
@Data
public class AiBalanceChangeRecordListVo implements Serializable {
private static final long serialVersionUID = 1L;
@Excel(name = "主键ID", sort = 1)
private Long id;
@Excel(name = "用户ID", sort = 2)
private String uuid;
@Excel(name = "用户昵称", sort = 3)
private String nickname;
@Excel(name = "关联订单号", sort = 4)
private String orderNo;
@Excel(name = "操作类型", sort = 5, readConverterExp = "0=充值,1=返佣,2=充值赠送,3=体验金赠送,4=体验金回收,5=一键换衣,6=图生图2,7=一键换脸,8=快捷生图,9=快捷生视频,10=退款,11=系统操作,12=团队下发,13=团队收回")
private Integer type;
@Excel(name = "变更积分", sort = 6, handler = BalanceChangeAmountExcelHandler.class)
private BigDecimal changeAmount;
@Excel(name = "变更后积分", sort = 7)
private BigDecimal resultAmount;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "操作时间", sort = 8, width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Excel(name = "备注", sort = 9)
private String remark;
public static AiBalanceChangeRecordListVo from(AiBalanceChangeRecord e) {
if (e == null) {
return null;
}
AiBalanceChangeRecordListVo v = new AiBalanceChangeRecordListVo();
v.setId(e.getId());
v.setUuid(e.getUuid());
v.setNickname(e.getNickname());
v.setOrderNo(e.getOrderNo());
v.setType(e.getType());
v.setChangeAmount(e.getChangeAmount());
v.setResultAmount(e.getResultAmount());
v.setCreateTime(e.getCreateTime());
v.setRemark(e.getRemark());
return v;
}
}

View File

@ -0,0 +1,42 @@
package com.ruoyi.ai.handler;
import java.math.BigDecimal;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
import com.ruoyi.common.utils.poi.ExcelHandlerAdapter;
/**
* 导出变更积分正数前加+与前端列表展示一致
*/
public class BalanceChangeAmountExcelHandler implements ExcelHandlerAdapter
{
@Override
public Object format(Object value, String[] args, Cell cell, Workbook wb)
{
if (value == null)
{
return "";
}
BigDecimal amount;
if (value instanceof BigDecimal)
{
amount = (BigDecimal) value;
}
else if (value instanceof Number)
{
amount = BigDecimal.valueOf(((Number) value).doubleValue());
}
else
{
return value.toString();
}
String plain = amount.stripTrailingZeros().toPlainString();
if (amount.compareTo(BigDecimal.ZERO) > 0)
{
return "+" + plain;
}
return plain;
}
}

View File

@ -22,6 +22,9 @@ public class SubteamOverviewVO implements Serializable {
/** AI 用户数量ai_user实时 */
private Integer aiUserCount;
/** AI 账号上限,与部门 max_user_count 一致null 或小于等于 0 表示不限制 */
private Integer aiUserMaxCount;
/** 近七日消耗积分ai_video_report_data可能来自缓存 */
private BigDecimal last7DaysConsumeScore;

View File

@ -49,6 +49,7 @@ public class SubteamOverviewServiceImpl implements ISubteamOverviewService {
vo.setDeptName(dept != null ? dept.getDeptName() : "");
vo.setBalance(dept != null && dept.getBalance() != null ? dept.getBalance() : BigDecimal.ZERO);
vo.setAiUserCount(aiUserService.countAiUserByDeptId(deptId));
vo.setAiUserMaxCount(dept != null ? dept.getMaxUserCount() : null);
LocalDate end = LocalDate.now();
LocalDate start = end.minusDays(6);

View File

@ -12,6 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="orderNo" column="order_no" />
<result property="userId" column="user_id" />
<result property="type" column="type" />
<result property="changeAmount" column="change_amount" />
@ -21,7 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectAiBalanceChangeRecordVo">
select r.id, r.del_flag, r.create_by, r.create_time, r.update_by, r.update_time, r.remark, r.user_id, r.type, r.change_amount, r.result_amount, u.nickname, u.user_id uuid from ai_balance_change_record r
select r.id, r.del_flag, r.create_by, r.create_time, r.update_by, r.update_time, r.remark, r.order_no, r.user_id, r.type, r.change_amount, r.result_amount, u.nickname, u.user_id uuid from ai_balance_change_record r
left join ai_user u on u.id = r.user_id
</sql>