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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="部门名称" prop="deptName"> <el-form-item label="团队名称" prop="deptName">
<el-input <el-input
v-model="queryParams.deptName" v-model="queryParams.deptName"
placeholder="支持模糊搜索" placeholder="支持模糊搜索"
@ -55,14 +55,22 @@
<el-table v-loading="loading" :data="groupChargeOrderList"> <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="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"> <el-table-column label="类型" align="center" width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ orderTypeLabel(scope.row.orderType) }}</span> <span>{{ orderTypeLabel(scope.row.orderType) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="金额(元)" align="center" prop="money" width="120" /> <el-table-column label="金额(元)" align="center" prop="money" width="120">
<el-table-column label="积分" align="center" prop="amount" 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"> <el-table-column label="状态" align="center" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ statusLabel(scope.row.status) }}</span> <span>{{ statusLabel(scope.row.status) }}</span>
@ -176,6 +184,28 @@ export default {
this.getList() this.getList()
}, },
methods: { 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) { orderTypeLabel(orderType) {
const n = orderType !== undefined && orderType !== null ? Number(orderType) : null const n = orderType !== undefined && orderType !== null ? Number(orderType) : null
const hit = this.orderTypeOptions.find(o => o.value === n) const hit = this.orderTypeOptions.find(o => o.value === n)

View File

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

View File

@ -13,6 +13,7 @@
<el-input <el-input
v-model="queryParams.thirdPartyOrderNum" v-model="queryParams.thirdPartyOrderNum"
placeholder="支持模糊搜索" placeholder="支持模糊搜索"
style="width: 240px"
clearable clearable
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
@ -34,7 +35,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <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-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -50,13 +51,9 @@
/> />
</el-form-item> </el-form-item>
<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 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-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 <el-button
type="warning" type="warning"
plain plain
@ -65,19 +62,23 @@
@click="handleExport" @click="handleExport"
v-hasPermi="['/ai/video/order/']" v-hasPermi="['/ai/video/order/']"
>导出</el-button> >导出</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-row> </el-form>
<el-table v-loading="loading" :data="orderList"> <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="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="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="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="thirdPartyOrderNum" width="200" show-overflow-tooltip />
<el-table-column label="积分" align="center" prop="amount" width="100" /> <el-table-column label="消耗积分" align="center" prop="amount" width="120">
<el-table-column label="tokens" align="center" prop="totalUsage" width="100">
<template slot-scope="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column label="状态" align="center" width="100"> <el-table-column label="状态" align="center" width="100">
@ -85,7 +86,7 @@
<span>{{ statusLabel(scope.row.status) }}</span> <span>{{ statusLabel(scope.row.status) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="结果预览" min-width="200"> <el-table-column label="结果预览" min-width="185">
<template slot-scope="scope"> <template slot-scope="scope">
<div v-if="extractVideoUrl(scope.row.result)"> <div v-if="extractVideoUrl(scope.row.result)">
<video <video
@ -172,6 +173,17 @@ export default {
this.getList() this.getList()
}, },
methods: { 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() { initDefaultDateRange() {
this.dateRange = this.buildLastMonthDateRange() this.dateRange = this.buildLastMonthDateRange()
}, },

View File

@ -10,10 +10,10 @@
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="部门名称" prop="deptName"> <el-form-item label="团队名称" prop="deptName">
<el-input <el-input
v-model="queryParams.deptName" v-model="queryParams.deptName"
placeholder="请输入部门名称" placeholder="请输入团队名称"
clearable clearable
style="width: 140px" style="width: 140px"
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
@ -38,11 +38,6 @@
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <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-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 <el-button
type="warning" type="warning"
plain plain
@ -51,21 +46,33 @@
@click="handleExport" @click="handleExport"
v-hasPermi="['ai:record:export']" v-hasPermi="['ai:record:export']"
>导出</el-button> >导出</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-row> </el-form>
<el-table v-loading="loading" :data="recordList"> <el-table v-loading="loading" :data="recordList">
<el-table-column label="ID" align="center" prop="id" width="100"/> <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="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"> <el-table-column label="操作类型" align="center" width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ typeLabel(scope.row.type) }}</span> <span>{{ typeLabel(scope.row.type) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="变更积分" align="center" prop="changeAmount" width="100" /> <el-table-column
<el-table-column label="变更后积分" align="center" prop="resultAmount" width="100" /> 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="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160"> <el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template slot-scope="scope"> <template slot-scope="scope">
@ -116,6 +123,38 @@ export default {
this.getList() this.getList()
}, },
methods: { 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) { typeLabel(type) {
const n = type !== undefined && type !== null ? Number(type) : null const n = type !== undefined && type !== null ? Number(type) : null
const hit = this.typeOptions.find(item => item.value === n) 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> <span>{{ parseTime(scope.row.loginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template> </template>
</el-table-column> </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"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="460">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
@ -439,6 +443,17 @@ export default {
this.getList(); this.getList();
}, },
methods: { 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() { loadDeptTree() {
listDept().then(res => { listDept().then(res => {
this.deptOptions = this.handleTree(res.data, "deptId"); this.deptOptions = this.handleTree(res.data, "deptId");

View File

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

View File

@ -35,7 +35,7 @@
</el-table-column> </el-table-column>
<el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" /> <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="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="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" /> <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
<el-table-column label="浏览器" align="center" prop="browser" /> <el-table-column label="浏览器" align="center" prop="browser" />

View File

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

View File

@ -34,8 +34,12 @@
<span>{{ typeLabel(s.row.type) }}</span> <span>{{ typeLabel(s.row.type) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="变更积分" prop="changeAmount" width="100" /> <el-table-column label="变更积分" prop="changeAmount" width="120" align="right">
<el-table-column label="变更后积分" prop="resultAmount" width="100" /> <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-column label="时间" width="160"><template slot-scope="s">{{ parseTime(s.row.createTime) }}</template></el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> <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() }, created() { this.getList() },
methods: { 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) { typeLabel(type) {
const n = type !== undefined && type !== null ? Number(type) : null const n = type !== undefined && type !== null ? Number(type) : null
const hit = this.typeOptions.find(item => item.value === n) const hit = this.typeOptions.find(item => item.value === n)

View File

@ -18,7 +18,7 @@
</el-row> </el-row>
<el-row :gutter="16" style="margin-top:16px"> <el-row :gutter="16" style="margin-top:16px">
<el-col :span="8"> <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>
<el-col :span="8"> <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> <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() { created() {
this.load() 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: { methods: {
load() { load() {
this.loading = true this.loading = true

View File

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

View File

@ -50,7 +50,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="balance" label="剩余积分" width="120" align="right"> <el-table-column prop="balance" label="剩余积分" width="120" align="right">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.balance != null ? scope.row.balance : '—' }}</span> <span>{{ formatDeptBalance(scope.row.balance) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" label="状态" width="100"> <el-table-column prop="status" label="状态" width="100">
@ -177,14 +177,14 @@
:max="999999" :max="999999"
:precision="0" :precision="0"
controls-position="right" controls-position="right"
placeholder="0=不限制" placeholder="0 表示不限制"
style="width: 100%" style="width: 100%"
:disabled="isFirstLevelEditForm" :disabled="isFirstLevelEditForm"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <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-col>
</el-row> </el-row>
</el-form> </el-form>
@ -322,15 +322,12 @@
> >
<el-form ref="editScoreFormRef" :model="editScoreForm" :rules="editScoreRules" label-width="88px"> <el-form ref="editScoreFormRef" :model="editScoreForm" :rules="editScoreRules" label-width="88px">
<el-form-item label="积分" prop="score"> <el-form-item label="积分" prop="score">
<el-input-number <el-input
v-model="editScoreForm.score" :value="editScoreScoreDisplay"
:precision="0" class="edit-score-input"
:step="1" clearable
:min="-100000000" placeholder="正数增加负数扣减不能为0如 9,999,999 或 -1,000,000"
:max="100000000" @input="onEditScoreInput"
controls-position="right"
class="edit-score-input-number"
placeholder="正数增加负数扣减不能为0"
/> />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
@ -380,7 +377,7 @@
color: #e6a23c; color: #e6a23c;
font-size: 12px; font-size: 12px;
} }
.edit-score-input-number { .edit-score-input {
width: 100%; width: 100%;
} }
</style> </style>
@ -395,7 +392,10 @@ import {
formatMoneyWesternFinal, formatMoneyWesternFinal,
sanitizeIntDigits, sanitizeIntDigits,
formatIntWesternDisplay, formatIntWesternDisplay,
intStringToNumber intStringToNumber,
sanitizeSignedIntDigits,
formatSignedIntDisplay,
signedIntStringToNumber
} from "@/utils/westernNumberFormat" } from "@/utils/westernNumberFormat"
import Treeselect from "@riophae/vue-treeselect" import Treeselect from "@riophae/vue-treeselect"
import "@riophae/vue-treeselect/dist/vue-treeselect.css" import "@riophae/vue-treeselect/dist/vue-treeselect.css"
@ -454,6 +454,7 @@ export default {
] ]
}, },
editScoreOpen: false, editScoreOpen: false,
editScoreScoreDisplay: "",
editScoreForm: { editScoreForm: {
deptId: undefined, deptId: undefined,
deptName: "", deptName: "",
@ -531,6 +532,16 @@ export default {
message: "请输入正确的手机号码", message: "请输入正确的手机号码",
trigger: "blur" 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: { 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) { isFirstLevelRow(row) {
if (!row) { if (!row) {
return false return false
@ -711,6 +738,7 @@ export default {
}) })
}, },
resetEditScore() { resetEditScore() {
this.editScoreScoreDisplay = ""
this.editScoreForm = { this.editScoreForm = {
deptId: undefined, deptId: undefined,
deptName: "", deptName: "",
@ -727,6 +755,7 @@ export default {
if (!this.isFirstLevelRow(row) && !this.isSecondLevelRow(row)) { if (!this.isFirstLevelRow(row) && !this.isSecondLevelRow(row)) {
return return
} }
this.editScoreScoreDisplay = ""
this.editScoreForm = { this.editScoreForm = {
deptId: row.deptId, deptId: row.deptId,
deptName: row.deptName, deptName: row.deptName,
@ -807,7 +836,7 @@ export default {
leader: undefined, leader: undefined,
phone: undefined, phone: undefined,
email: undefined, email: undefined,
maxUserCount: undefined, maxUserCount: 0,
status: "0" status: "0"
} }
this.originalForm = {} this.originalForm = {}
@ -832,6 +861,7 @@ export default {
} }
this.form.parentId = row.deptId this.form.parentId = row.deptId
} }
this.form.maxUserCount = 0
this.open = true this.open = true
this.title = "添加二级团队" this.title = "添加二级团队"
listDept().then(response => { listDept().then(response => {
@ -850,8 +880,11 @@ export default {
handleUpdate(row) { handleUpdate(row) {
this.reset() this.reset()
getDept(row.deptId).then(response => { getDept(row.deptId).then(response => {
this.form = response.data this.form = { ...response.data }
this.originalForm = { ...response.data } this.originalForm = { ...response.data }
if (this.form.maxUserCount == null) {
this.form.maxUserCount = 0
}
this.open = true this.open = true
this.title = "修改团队" this.title = "修改团队"
listDeptExcludeChild(row.deptId).then(response => { listDeptExcludeChild(row.deptId).then(response => {

View File

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

View File

@ -6,7 +6,7 @@
<pane size="16"> <pane size="16">
<el-col> <el-col>
<div class="head-container"> <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>
<div class="head-container"> <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" /> <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="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="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="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="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
<el-table-column label="状态" align="center" key="status" v-if="columns[5].visible"> <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
<template slot-scope="scope"> <template slot-scope="scope">
@ -236,7 +236,7 @@ export default {
enabledDeptOptions: undefined, enabledDeptOptions: undefined,
// //
open: false, open: false,
// //
deptName: undefined, deptName: undefined,
// //
initPassword: undefined, initPassword: undefined,

View File

@ -24,7 +24,7 @@
<div class="pull-right">{{ user.email }}</div> <div class="pull-right">{{ user.email }}</div>
</li> </li>
<li class="list-group-item"> <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> <div class="pull-right" v-if="user.dept">{{ user.dept.deptName }} / {{ postGroup }}</div>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package com.ruoyi.web.controller.ai; package com.ruoyi.web.controller.ai;
import com.ruoyi.ai.domain.AiBalanceChangeRecord; import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import com.ruoyi.ai.domain.vo.AiBalanceChangeRecordListVo;
import com.ruoyi.ai.service.IAiBalanceChangeRecordService; import com.ruoyi.ai.service.IAiBalanceChangeRecordService;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 余额使用记录Controller * 余额使用记录Controller
@ -37,7 +39,10 @@ public class AiBalanceChangeRecordController extends BaseController
{ {
startPage(); startPage();
List<AiBalanceChangeRecord> list = aiBalanceChangeRecordService.selectAiBalanceChangeRecordList(aiBalanceChangeRecord); 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) public void export(HttpServletResponse response, AiBalanceChangeRecord aiBalanceChangeRecord)
{ {
List<AiBalanceChangeRecord> list = aiBalanceChangeRecordService.selectAiBalanceChangeRecordList(aiBalanceChangeRecord); List<AiBalanceChangeRecord> list = aiBalanceChangeRecordService.selectAiBalanceChangeRecordList(aiBalanceChangeRecord);
ExcelUtil<AiBalanceChangeRecord> util = new ExcelUtil<AiBalanceChangeRecord>(AiBalanceChangeRecord.class); List<AiBalanceChangeRecordListVo> rows = list.stream().map(AiBalanceChangeRecordListVo::from).collect(Collectors.toList());
util.exportExcel(response, list, "余额使用记录数据"); 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')") @PreAuthorize("@ss.hasPermi('ai:dept:remove')")
@Log(title = "AI部门管理", businessType = BusinessType.DELETE) @Log(title = "AI团队管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{deptId}") @DeleteMapping("/{deptId}")
public AjaxResult remove(@PathVariable Long deptId) public AjaxResult remove(@PathVariable Long deptId)
{ {
if (deptService.hasChildByDeptId(deptId)) if (deptService.hasChildByDeptId(deptId))
{ {
return warn("存在下级部门,不允许删除"); return warn("存在下级团队,不允许删除");
} }
if (deptService.checkDeptExistUser(deptId)) if (deptService.checkDeptExistUser(deptId))
{ {
return warn("部门存在系统用户,不允许删除"); return warn("团队存在系统用户,不允许删除");
} }
if (aiUserService.countAiUserByDeptId(deptId) > 0) if (aiUserService.countAiUserByDeptId(deptId) > 0)
{ {
return warn("部门存在 AI 用户,不允许删除"); return warn("团队存在 AI 用户,不允许删除");
} }
deptService.checkDeptDataScope(deptId); deptService.checkDeptDataScope(deptId);
return toAjax(deptService.deleteDeptById(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')") @PreAuthorize("@ss.hasPermi('ai:user:deptScoreReclaim')")
@Log(title = "AI用户部门积分回收", businessType = BusinessType.UPDATE) @Log(title = "AI用户团队积分回收", businessType = BusinessType.UPDATE)
@PutMapping("/dept-score/reclaim") @PutMapping("/dept-score/reclaim")
public AjaxResult reclaimDeptScore(@Valid @RequestBody AiUserDeptScoreRequest request) { public AjaxResult reclaimDeptScore(@Valid @RequestBody AiUserDeptScoreRequest request) {
deptUserScoreTransferService.reclaimDeptScore(request); deptUserScoreTransferService.reclaimDeptScore(request);

View File

@ -1,15 +1,14 @@
package com.ruoyi.ai.domain; package com.ruoyi.ai.domain;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal;
/** /**
* 余额使用记录对象 ai_balance_change_record * 余额使用记录对象 ai_balance_change_record
* *
@ -36,7 +35,6 @@ public class AiBalanceChangeRecord extends BaseEntity {
/** /**
* 关联订单号 * 关联订单号
*/ */
@Excel(name = "关联订单号")
private String orderNo; private String orderNo;
/** /**
@ -45,34 +43,30 @@ public class AiBalanceChangeRecord extends BaseEntity {
private Long userId; private Long userId;
/** /**
* 用户ID * 用户对外标识查询结果来自关联 ai_user.user_id
*/ */
@TableField(exist = false) @TableField(exist = false)
private String uuid; private String uuid;
/** /**
* 操作类型0-充值 1-返佣 2-充值赠送 3-体验金赠送 4-体验金回收 5-图生图 6-一键换脸 7-快捷生图 8-快捷生视频 9-退款 10-系统操作 12-部门下放积分 13-回收至部门 * 操作类型
*/ */
@Excel(name = "操作类型")
private Integer type; private Integer type;
/** /**
* 变更积分 * 变更积分
*/ */
@Excel(name = "变更积分")
private BigDecimal changeAmount; private BigDecimal changeAmount;
/** /**
* 变更后金额 * 变更后积分
*/ */
@Excel(name = "变更后积分")
private BigDecimal resultAmount; private BigDecimal resultAmount;
/** /**
* 用户昵称 * 用户昵称关联查询
*/ */
@TableField(exist = false) @TableField(exist = false)
@Excel(name = "用户昵称")
private String nickname; 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实时 */ /** AI 用户数量ai_user实时 */
private Integer aiUserCount; private Integer aiUserCount;
/** AI 账号上限,与部门 max_user_count 一致null 或小于等于 0 表示不限制 */
private Integer aiUserMaxCount;
/** 近七日消耗积分ai_video_report_data可能来自缓存 */ /** 近七日消耗积分ai_video_report_data可能来自缓存 */
private BigDecimal last7DaysConsumeScore; private BigDecimal last7DaysConsumeScore;

View File

@ -49,6 +49,7 @@ public class SubteamOverviewServiceImpl implements ISubteamOverviewService {
vo.setDeptName(dept != null ? dept.getDeptName() : ""); vo.setDeptName(dept != null ? dept.getDeptName() : "");
vo.setBalance(dept != null && dept.getBalance() != null ? dept.getBalance() : BigDecimal.ZERO); vo.setBalance(dept != null && dept.getBalance() != null ? dept.getBalance() : BigDecimal.ZERO);
vo.setAiUserCount(aiUserService.countAiUserByDeptId(deptId)); vo.setAiUserCount(aiUserService.countAiUserByDeptId(deptId));
vo.setAiUserMaxCount(dept != null ? dept.getMaxUserCount() : null);
LocalDate end = LocalDate.now(); LocalDate end = LocalDate.now();
LocalDate start = end.minusDays(6); 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="updateBy" column="update_by" />
<result property="updateTime" column="update_time" /> <result property="updateTime" column="update_time" />
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<result property="orderNo" column="order_no" />
<result property="userId" column="user_id" /> <result property="userId" column="user_id" />
<result property="type" column="type" /> <result property="type" column="type" />
<result property="changeAmount" column="change_amount" /> <result property="changeAmount" column="change_amount" />
@ -21,7 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectAiBalanceChangeRecordVo"> <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 left join ai_user u on u.id = r.user_id
</sql> </sql>