Merge branch 'seedance_balance' of https://gitea.06zk.com/best_yunwei/ai_images into seedance_balance

# Conflicts:
#	web-api/ruoyi-system/src/main/java/com/ruoyi/ai/mapper/AiVideoReportDataMapper.java
This commit is contained in:
yys 2026-04-21 09:50:29 +08:00
commit b2f1bc8439
73 changed files with 4589 additions and 1318 deletions

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询团队(部门)对应火山引擎配置列表
export function listConfig(query) {
return request({
url: '/ai/config/list',
method: 'get',
params: query
})
}
// 查询团队(部门)对应火山引擎配置详细
export function getConfig(id) {
return request({
url: '/ai/config/' + id,
method: 'get'
})
}
// 新增团队(部门)对应火山引擎配置
export function addConfig(data) {
return request({
url: '/ai/config',
method: 'post',
data: data
})
}
// 修改团队(部门)对应火山引擎配置
export function updateConfig(data) {
return request({
url: '/ai/config',
method: 'put',
data: data
})
}
// 删除团队(部门)对应火山引擎配置
export function delConfig(id) {
return request({
url: '/ai/config/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1,10 @@
import request from '@/utils/request'
// 查询团队每日消耗统计
export function listData(query) {
return request({
url: '/ai/data/list',
method: 'get',
params: query
})
}

View File

@ -1,6 +1,6 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询订单管理列表 // 查询团队(部门)充值退款订单列表
export function listOrder(query) { export function listOrder(query) {
return request({ return request({
url: '/ai/order/list', url: '/ai/order/list',
@ -9,7 +9,7 @@ export function listOrder(query) {
}) })
} }
// 查询订单管理详细 // 查询团队(部门)充值退款订单详细
export function getOrder(id) { export function getOrder(id) {
return request({ return request({
url: '/ai/order/' + id, url: '/ai/order/' + id,
@ -17,7 +17,7 @@ export function getOrder(id) {
}) })
} }
// 新增订单管理 // 新增团队(部门)充值退款订单
export function addOrder(data) { export function addOrder(data) {
return request({ return request({
url: '/ai/order', url: '/ai/order',
@ -26,7 +26,7 @@ export function addOrder(data) {
}) })
} }
// 修改订单管理 // 修改团队(部门)充值退款订单
export function updateOrder(data) { export function updateOrder(data) {
return request({ return request({
url: '/ai/order', url: '/ai/order',
@ -35,29 +35,10 @@ export function updateOrder(data) {
}) })
} }
export function changeIsTop(id, isTop) { // 删除团队(部门)充值退款订单
const data = {
id,
isTop
}
return request({
url: '/ai/order',
method: 'put',
data: data
})
}
// 删除订单管理
export function delOrder(id) { export function delOrder(id) {
return request({ return request({
url: '/ai/order/' + id, url: '/ai/order/' + id,
method: 'delete' method: 'delete'
}) })
} }
export function downloadVideo(id) {
return request({
url: '/api/ai/' + id,
method: 'get'
})
}

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询团队(部门)余额变动列表
export function listRecord(query) {
return request({
url: '/ai/record/list',
method: 'get',
params: query
})
}
// 查询团队(部门)余额变动详细
export function getRecord(id) {
return request({
url: '/ai/record/' + id,
method: 'get'
})
}
// 新增团队(部门)余额变动
export function addRecord(data) {
return request({
url: '/ai/record',
method: 'post',
data: data
})
}
// 修改团队(部门)余额变动
export function updateRecord(data) {
return request({
url: '/ai/record',
method: 'put',
data: data
})
}
// 删除团队(部门)余额变动
export function delRecord(id) {
return request({
url: '/ai/record/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1,70 @@
import request from '@/utils/request'
export function getSubteamOverview() {
return request({ url: '/subteam/overview', method: 'get' })
}
export function listSubteamUser(query) {
return request({ url: '/subteam/user/list', method: 'get', params: query })
}
export function getSubteamUser(id) {
const url = id != null && id !== '' ? '/subteam/user/' + id : '/subteam/user'
return request({ url, method: 'get' })
}
export function addSubteamUser(data) {
return request({ url: '/subteam/user', method: 'post', data })
}
export function updateSubteamUser(data) {
return request({ url: '/subteam/user', method: 'put', data })
}
export function delSubteamUser(userIds) {
return request({ url: '/subteam/user/' + userIds, method: 'delete' })
}
export function resetSubteamUserPwd(data) {
return request({ url: '/subteam/user/resetPwd', method: 'put', data })
}
export function changeSubteamUserStatus(data) {
return request({ url: '/subteam/user/changeStatus', method: 'put', data })
}
export function listSubteamVideoOrder(query) {
return request({ url: '/subteam/video-order/list', method: 'get', params: query })
}
export function getSubteamVideoOrder(id) {
return request({ url: '/subteam/video-order/' + id, method: 'get' })
}
export function listSubteamChargeOrder(query) {
return request({ url: '/subteam/charge-order/list', method: 'get', params: query })
}
export function getSubteamChargeOrder(id) {
return request({ url: '/subteam/charge-order/' + id, method: 'get' })
}
export function listSubteamUserBalance(query) {
return request({ url: '/subteam/user-balance/list', method: 'get', params: query })
}
export function getSubteamUserBalance(id) {
return request({ url: '/subteam/user-balance/' + id, method: 'get' })
}
export function listSubteamConsumeStat(query) {
return request({ url: '/subteam/consume-stat/list', method: 'get', params: query })
}
export function listSubteamGroupBalance(query) {
return request({ url: '/subteam/group-balance/list', method: 'get', params: query })
}
export function getSubteamGroupBalance(id) {
return request({ url: '/subteam/group-balance/' + id, method: 'get' })
}

View File

@ -0,0 +1,270 @@
<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-input
v-model="queryParams.deptId"
placeholder="请输入部门ID"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<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
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ai:config:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['ai:config: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:config:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ai:config:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<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="视频模型列表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" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ai:config:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ai:config:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改团队部门对应火山引擎配置对话框 -->
<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>
<el-form-item label="视频模型列表JSON(label+value)" prop="modelParm">
<el-input v-model="form.modelParm" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="Byte project加密" prop="project">
<el-input v-model="form.project" placeholder="请输入Byte project加密" />
</el-form-item>
<el-form-item label="Byte API Key加密" prop="byteApiKey">
<el-input v-model="form.byteApiKey" placeholder="请输入Byte API Key加密" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listConfig, getConfig, delConfig, addConfig, updateConfig } from "@/api/ai/config"
export default {
name: "Config",
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
configList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
deptId: null,
modelParm: null,
project: null,
byteApiKey: null,
},
//
form: {},
//
rules: {
deptId: [
{ required: true, message: "部门ID不能为空", trigger: "blur" }
],
}
}
},
created() {
this.getList()
},
methods: {
/** 查询团队(部门)对应火山引擎配置列表 */
getList() {
this.loading = true
listConfig(this.queryParams).then(response => {
this.configList = response.rows
this.total = response.total
this.loading = false
})
},
//
cancel() {
this.open = false
this.reset()
},
//
reset() {
this.form = {
id: null,
deptId: null,
modelParm: null,
project: null,
byteApiKey: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null
}
this.resetForm("form")
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = "添加团队(部门)对应火山引擎配置"
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const id = row.id || this.ids
getConfig(id).then(response => {
this.form = response.data
this.open = true
this.title = "修改团队(部门)对应火山引擎配置"
})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateConfig(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
} else {
addConfig(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
})
}
}
})
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids
this.$modal.confirm('是否确认删除团队(部门)对应火山引擎配置编号为"' + ids + '"的数据项?').then(function() {
return delConfig(ids)
}).then(() => {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
},
/** 导出按钮操作 */
handleExport() {
this.download('ai/config/export', {
...this.queryParams
}, `config_${new Date().getTime()}.xlsx`)
}
}
}
</script>

View File

@ -0,0 +1,141 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="96px">
<el-form-item label="日期" prop="statDate">
<el-date-picker
v-model="queryParams.statDate"
type="date"
value-format="yyyyMMdd"
placeholder="请选择日期"
clearable
/>
</el-form-item>
<el-form-item label="团队名称" prop="deptId">
<el-select
v-model="queryParams.deptId"
placeholder="请选择团队"
clearable
filterable
style="width: 220px"
>
<el-option
v-for="item in deptOptions"
:key="item.deptId"
:label="item.deptName"
:value="item.deptId"
/>
</el-select>
</el-form-item>
<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">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="dataList">
<el-table-column label="日期" align="center" prop="dateKey" />
<el-table-column label="团队名称" align="center" prop="deptName" />
<el-table-column label="实际充值积分(充值-退款)" align="center" prop="rechargeScore" />
<el-table-column label="消耗积分" align="center" prop="score" />
<el-table-column label="实际订单数量(成功)" align="center" prop="orderCount" />
<el-table-column label="三方消耗tokens数量" align="center" prop="useTokens" />
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listData } from "@/api/ai/data"
import { listDept } from "@/api/ai/dept"
export default {
name: "TeamConsumeData",
data() {
return {
//
loading: false,
//
showSearch: true,
//
total: 0,
//
dataList: [],
//
deptOptions: [],
//
searched: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
statDate: null,
deptId: null
},
}
},
created() {
this.loadSecondLevelDeptOptions()
},
methods: {
/** 加载二级部门选项 */
loadSecondLevelDeptOptions() {
listDept({ status: "0" }).then(response => {
const allDeptList = response.data || []
this.deptOptions = allDeptList.filter(item => this.isSecondLevelDept(item))
})
},
/** 判断是否为二级部门(一级部门的直属子部门) */
isSecondLevelDept(dept) {
if (!dept || !dept.ancestors) {
return false
}
return String(dept.ancestors).split(",").filter(Boolean).length === 2
},
/** 查询团队每日消耗统计列表 */
getList() {
if (!this.searched) {
this.loading = false
this.dataList = []
this.total = 0
return
}
this.loading = true
listData(this.queryParams).then(response => {
this.dataList = response.rows
this.total = response.total
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
if (!this.queryParams.statDate || !this.queryParams.deptId) {
this.$modal.msgWarning("请先填写日期和团队名称后再搜索")
return
}
this.searched = true
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.searched = false
this.loading = false
this.dataList = []
this.total = 0
}
}
}
</script>

View File

@ -1,79 +1,59 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
:model="queryParams" <el-form-item label="团队名称" prop="deptName">
ref="queryForm"
size="small"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="订单编号" prop="orderNum">
<el-input <el-input
v-model="queryParams.orderNum" 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="用户ID" prop="uuid"> <el-form-item label="类型" prop="orderType">
<el-input <el-select v-model="queryParams.orderType" placeholder="全部" clearable style="width: 140px">
v-model="queryParams.uuid" <el-option v-for="item in orderTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
placeholder="请输入用户ID"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="操作类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择操作类型" clearable>
<el-option
v-for="dict in dict.type.ai_function_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="是否置顶" prop="isTop">
<el-select v-model="queryParams.isTop" placeholder="请选择" clearable>
<el-option
v-for="dict in dict.type.sys_yes_no"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
filterable
clearable
style="width: 200px; margin-bottom: 16px;"
>
<el-option
v-for="(label, value) in statusMap"
:key="value"
:label="label"
:value="Number(value)"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<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
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ai:order:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['ai:order: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:order:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button <el-button
type="warning" type="warning"
plain plain
@ -82,113 +62,45 @@
@click="handleExport" @click="handleExport"
v-hasPermi="['ai:order:export']" v-hasPermi="['ai:order:export']"
>导出</el-button> >导出</el-button>
</el-form-item> </el-col>
</el-form> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> </el-row>
<el-table v-loading="loading" :data="orderList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="orderList" @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" width="60" /> <el-table-column label="ID" align="center" prop="id" width="72" />
<!-- <el-table-column label="备注" align="center" prop="remark" /> --> <el-table-column label="团队名称" align="center" prop="deptName" min-width="120" show-overflow-tooltip />
<el-table-column label="订单编号" align="center" prop="orderNum" width="150"/> <el-table-column label="类型" align="center" width="100">
<el-table-column label="用户ID" align="center" prop="uuid" width="100" />
<el-table-column label="操作类型" align="center" prop="type" width="90" >
<template slot-scope="scope"> <template slot-scope="scope">
<dict-tag :options="dict.type.ai_function_type" :value="scope.row.type" /> <span>{{ orderTypeLabel(scope.row.orderType) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="金额" align="center" prop="amount" width="100" /> <el-table-column label="金额(元)" align="center" prop="money" width="100" />
<el-table-column label="生成结果" align="center" width="200"> <el-table-column label="积分" align="center" prop="amount" width="100" />
<el-table-column label="时间" align="center" prop="createTime" width="160">
<template slot-scope="scope"> <template slot-scope="scope">
<!-- 判断是否为链接 --> <span>{{ parseTime(scope.row.createTime) }}</span>
<template v-if="isUrl(scope.row.result)">
<!-- 图片链接 -->
<img
v-if="isImage(scope.row.result)"
:src="scope.row.result"
class="preview-img"
@click="viewImage(scope.row.result)"
alt="预览图片"
/>
<!-- 视频链接 -->
<video
v-else-if="isVideo(scope.row.result)"
:src="scope.row.result"
class="preview-video"
@click="playVideo(scope.row.result)"
/>
</template>
<!-- 视频任务失败等场景result VideoTaskError JSON表格内简短悬停看全文 -->
<template v-else-if="parseVolcTaskErrorJson(scope.row.result)">
<span
v-for="err in [parseVolcTaskErrorJson(scope.row.result)]"
:key="'oe-' + scope.row.id"
class="order-result-error-wrap"
>
<el-tooltip
placement="top-start"
effect="dark"
:open-delay="200"
popper-class="order-result-error-tooltip-popper"
>
<div slot="content" class="order-result-error-tooltip-body">
<div class="order-result-error-tooltip-row">
<span class="order-result-error-tooltip-k">错误编号</span>
<span class="order-result-error-tooltip-v">{{ err.code || '—' }}</span>
</div>
<div
v-if="volcTaskErrorHintForCode(err.code)"
class="order-result-error-tooltip-row"
>
<span class="order-result-error-tooltip-k">中文说明</span>
<span class="order-result-error-tooltip-v">{{
volcTaskErrorHintForCode(err.code)
}}</span>
</div>
<div class="order-result-error-tooltip-row order-result-error-tooltip-row--msg">
<span class="order-result-error-tooltip-k">信息</span>
<span class="order-result-error-tooltip-v">{{ err.message || '—' }}</span>
</div>
</div>
<span class="order-result-error-compact">{{ volcTaskErrorCellSummary(err) }}</span>
</el-tooltip>
</span>
</template>
<!-- 非链接内容如任务 id可点击尝试拉取视频 -->
<span v-else @click="handleOtherEvent(scope.row)">{{ scope.row.result }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="文字描述" align="center" prop="text"> <el-table-column label="备注" align="center" prop="remark" min-width="140" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
<template slot-scope="scope"> <template slot-scope="scope">
<!-- 核心 popover ref触发元素绑定 v-popover --> <el-button
<el-popover size="mini"
ref="promptPopover" type="text"
placement="top" icon="el-icon-edit"
width="400" @click="handleUpdate(scope.row)"
trigger="hover" v-hasPermi="['ai:order:edit']"
:content="scope.row.text || '无提示词'" >修改</el-button>
></el-popover> <el-button
<!-- 触发元素绑定 v-popover popover ref --> size="mini"
<div type="text"
class="text-ellipsis-two-lines" icon="el-icon-delete"
v-popover:promptPopover @click="handleDelete(scope.row)"
style="cursor: pointer;" v-hasPermi="['ai:order:remove']"
>{{ scope.row.text}}</div> >删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="置顶" align="center" key="status" width="60">
<template slot-scope="scope">
<el-switch
v-model="scope.row.isTop"
active-value="Y"
inactive-value="N"
@change="handleIsTopChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="150"/>
<el-table-column label="状态" align="center" prop="status" :formatter="formatStatus" width="70"/>
<el-table-column label="来源" align="center" prop="source" width="70"/>
</el-table> </el-table>
<pagination <pagination
@ -199,39 +111,40 @@
@pagination="getList" @pagination="getList"
/> />
<!-- 添加或修改订单管理对话框 --> <el-dialog :title="title" :visible.sync="open" width="560px" 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="100px">
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form-item label="团队" prop="deptId">
<el-form-item label="订单编号" prop="orderNum"> <treeselect
<el-input v-model="form.orderNum" placeholder="请输入订单编号" /> v-model="form.deptId"
:options="deptOptions"
:normalizer="normalizer"
:show-count="true"
placeholder="请选择团队(二级部门)"
/>
</el-form-item> </el-form-item>
<el-form-item label="用户ID" prop="userId"> <el-form-item label="类型" prop="orderType">
<el-input v-model="form.userId" placeholder="请输入用户ID" /> <el-select v-model="form.orderType" placeholder="请选择" style="width: 100%">
</el-form-item> <el-option v-for="item in orderTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-form-item label="状态" prop="status">
<el-select
v-model="form.status"
placeholder="请选择状态"
filterable
clearable
style="width: 200px;"
>
<el-option
v-for="(label, value) in statusMap"
:key="value"
:label="label"
:value="Number(value)"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="金额" prop="amount"> <el-form-item label="金额(元)" prop="money">
<el-input v-model="form.amount" placeholder="请输入金额" /> <el-input v-model="form.money" placeholder="可选,对账用金额" />
</el-form-item> </el-form-item>
<el-form-item label="生成结果" prop="result"> <el-form-item label="积分" prop="amount">
<el-input v-model="form.result" placeholder="请输入生成结果" /> <el-input v-model="form.amount" placeholder="充值/退款填正数;手动修改可填负数表示扣减" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择" style="width: 100%">
<el-option label="进行中" :value="0" />
<el-option label="已完成" :value="1" />
<el-option label="失败" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="订单编号" prop="orderNum" v-if="form.id != null">
<el-input v-model="form.orderNum" disabled />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="对账说明、打款信息等" maxlength="500" show-word-limit />
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
@ -239,424 +152,158 @@
<el-button @click="cancel"> </el-button> <el-button @click="cancel"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<!-- 图片预览弹窗 -->
<el-dialog :visible.sync="imageDialogVisible" title="图片预览" width="60%">
<img :src="previewImageUrl" class="dialog-img" alt="大图预览" />
</el-dialog>
<!-- 新增视频弹窗 -->
<el-dialog :visible.sync="videoDialogVisible" title="视频播放" width="60%">
<video :src="previewVideoUrl" controls class="dialog-video"></video>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { import { listOrder, getOrder, delOrder, addOrder, updateOrder } from "@/api/ai/order"
listOrder, import { deptTreeSelect } from "@/api/system/user"
getOrder, import Treeselect from "@riophae/vue-treeselect"
delOrder, import "@riophae/vue-treeselect/dist/vue-treeselect.css"
addOrder,
downloadVideo,
changeIsTop,
updateOrder
} from "@/api/ai/order";
export default { export default {
name: "Order", name: "TeamChargeOrder",
dicts: ["ai_function_type", "sys_yes_no"], components: { Treeselect },
data() { data() {
return { return {
imageDialogVisible: false,
videoDialogVisible: false,
previewVideoUrl: "",
previewImageUrl: "",
dateRange: [],
statusMap: {
0: "进行中",
1: "已完成",
2: "已失败"
},
//
loading: true, loading: true,
//
ids: [], ids: [],
//
single: true, single: true,
//
multiple: true, multiple: true,
//
showSearch: true, showSearch: true,
//
total: 0, total: 0,
//
orderList: [], orderList: [],
//
title: "", title: "",
//
open: false, open: false,
// deptOptions: [],
orderTypeOptions: [
{ label: "充值", value: 0 },
{ label: "退款", value: 1 },
{ label: "手动修改", value: 2 }
],
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
orderNum: null, deptName: null,
userId: null, orderType: null
type: null,
amount: null,
result: null,
status: null
}, },
//
form: {}, form: {},
//
rules: { rules: {
delFlag: [ deptId: [{ required: true, message: "请选择团队", trigger: "change" }],
{ required: true, message: "删除标志不能为空", trigger: "blur" } orderType: [{ required: true, message: "请选择类型", trigger: "change" }],
] amount: [{ required: true, message: "请填写积分", trigger: "blur" }],
status: [{ required: true, message: "请选择状态", trigger: "change" }]
} }
}; }
}, },
created() { created() {
this.getList(); this.getDeptTree()
this.getList()
}, },
methods: { methods: {
handleIsTopChange(row) { orderTypeLabel(orderType) {
let text = row.isTop === "Y" ? "置顶" : "取消置顶"; const n = orderType !== undefined && orderType !== null ? Number(orderType) : null
this.$modal const hit = this.orderTypeOptions.find(o => o.value === n)
.confirm('确认要"' + text + '""' + row.id + '"作品吗?') return hit ? hit.label : "—"
.then(function() {
return changeIsTop(row.id, row.isTop);
})
.then(() => {
this.$modal.msgSuccess(text + "成功");
})
.catch(function() {
row.isTop = row.isTop === "Y" ? "N" : "Y";
});
}, },
// http(s) URL portal-ui getDeptTree() {
isUrl(str) { deptTreeSelect().then(response => {
const value = String(str || "").trim(); this.deptOptions = response.data || []
return /^https?:\/\//i.test(value); })
}, },
/** 已知错误码对应中文说明(与门户 VideoGen 一致) */ normalizer(node) {
volcTaskErrorHintForCode(code) { if (node.children && !node.children.length) {
const c = String(code || "").trim(); delete node.children
const hints = {
OutputVideoSensitiveContentDetected: "输出视频可能包含敏感信息",
InvalidParameter: "请求参数无效"
};
return hints[c] || "";
},
/** 表格内摘要:已知码只显示中文,未知码显示 code */
volcTaskErrorCellSummary(err) {
if (!err) return "—";
const code = String(err.code || "").trim();
const hint = this.volcTaskErrorHintForCode(code);
if (hint) return hint;
if (code) return code;
const msg = String(err.message || "").trim();
return msg ? msg.slice(0, 32) + (msg.length > 32 ? "…" : "") : "—";
},
/** 已知火山错误码后附中文说明(括号),导出等场景可用 */
volcFailureCodeWithHint(code) {
const c = String(code || "").trim();
if (!c) return "";
const hint = this.volcTaskErrorHintForCode(c);
return hint ? `${c}${hint}` : c;
},
/** result 为火山回调失败写入的 VideoTaskError JSON 时解析为 { code, message } */
parseVolcTaskErrorJson(str) {
const s = String(str || "").trim();
if (!s || s[0] !== "{") return null;
try {
const o = JSON.parse(s);
if (
o &&
typeof o === "object" &&
!Array.isArray(o) &&
("code" in o || "message" in o)
) {
return {
code: o.code != null ? String(o.code) : "",
message: o.message != null ? String(o.message) : ""
};
}
} catch (_) {
/* ignore */
} }
return null; return {
}, id: node.id,
// portal-ui GeneratedAssets label: node.label,
isImage(url) { children: node.children
const value = String(url || "").trim();
if (!value) return false;
return /\.(jpeg|jpg|png|gif|webp|bmp)(\?.*)?$/i.test(value);
},
// portal-ui VideoGen/GeneratedAssets
isVideo(url) {
const value = String(url || "").trim();
if (!value) return false;
return /\.(mp4|mov|webm|ogg|m4v|avi|mkv)(\?.*)?$/i.test(value);
},
//
viewImage(url) {
this.previewImageUrl = url;
this.imageDialogVisible = true;
},
//
playVideo(url) {
this.previewVideoUrl = url;
this.videoDialogVisible = true;
},
//
async handleOtherEvent(row) {
if (this.parseVolcTaskErrorJson(row.result)) return;
//
if (row.isDownloading) return;
const originalResult = row.result;
try {
//
row.isDownloading = true;
//
// ...
row.result = "下载中...";
//
const resultId = originalResult; // resultID
var res = await downloadVideo(resultId);
if (res.status === "succeeded") {
row.result = res.content.video_url;
} else {
row.result = "视频生成中"
}
} catch (error) {
console.error('下载失败:', error);
//
row.result = originalResult || row.result.replace("下载中...", "");
this.$message.error('下载失败,请重试');
} finally {
//
row.isDownloading = false;
} }
}, },
formatStatus(row) {
// ""
return this.statusMap[row.status] || "未知";
},
/** 查询订单管理列表 */
getList() { getList() {
this.loading = true; this.loading = true
listOrder(this.addDateRange(this.queryParams, this.dateRange)).then( listOrder(this.queryParams).then(response => {
response => { this.orderList = response.rows
this.orderList = response.rows; this.total = response.total
this.total = response.total; this.loading = false
this.loading = false; })
}
);
}, },
//
cancel() { cancel() {
this.open = false; this.open = false
this.reset(); this.reset()
}, },
//
reset() { reset() {
this.form = { this.form = {
id: null, id: null,
delFlag: null, deptId: undefined,
createBy: null, orderType: 0,
createTime: null, money: undefined,
updateBy: null, amount: undefined,
updateTime: null, remark: undefined,
remark: null, status: 1,
orderNum: null, orderNum: undefined
userId: null, }
type: null, this.resetForm("form")
amount: null,
result: null,
status: null
};
this.resetForm("form");
}, },
/** 搜索按钮操作 */
handleQuery() { handleQuery() {
this.queryParams.pageNum = 1; this.queryParams.pageNum = 1
this.getList(); this.getList()
}, },
/** 重置按钮操作 */
resetQuery() { resetQuery() {
this.dateRange = []; this.resetForm("queryForm")
this.resetForm("queryForm"); this.handleQuery()
this.handleQuery();
}, },
//
handleSelectionChange(selection) { handleSelectionChange(selection) {
this.ids = selection.map(item => item.id); this.ids = selection.map(item => item.id)
this.single = selection.length !== 1; this.single = selection.length !== 1
this.multiple = !selection.length; this.multiple = !selection.length
}, },
/** 新增按钮操作 */
handleAdd() { handleAdd() {
this.reset(); this.reset()
this.open = true; this.open = true
this.title = "添加订单管理"; this.title = "新增团队订单"
}, },
/** 修改按钮操作 */
handleUpdate(row) { handleUpdate(row) {
this.reset(); this.reset()
const id = row.id || this.ids; const id = row.id || this.ids[0]
getOrder(id).then(response => { getOrder(id).then(response => {
this.form = response.data; this.form = response.data
this.open = true; this.open = true
this.title = "修改订单管理"; this.title = "修改团队订单"
}); })
}, },
/** 提交按钮 */
submitForm() { submitForm() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (!valid) {
if (this.form.id != null) { return
updateOrder(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addOrder(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
} }
}); const payload = { ...this.form }
if (payload.id == null) {
addOrder(payload).then(() => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
})
} else {
updateOrder(payload).then(() => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
}
})
}, },
/** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
const ids = row.id || this.ids; const ids = row.id || this.ids
this.$modal this.$modal.confirm("是否确认删除所选团队订单?").then(() => delOrder(ids)).then(() => {
.confirm('是否确认删除订单管理编号为"' + ids + '"的数据项?') this.getList()
.then(function() { this.$modal.msgSuccess("删除成功")
return delOrder(ids); }).catch(() => {})
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
})
.catch(() => {});
}, },
/** 导出按钮操作 */
handleExport() { handleExport() {
this.download( this.download("ai/order/export", { ...this.queryParams }, `team_orders_${new Date().getTime()}.xlsx`)
"ai/order/export",
{
...this.queryParams
},
`order_${new Date().getTime()}.xlsx`
);
} }
} }
}; }
</script> </script>
<style>
.dialog-video {
width: 100%;
height: auto;
}
.preview-img {
width: 80px;
height: 60px;
object-fit: cover;
cursor: pointer;
border-radius: 4px;
}
.preview-video {
width: 120px;
height: 80px;
border-radius: 4px;
}
.dialog-img {
width: 100%;
height: auto;
}
.order-result-error-wrap {
display: inline-block;
}
.order-result-error-compact {
display: inline-block;
max-width: 200px;
margin: 0 auto;
font-size: 12px;
line-height: 1.35;
color: #c45656;
cursor: help;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.order-result-error-tooltip-body {
max-width: 420px;
text-align: left;
line-height: 1.5;
font-size: 12px;
}
.order-result-error-tooltip-row {
margin-bottom: 6px;
}
.order-result-error-tooltip-row:last-child {
margin-bottom: 0;
}
.order-result-error-tooltip-row--msg .order-result-error-tooltip-v {
display: block;
margin-top: 2px;
white-space: pre-wrap;
word-break: break-word;
}
.order-result-error-tooltip-k {
font-weight: 600;
color: #fde2e2;
}
.order-result-error-tooltip-v {
color: #fff;
}
.text-ellipsis-two-lines {
/* 必须设置宽度(继承表格列宽,也可手动指定) */
width: 100%;
/* 核心属性:多行文本溢出隐藏 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2; /* 显示2行 */
line-clamp: 2; /* 标准属性,兼容现代浏览器 */
overflow: hidden;
/* 可选:调整行高,优化显示 */
line-height: 1.5;
max-height: 3em; /* 行高1.5 × 2行 = 3em防止高度溢出 */
/* 可选:文本对齐 */
text-align: center;
/* 解决scoped样式穿透如果需要 */
/deep/ & {
box-sizing: border-box;
padding: 0 4px;
}
}
</style>

View File

@ -0,0 +1,312 @@
<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="订单号" prop="relationOrderNo">
<el-input
v-model="queryParams.relationOrderNo"
placeholder="请输入关联(充值/退款)订单号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="部门ID" prop="deptId">
<el-input
v-model="queryParams.deptId"
placeholder="请输入部门ID"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="变更金额" prop="changeAmount">
<el-input
v-model="queryParams.changeAmount"
placeholder="请输入变更金额"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="变更后金额" prop="resultAmount">
<el-input
v-model="queryParams.resultAmount"
placeholder="请输入变更后金额"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<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
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ai:record:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['ai:record: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:record:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ai:record:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<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="订单号" align="center" prop="relationOrderNo" />
<el-table-column label="部门ID" align="center" prop="deptId" />
<el-table-column label="操作类型" align="center" prop="type" />
<el-table-column label="变更金额" align="center" prop="changeAmount" />
<el-table-column label="变更后金额" align="center" prop="resultAmount" />
<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
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ai:record:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ai:record:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改团队部门余额变动对话框 -->
<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="订单号" prop="relationOrderNo">
<el-input v-model="form.relationOrderNo" placeholder="请输入关联(充值/退款)订单号" />
</el-form-item>
<el-form-item label="部门ID" prop="deptId">
<el-input v-model="form.deptId" placeholder="请输入部门ID" />
</el-form-item>
<el-form-item label="变更金额" prop="changeAmount">
<el-input v-model="form.changeAmount" placeholder="请输入变更金额" />
</el-form-item>
<el-form-item label="变更后金额" prop="resultAmount">
<el-input v-model="form.resultAmount" placeholder="请输入变更后金额" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listRecord, getRecord, delRecord, addRecord, updateRecord } from "@/api/ai/record"
export default {
name: "Record",
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
recordList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
relationOrderNo: null,
deptId: null,
type: null,
changeAmount: null,
resultAmount: null,
},
//
form: {},
//
rules: {
deptId: [
{ required: true, message: "部门ID不能为空", trigger: "blur" }
],
type: [
{ required: true, message: "操作类型不能为空", trigger: "change" }
],
changeAmount: [
{ required: true, message: "变更金额不能为空", trigger: "blur" }
],
resultAmount: [
{ required: true, message: "变更后金额不能为空", trigger: "blur" }
],
createTime: [
{ required: true, message: "创建时间不能为空", trigger: "blur" }
],
}
}
},
created() {
this.getList()
},
methods: {
/** 查询团队(部门)余额变动列表 */
getList() {
this.loading = true
listRecord(this.queryParams).then(response => {
this.recordList = response.rows
this.total = response.total
this.loading = false
})
},
//
cancel() {
this.open = false
this.reset()
},
//
reset() {
this.form = {
id: null,
relationOrderNo: null,
deptId: null,
type: null,
changeAmount: null,
resultAmount: null,
remark: null,
createTime: null,
updateTime: null
}
this.resetForm("form")
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = "添加团队(部门)余额变动"
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const id = row.id || this.ids
getRecord(id).then(response => {
this.form = response.data
this.open = true
this.title = "修改团队(部门)余额变动"
})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateRecord(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
})
} else {
addRecord(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
})
}
}
})
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids
this.$modal.confirm('是否确认删除团队(部门)余额变动编号为"' + ids + '"的数据项?').then(function() {
return delRecord(ids)
}).then(() => {
this.getList()
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
},
/** 导出按钮操作 */
handleExport() {
this.download('ai/record/export', {
...this.queryParams
}, `record_${new Date().getTime()}.xlsx`)
}
}
}
</script>

View File

@ -0,0 +1,64 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="88px">
<el-form-item label="类型" prop="orderType">
<el-select v-model="queryParams.orderType" clearable placeholder="全部">
<el-option label="充值" :value="0" /><el-option label="退款" :value="1" /><el-option label="手动修改" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="订单编号" prop="orderNum">
<el-input v-model="queryParams.orderNum" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<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 class="mb8"><right-toolbar :show-search.sync="showSearch" @queryTable="getList" /></el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="ID" prop="id" width="70" />
<el-table-column label="团队" prop="deptName" min-width="100" show-overflow-tooltip />
<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="时间" 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>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
</template>
<script>
import { listSubteamChargeOrder } from '@/api/subteam'
export default {
name: 'SubteamChargeOrder',
data() {
return {
loading: true,
showSearch: true,
list: [],
total: 0,
queryParams: { pageNum: 1, pageSize: 10, orderType: undefined, orderNum: undefined }
}
},
created() { this.getList() },
methods: {
typeLabel(t) {
const m = { 0: '充值', 1: '退款', 2: '手动修改' }
return m[t] != null ? m[t] : '—'
},
getList() {
this.loading = true
listSubteamChargeOrder(this.queryParams).then(res => {
this.list = res.rows
this.total = res.total
this.loading = false
})
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
resetQuery() { this.resetForm('queryForm'); this.handleQuery() }
}
}
</script>

View File

@ -0,0 +1,66 @@
<template>
<div class="app-container">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="96px">
<el-form-item label="统计日期" prop="statDate">
<el-date-picker v-model="queryParams.statDate" type="date" value-format="yyyyMMdd" placeholder="请选择日期" clearable />
</el-form-item>
<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>
<p class="tip">仅展示当前团队数据请选择日期后查询</p>
<el-table v-loading="loading" :data="list">
<el-table-column label="日期" prop="dateKey" />
<el-table-column label="团队" prop="deptName" />
<el-table-column label="充值积分" prop="rechargeScore" />
<el-table-column label="消耗积分" prop="score" />
<el-table-column label="成功订单" prop="orderCount" />
<el-table-column label="Tokens" prop="useTokens" />
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
</template>
<script>
import { listSubteamConsumeStat } from '@/api/subteam'
export default {
name: 'SubteamConsumeStat',
data() {
return {
loading: false,
list: [],
total: 0,
queryParams: { pageNum: 1, pageSize: 10, statDate: null }
}
},
methods: {
getList() {
if (!this.queryParams.statDate) {
this.list = []
this.total = 0
return
}
this.loading = true
listSubteamConsumeStat(this.queryParams).then(res => {
this.list = res.rows
this.total = res.total
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.list = []
this.total = 0
}
}
}
</script>
<style scoped>
.tip { color: #909399; font-size: 13px; margin-bottom: 12px; }
</style>

View File

@ -0,0 +1,55 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true">
<el-form-item label="类型" prop="type">
<el-input v-model="queryParams.type" clearable placeholder="0-4" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="关联单号" prop="relationOrderNo">
<el-input v-model="queryParams.relationOrderNo" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<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 class="mb8"><right-toolbar :show-search.sync="showSearch" @queryTable="getList" /></el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="ID" prop="id" width="80" />
<el-table-column label="关联单号" prop="relationOrderNo" min-width="120" show-overflow-tooltip />
<el-table-column label="类型" prop="type" width="80" />
<el-table-column label="变更金额" prop="changeAmount" width="100" />
<el-table-column label="变更后" prop="resultAmount" width="100" />
<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" />
</div>
</template>
<script>
import { listSubteamGroupBalance } from '@/api/subteam'
export default {
name: 'SubteamGroupBalance',
data() {
return {
loading: true,
showSearch: true,
list: [],
total: 0,
queryParams: { pageNum: 1, pageSize: 10, type: undefined, relationOrderNo: undefined }
}
},
created() { this.getList() },
methods: {
getList() {
this.loading = true
listSubteamGroupBalance(this.queryParams).then(res => {
this.list = res.rows
this.total = res.total
this.loading = false
})
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
resetQuery() { this.resetForm('queryForm'); this.handleQuery() }
}
}
</script>

View File

@ -0,0 +1,58 @@
<template>
<div class="app-container">
<el-row :gutter="16" v-loading="loading">
<el-col :span="8">
<el-card shadow="hover"><div class="metric-title">团队 ID</div><div class="metric-val">{{ info.deptId }}</div></el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover"><div class="metric-title">团队名称</div><div class="metric-val">{{ info.deptName }}</div></el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover"><div class="metric-title">积分余额部门</div><div class="metric-val">{{ info.balance }}</div></el-card>
</el-col>
</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-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>
</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.last7DaysOrderCount }}</div></el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getSubteamOverview } from '@/api/subteam'
export default {
name: 'SubteamOverview',
data() {
return {
loading: true,
info: {}
}
},
created() {
this.load()
},
methods: {
load() {
this.loading = true
getSubteamOverview().then(res => {
this.info = res.data || {}
this.loading = false
})
}
}
}
</script>
<style scoped>
.metric-title { color: #909399; font-size: 13px; }
.metric-val { font-size: 22px; margin-top: 8px; font-weight: 600; }
.hint { font-size: 12px; color: #909399; }
</style>

View File

@ -0,0 +1,300 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true">
<el-form-item label="账号" prop="username">
<el-input v-model="queryParams.username" placeholder="账号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="queryParams.phone" placeholder="手机号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option label="正常" :value="0" />
<el-option label="停用" :value="1" />
</el-select>
</el-form-item>
<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 v-hasPermi="['subteam:user:add']" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['subteam:user:edit']" type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['subteam:user:remove']" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col>
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" />
<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="phone" width="120" />
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_normal_disable" :value="String(scope.row.status)" />
</template>
</el-table-column>
<el-table-column label="操作" width="320">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-download"
@click="openDeptScoreDialog(scope.row, 'issue')"
v-hasPermi="['ai:user:deptScoreIssue']"
>下放积分</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-upload2"
@click="openDeptScoreDialog(scope.row, 'reclaim')"
v-hasPermi="['ai:user:deptScoreReclaim']"
>回收积分</el-button>
<el-button v-hasPermi="['subteam:user:edit']" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button v-hasPermi="['subteam:user:resetPwd']" size="mini" type="text" icon="el-icon-key" @click="handleResetPwd(scope.row)">密码</el-button>
<el-button v-hasPermi="['subteam:user:remove']" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :visible.sync="open" width="640px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="88px">
<el-form-item label="昵称" prop="nickname"><el-input v-model="form.nickname" /></el-form-item>
<el-row v-if="!form.id">
<el-col :span="12"><el-form-item label="账号" prop="username"><el-input v-model="form.username" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="密码" prop="password"><el-input v-model="form.password" type="password" show-password /></el-form-item></el-col>
</el-row>
<el-form-item label="手机" prop="phone"><el-input v-model="form.phone" /></el-form-item>
<el-form-item label="邮箱" prop="email"><el-input v-model="form.email" /></el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="0">正常</el-radio>
<el-radio :label="1">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" /></el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<el-dialog
:title="deptScoreMode === 'issue' ? '下放积分' : '回收积分'"
:visible.sync="deptScoreOpen"
width="480px"
append-to-body
@close="cancelDeptScore"
>
<el-form ref="deptScoreFormRef" :model="deptScoreForm" :rules="deptScoreRules" label-width="88px">
<el-form-item label="用户账号">
<span>{{ deptScoreForm.username }}</span>
</el-form-item>
<el-form-item label="积分数量" prop="amount">
<el-input-number
v-model="deptScoreForm.amount"
:min="1"
:max="100000000"
:precision="0"
:step="1"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="deptScoreForm.remark"
type="textarea"
:rows="2"
maxlength="50"
show-word-limit
placeholder="选填最多50字"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitDeptScore"> </el-button>
<el-button @click="cancelDeptScore"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listSubteamUser, getSubteamUser, addSubteamUser, updateSubteamUser, delSubteamUser,
resetSubteamUserPwd
} from '@/api/subteam'
import { issueDeptScore, reclaimDeptScore } from '@/api/ai/user'
export default {
name: 'SubteamUser',
dicts: ['sys_normal_disable'],
data() {
return {
loading: true,
showSearch: true,
userList: [],
total: 0,
ids: [],
single: true,
multiple: true,
title: '',
open: false,
deptScoreOpen: false,
deptScoreMode: 'issue',
deptScoreForm: {
userId: null,
username: '',
amount: undefined,
remark: ''
},
deptScoreRules: {
amount: [
{ required: true, message: '请输入积分数量', trigger: 'blur' },
{
type: 'number',
min: 1,
max: 100000000,
message: '请输入 1100000000 的整数',
trigger: 'blur'
}
],
remark: [{ max: 50, message: '备注最多50个字', trigger: 'blur' }]
},
queryParams: { pageNum: 1, pageSize: 10, username: undefined, phone: undefined, status: undefined },
form: {},
rules: {
nickname: [{ required: true, message: '必填', trigger: 'blur' }],
username: [{ required: true, message: '必填', trigger: 'blur' }],
password: [{ required: true, message: '必填', trigger: 'blur' }],
email: [{ type: 'email', message: '邮箱格式', trigger: ['blur', 'change'] }]
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.loading = true
listSubteamUser(this.queryParams).then(res => {
this.userList = res.rows
this.total = res.total
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(rows) {
this.ids = rows.map(r => r.id)
this.single = rows.length !== 1
this.multiple = !rows.length
},
reset() {
this.form = { id: undefined, username: undefined, nickname: undefined, password: undefined,
phone: undefined, email: undefined, status: 0, remark: undefined }
this.resetForm('form')
},
handleAdd() {
this.reset()
this.open = true
this.title = '新增AI用户'
},
handleUpdate(row) {
this.reset()
const id = row.id || this.ids[0]
getSubteamUser(id).then(r => {
this.form = r.data || {}
this.form.status = Number(this.form.status || 0)
this.open = true
this.title = '修改AI用户'
})
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
if (this.form.id) {
updateSubteamUser(this.form).then(() => { this.$modal.msgSuccess('成功'); this.open = false; this.getList() })
} else {
addSubteamUser(this.form).then(() => { this.$modal.msgSuccess('成功'); this.open = false; this.getList() })
}
})
},
cancel() { this.open = false },
handleDelete(row) {
const userIds = row.id || this.ids
this.$modal.confirm('确认删除?').then(() => delSubteamUser(userIds)).then(() => { this.getList(); this.$modal.msgSuccess('已删除') }).catch(() => {})
},
handleResetPwd(row) {
this.$prompt('新密码', '重置', { inputType: 'password' }).then(({ value }) => {
resetSubteamUserPwd({ id: row.id, password: value }).then(() => this.$modal.msgSuccess('已重置'))
}).catch(() => {})
},
openDeptScoreDialog(row, mode) {
if (!row.deptId) {
this.$modal.msgWarning('请先分配归属部门后再操作积分')
return
}
this.deptScoreMode = mode
this.deptScoreForm = {
userId: row.id,
username: row.username || row.userId || '',
amount: undefined,
remark: ''
}
this.deptScoreOpen = true
this.$nextTick(() => {
if (this.$refs.deptScoreFormRef) {
this.$refs.deptScoreFormRef.clearValidate()
}
})
},
submitDeptScore() {
this.$refs.deptScoreFormRef.validate(valid => {
if (!valid) {
return
}
const raw = this.deptScoreForm.amount
const amount = typeof raw === 'number' ? Math.trunc(raw) : parseInt(String(raw), 10)
if (!Number.isFinite(amount) || amount < 1 || amount > 100000000) {
this.$modal.msgWarning('请输入 1100000000 的整数积分')
return
}
const payload = {
userId: this.deptScoreForm.userId,
amount,
remark: this.deptScoreForm.remark || undefined
}
const req = this.deptScoreMode === 'issue' ? issueDeptScore : reclaimDeptScore
req(payload).then(() => {
this.$modal.msgSuccess('操作成功')
this.deptScoreOpen = false
this.getList()
})
})
},
cancelDeptScore() {
this.deptScoreOpen = false
this.deptScoreForm = { userId: null, username: '', amount: undefined, remark: '' }
}
}
}
</script>

View File

@ -0,0 +1,56 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true">
<el-form-item label="用户ID" prop="userId">
<el-input v-model="queryParams.userId" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-input v-model="queryParams.type" clearable placeholder="类型码" @keyup.enter.native="handleQuery" />
</el-form-item>
<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 class="mb8"><right-toolbar :show-search.sync="showSearch" @queryTable="getList" /></el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="ID" prop="id" width="80" />
<el-table-column label="用户ID" prop="userId" width="90" />
<el-table-column label="账号" prop="nickname" min-width="100" />
<el-table-column label="类型" prop="type" width="80" />
<el-table-column label="变更" prop="changeAmount" width="100" />
<el-table-column label="余额" prop="resultAmount" width="100" />
<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" />
</div>
</template>
<script>
import { listSubteamUserBalance } from '@/api/subteam'
export default {
name: 'SubteamUserBalance',
data() {
return {
loading: true,
showSearch: true,
list: [],
total: 0,
queryParams: { pageNum: 1, pageSize: 10, userId: undefined, type: undefined }
}
},
created() { this.getList() },
methods: {
getList() {
this.loading = true
listSubteamUserBalance(this.queryParams).then(res => {
this.list = res.rows
this.total = res.total
this.loading = false
})
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
resetQuery() { this.resetForm('queryForm'); this.handleQuery() }
}
}
</script>

View File

@ -0,0 +1,138 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true">
<el-form-item label="订单号" prop="orderNum">
<el-input v-model="queryParams.orderNum" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="用户ID" prop="userId">
<el-input v-model="queryParams.userId" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" clearable placeholder="状态">
<el-option label="进行中" :value="0" /><el-option label="已完成" :value="1" /><el-option label="失败" :value="2" />
</el-select>
</el-form-item>
<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 class="mb8"><right-toolbar :show-search.sync="showSearch" @queryTable="getList" /></el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="ID" prop="id" width="70" />
<el-table-column label="订单号" prop="orderNum" min-width="120" show-overflow-tooltip />
<el-table-column label="用户ID" prop="userId" width="90" />
<el-table-column label="金额" prop="amount" width="90" />
<el-table-column label="状态" prop="status" width="80" />
<el-table-column label="结果预览" min-width="220">
<template slot-scope="s">
<div v-if="extractVideoUrl(s.row.result)">
<video
:src="extractVideoUrl(s.row.result)"
controls
preload="metadata"
style="width: 180px; max-height: 100px; border-radius: 4px;"
/>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="160"><template slot-scope="s">{{ parseTime(s.row.createTime) }}</template></el-table-column>
<el-table-column label="操作" width="90" fixed="right">
<template slot-scope="s">
<el-button type="text" size="mini" @click="handleDetail(s.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog title="订单详情" :visible.sync="detailVisible" width="720px" append-to-body>
<el-descriptions :column="1" border size="small">
<el-descriptions-item v-for="(value, key) in detailData" :key="key" :label="key">
<pre v-if="isObjectValue(value)" style="margin: 0; white-space: pre-wrap;">{{ formatJson(value) }}</pre>
<span v-else>{{ value === null || value === undefined || value === '' ? '-' : value }}</span>
</el-descriptions-item>
</el-descriptions>
<span slot="footer">
<el-button size="mini" @click="detailVisible = false">关闭</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listSubteamVideoOrder } from '@/api/subteam'
export default {
name: 'SubteamVideoOrder',
data() {
return {
loading: true,
showSearch: true,
list: [],
total: 0,
detailVisible: false,
detailData: {},
queryParams: { pageNum: 1, pageSize: 10, orderNum: undefined, userId: undefined, status: undefined }
}
},
created() { this.getList() },
methods: {
getList() {
this.loading = true
listSubteamVideoOrder(this.queryParams).then(res => {
this.list = res.rows
this.total = res.total
this.loading = false
})
},
extractVideoUrl(result) {
if (!result) return ''
if (typeof result === 'string') {
const value = result.trim()
if (!value) return ''
if (/^https?:\/\/|^\/\//.test(value)) return value
try {
const parsed = JSON.parse(value)
return this.extractVideoUrl(parsed)
} catch (e) {
return ''
}
}
if (Array.isArray(result)) {
for (let i = 0; i < result.length; i += 1) {
const url = this.extractVideoUrl(result[i])
if (url) return url
}
return ''
}
if (typeof result === 'object') {
const directKeys = ['videoUrl', 'url', 'resultUrl', 'playUrl', 'output', 'fileUrl']
for (let i = 0; i < directKeys.length; i += 1) {
const key = directKeys[i]
if (result[key]) {
const url = this.extractVideoUrl(result[key])
if (url) return url
}
}
}
return ''
},
handleDetail(row) {
this.detailData = row || {}
this.detailVisible = true
},
isObjectValue(value) {
return value !== null && typeof value === 'object'
},
formatJson(value) {
try {
return JSON.stringify(value, null, 2)
} catch (e) {
return String(value)
}
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
resetQuery() { this.resetForm('queryForm'); this.handleQuery() }
}
}
</script>

View File

@ -26,16 +26,6 @@
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:dept:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="info" type="info"
@ -57,10 +47,10 @@
: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="80"></el-table-column> <el-table-column prop="orderNum" label="排序" width="200"></el-table-column>
<el-table-column prop="balance" label="剩余积分" width="200" align="right"> <el-table-column prop="maxUserCount" label="账号上限" width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.balance != null ? scope.row.balance : '—' }}</span> <span>{{ scope.row.maxUserCount != null && scope.row.maxUserCount > 0 ? scope.row.maxUserCount : '不限制' }}</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">
@ -68,20 +58,13 @@
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="150"> <el-table-column label="创建时间" align="center" prop="createTime" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<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
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAdd(scope.row)"
v-hasPermi="['system:dept:add']"
>新增</el-button>
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@ -89,6 +72,14 @@
@click="handleUpdate(scope.row)" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dept:edit']" v-hasPermi="['system:dept:edit']"
>修改</el-button> >修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAdd(scope.row)"
v-if="isFirstLevelRow(scope.row)"
v-hasPermi="['system:dept:add']"
>新增</el-button>
<el-button <el-button
v-if="scope.row.parentId != 0" v-if="scope.row.parentId != 0"
size="mini" size="mini"
@ -97,20 +88,6 @@
@click="handleDelete(scope.row)" @click="handleDelete(scope.row)"
v-hasPermi="['system:dept:remove']" v-hasPermi="['system:dept:remove']"
>删除</el-button> >删除</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-wallet"
@click="handleChargeRefund(scope.row)"
v-hasPermi="['system:dept:chargeRefund']"
>充值/退款</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-s-operation"
@click="handleEditScore(scope.row)"
v-hasPermi="['system:dept:chargeRefund']"
>积分更正</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -133,31 +110,31 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="显示排序" prop="orderNum"> <el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" /> <el-input-number v-model="form.orderNum" controls-position="right" :min="0" :disabled="isFirstLevelEditForm" />
</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="leader"> <el-form-item label="负责人" prop="leader">
<el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" /> <el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" :disabled="isFirstLevelEditForm" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="联系电话" prop="phone"> <el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" /> <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" :disabled="isFirstLevelEditForm" />
</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="email"> <el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" :disabled="isFirstLevelEditForm" />
</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" :disabled="isFirstLevelEditForm">
<el-radio <el-radio
v-for="dict in dict.type.sys_normal_disable" v-for="dict in dict.type.sys_normal_disable"
:key="dict.value" :key="dict.value"
@ -167,6 +144,25 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row>
<el-col :span="12">
<el-form-item label="账号上限" prop="maxUserCount">
<el-input-number
v-model="form.maxUserCount"
:min="0"
:max="999999"
:precision="0"
controls-position="right"
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">限制本部门下启用状态账号数量0 或不填表示不限制</p>
</el-col>
</el-row>
<el-row v-if="isSecondLevelCompanyForm"> <el-row v-if="isSecondLevelCompanyForm">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="Byte API Key"> <el-form-item label="Byte API Key">
@ -230,95 +226,11 @@
</el-row> </el-row>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<span v-if="isFirstLevelEditForm" class="form-tip">一级部门仅允许修改名称</span>
<el-button type="primary" @click="submitForm"> </el-button> <el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button> <el-button @click="cancel"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<el-dialog
:title="'充值/退款 — ' + (chargeRefundForm.deptName || '')"
:visible.sync="chargeRefundOpen"
width="520px"
append-to-body
@close="resetChargeRefund"
>
<el-form ref="chargeRefundFormRef" :model="chargeRefundForm" :rules="chargeRefundRules" label-width="88px">
<el-form-item label="类型" prop="orderType">
<el-radio-group v-model="chargeRefundForm.orderType">
<el-radio :label="0">充值</el-radio>
<el-radio :label="1">退款</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="金额" prop="money">
<el-input
:value="chargeRefundMoneyDisplay"
placeholder="财务记录(元),如 9,999,999.00"
clearable
@input="onChargeRefundMoneyInput"
@blur="onChargeRefundMoneyBlur"
/>
</el-form-item>
<el-form-item label="积分" prop="amount">
<el-input
:value="chargeRefundAmountDisplay"
placeholder="变动积分,如 99,999,999"
clearable
@input="onChargeRefundAmountInput"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="chargeRefundForm.remark"
type="textarea"
:rows="2"
maxlength="50"
show-word-limit
placeholder="选填最多50字"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitChargeRefund"> </el-button>
<el-button @click="chargeRefundOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog
:title="'积分更正 — ' + (editScoreForm.deptName || '')"
:visible.sync="editScoreOpen"
width="520px"
append-to-body
@close="resetEditScore"
>
<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-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="editScoreForm.remark"
type="textarea"
:rows="2"
maxlength="50"
show-word-limit
placeholder="必填最多50字"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitEditScore"> </el-button>
<el-button @click="editScoreOpen = false"> </el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
@ -345,23 +257,15 @@
color: #909399; color: #909399;
line-height: 1.5; line-height: 1.5;
} }
.edit-score-input-number { .form-tip {
width: 100%; margin-right: 16px;
color: #e6a23c;
font-size: 12px;
} }
</style> </style>
<script> <script>
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild, chargeRefundDept, editScore } from "@/api/system/dept" import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
import {
WESTERN_MONEY_MAX,
sanitizeMoneyDigits,
formatMoneyWesternDisplay,
moneyStringToNumber,
formatMoneyWesternFinal,
sanitizeIntDigits,
formatIntWesternDisplay,
intStringToNumber
} 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"
@ -394,76 +298,8 @@ export default {
}, },
// //
form: {}, form: {},
originalForm: {},
modelParamRows: [{ label: '', value: '' }], modelParamRows: [{ label: '', value: '' }],
chargeRefundOpen: false,
chargeRefundMoneyDisplay: "",
chargeRefundAmountDisplay: "",
chargeRefundForm: {
deptId: undefined,
deptName: "",
orderType: 0,
money: undefined,
amount: undefined,
remark: ""
},
chargeRefundRules: {
orderType: [
{ required: true, message: "类型不能为空", trigger: "change" }
],
money: [
{ required: true, message: "金额不能为空", trigger: "blur" },
{ type: "number", min: 0, max: 10000000, message: "金额须在 010000000 之间", trigger: "blur" }
],
amount: [
{ required: true, message: "积分不能为空", trigger: "blur" },
{ type: "number", min: 1, max: 100000000, message: "积分须在 1100000000 之间", trigger: "blur" }
],
remark: [
{ max: 50, message: "备注不能超过50个字符", trigger: "blur" }
]
},
editScoreOpen: false,
editScoreForm: {
deptId: undefined,
deptName: "",
score: undefined,
remark: ""
},
editScoreRules: {
score: [
{ required: true, message: "积分不能为空", trigger: "blur" },
{
validator(rule, value, callback) {
if (value === undefined || value === null || value === "") {
callback(new Error("积分不能为空"))
} else if (!Number.isInteger(Number(value))) {
callback(new Error("积分须为整数"))
} else if (Number(value) === 0) {
callback(new Error("积分不能为0"))
} else if (Number(value) < -100000000 || Number(value) > 100000000) {
callback(new Error("积分须在 -100000000100000000 之间不含0"))
} else {
callback()
}
},
trigger: "blur"
}
],
remark: [
{ required: true, message: "备注不能为空", trigger: "blur" },
{
validator(rule, value, callback) {
if (!value || String(value).trim() === "") {
callback(new Error("备注不能为空"))
} else {
callback()
}
},
trigger: "blur"
},
{ max: 50, message: "备注不能超过50个字符", trigger: "blur" }
]
},
// //
rules: { rules: {
parentId: [ parentId: [
@ -506,9 +342,35 @@ export default {
return true return true
} }
return false return false
},
isFirstLevelEditForm() {
return this.form.deptId !== undefined && this.isFirstLevelRow(this.form)
} }
}, },
methods: { methods: {
isFirstLevelRow(row) {
if (!row) {
return false
}
return Number(row.parentId) === 0 && String(row.ancestors || "") === "0"
},
isSecondLevelRow(row) {
if (!row) {
return false
}
const ancestors = String(row.ancestors || "").split(",").filter(Boolean)
return ancestors.length === 2
},
getFirstLevelDeptOptions(nodes) {
if (!Array.isArray(nodes)) {
return []
}
return nodes.filter(item => this.isFirstLevelRow(item)).map(item => {
const current = { ...item }
delete current.children
return current
})
},
syncModelRowsFromForm() { syncModelRowsFromForm() {
this.modelParamRows = [{ label: '', value: '' }] this.modelParamRows = [{ label: '', value: '' }]
const raw = this.form.modelParm const raw = this.form.modelParm
@ -578,11 +440,13 @@ export default {
leader: undefined, leader: undefined,
phone: undefined, phone: undefined,
email: undefined, email: undefined,
maxUserCount: undefined,
byteApiKey: undefined, byteApiKey: undefined,
project: undefined, project: undefined,
modelParm: undefined, modelParm: undefined,
status: "0" status: "0"
} }
this.originalForm = {}
this.modelParamRows = [{ label: '', value: '' }] this.modelParamRows = [{ label: '', value: '' }]
this.resetForm("form") this.resetForm("form")
}, },
@ -598,13 +462,17 @@ export default {
/** 新增按钮操作 */ /** 新增按钮操作 */
handleAdd(row) { handleAdd(row) {
this.reset() this.reset()
if (row != undefined) { if (row !== undefined) {
if (!this.isFirstLevelRow(row)) {
this.$modal.msgError("仅允许在一级部门下新增二级部门")
return
}
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.getFirstLevelDeptOptions(this.handleTree(response.data, "deptId"))
}) })
}, },
/** 展开/折叠操作 */ /** 展开/折叠操作 */
@ -620,11 +488,13 @@ export default {
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.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") const allOptions = this.handleTree(response.data, "deptId")
this.deptOptions = this.getFirstLevelDeptOptions(allOptions)
if (this.deptOptions.length == 0) { if (this.deptOptions.length == 0) {
const noResultsOptions = { deptId: this.form.parentId, deptName: this.form.parentName, children: [] } const noResultsOptions = { deptId: this.form.parentId, deptName: this.form.parentName, children: [] }
this.deptOptions.push(noResultsOptions) this.deptOptions.push(noResultsOptions)
@ -637,7 +507,24 @@ export default {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
this.buildModelParmPayload() this.buildModelParmPayload()
if (this.form.deptId == undefined && !this.deptOptions.some(item => Number(item.deptId) === Number(this.form.parentId))) {
this.$modal.msgError("仅允许在一级部门下创建二级部门")
return
}
if (this.form.deptId != undefined) { if (this.form.deptId != undefined) {
if (this.isFirstLevelEditForm) {
const oldDept = this.originalForm || {}
this.form.parentId = oldDept.parentId
this.form.orderNum = oldDept.orderNum
this.form.leader = oldDept.leader
this.form.phone = oldDept.phone
this.form.email = oldDept.email
this.form.status = oldDept.status
this.form.maxUserCount = oldDept.maxUserCount
this.form.byteApiKey = oldDept.byteApiKey
this.form.project = oldDept.project
this.form.modelParm = oldDept.modelParm
}
updateDept(this.form).then(response => { updateDept(this.form).then(response => {
this.$modal.msgSuccess("修改成功") this.$modal.msgSuccess("修改成功")
this.open = false this.open = false
@ -653,132 +540,6 @@ export default {
} }
}) })
}, },
resetChargeRefund() {
this.chargeRefundMoneyDisplay = ""
this.chargeRefundAmountDisplay = ""
this.chargeRefundForm = {
deptId: undefined,
deptName: "",
orderType: 0,
money: undefined,
amount: undefined,
remark: ""
}
this.$nextTick(() => {
if (this.$refs.chargeRefundFormRef) {
this.$refs.chargeRefundFormRef.clearValidate()
}
})
},
handleChargeRefund(row) {
this.chargeRefundMoneyDisplay = ""
this.chargeRefundAmountDisplay = ""
this.chargeRefundForm = {
deptId: row.deptId,
deptName: row.deptName,
orderType: 0,
money: undefined,
amount: undefined,
remark: ""
}
this.chargeRefundOpen = true
this.$nextTick(() => {
if (this.$refs.chargeRefundFormRef) {
this.$refs.chargeRefundFormRef.clearValidate()
}
})
},
onChargeRefundMoneyInput(val) {
const sanitized = sanitizeMoneyDigits(val)
if (!sanitized) {
this.chargeRefundMoneyDisplay = ""
this.chargeRefundForm.money = undefined
return
}
const raw = parseFloat(sanitized)
if (!isNaN(raw) && raw > WESTERN_MONEY_MAX) {
this.chargeRefundForm.money = WESTERN_MONEY_MAX
this.chargeRefundMoneyDisplay = formatMoneyWesternFinal(WESTERN_MONEY_MAX)
return
}
this.chargeRefundMoneyDisplay = formatMoneyWesternDisplay(sanitized)
this.chargeRefundForm.money = moneyStringToNumber(sanitized)
},
onChargeRefundMoneyBlur() {
if (this.chargeRefundForm.money !== undefined && this.chargeRefundForm.money !== null) {
this.chargeRefundMoneyDisplay = formatMoneyWesternFinal(this.chargeRefundForm.money)
}
},
onChargeRefundAmountInput(val) {
const digits = sanitizeIntDigits(val)
this.chargeRefundAmountDisplay = digits === "" ? "" : formatIntWesternDisplay(digits)
this.chargeRefundForm.amount = intStringToNumber(digits)
},
submitChargeRefund() {
this.onChargeRefundMoneyBlur()
this.$refs["chargeRefundFormRef"].validate(valid => {
if (!valid) {
return
}
const data = {
deptId: this.chargeRefundForm.deptId,
orderType: this.chargeRefundForm.orderType,
money: this.chargeRefundForm.money,
amount: this.chargeRefundForm.amount,
remark: this.chargeRefundForm.remark ? this.chargeRefundForm.remark.trim() : undefined
}
chargeRefundDept(data).then(() => {
this.$modal.msgSuccess("操作成功")
this.chargeRefundOpen = false
this.getList()
})
})
},
resetEditScore() {
this.editScoreForm = {
deptId: undefined,
deptName: "",
score: undefined,
remark: ""
}
this.$nextTick(() => {
if (this.$refs.editScoreFormRef) {
this.$refs.editScoreFormRef.clearValidate()
}
})
},
handleEditScore(row) {
this.editScoreForm = {
deptId: row.deptId,
deptName: row.deptName,
score: undefined,
remark: ""
}
this.editScoreOpen = true
this.$nextTick(() => {
if (this.$refs.editScoreFormRef) {
this.$refs.editScoreFormRef.clearValidate()
}
})
},
submitEditScore() {
this.$refs["editScoreFormRef"].validate(valid => {
if (!valid) {
return
}
const remark = (this.editScoreForm.remark || "").trim()
const data = {
deptId: this.editScoreForm.deptId,
score: this.editScoreForm.score,
remark: remark
}
editScore(data).then(() => {
this.$modal.msgSuccess("操作成功")
this.editScoreOpen = false
this.getList()
})
})
},
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
this.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() { this.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() {

View File

@ -44,20 +44,7 @@
inputType="password" inputType="password"
:placeholder="`${$t('common.passwordPlaceholder')}`" /> :placeholder="`${$t('common.passwordPlaceholder')}`" />
<div class="login-link">
<mf-button
class="grey"
type="text"
@click="showForgot">
{{ $t('common.forgotPassword') }}
</mf-button>
</div>
<div class="login-submit"> <div class="login-submit">
<mf-button
size="large"
@click="showRegister">
{{ $t('common.register') }}
</mf-button>
<mf-button <mf-button
size="large" size="large"
type="primary" type="primary"
@ -67,25 +54,10 @@
</mf-button> </mf-button>
</div> </div>
</mf-dialog> </mf-dialog>
<Forgot
v-if="forgotVisible"
:visible="forgotVisible"
@open="forgotVisible = true"
@back="back"
@cancel="forgotVisible = false" />
<Register
v-if="registerVisible"
:visible="registerVisible"
@cancel="registerVisible = false"
@back="backFormRegister"
@open="registerVisible = true" />
</div> </div>
</template> </template>
<script> <script>
import Forgot from './Forgot.vue'
import Register from './Register.vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang/i18n' import i18n from '@/lang/i18n'
@ -93,22 +65,16 @@ export default {
data() { data() {
return { return {
email: '', email: '',
forgotVisible: false,
emailVisible: false, emailVisible: false,
registerVisible: false,
loading: false, loading: false,
username: '', username: '',
password: '' password: ''
} }
}, },
props: { props: {
visible: Boolean, visible: Boolean
register: Boolean
},
components: {
Forgot,
Register
}, },
components: {},
computed: { computed: {
...mapGetters(['lang']) ...mapGetters(['lang'])
}, },
@ -121,10 +87,6 @@ export default {
// } // }
this.username = "" this.username = ""
this.password = "" this.password = ""
let { inviteCode } = this.$route.query || {}
if (inviteCode) {
this.showRegister()
}
}, },
methods: { methods: {
cancel() { cancel() {
@ -132,24 +94,12 @@ export default {
this.password = '' this.password = ''
this.$emit('cancel') this.$emit('cancel')
}, },
showRegister() {
this.registerVisible = true
this.$emit('cancel')
},
changeLang(value) { changeLang(value) {
if (value != this.lang) { if (value != this.lang) {
this.$store.dispatch('main/setLanguage', value) this.$store.dispatch('main/setLanguage', value)
i18n.global.locale = value i18n.global.locale = value
} }
}, },
back() {
this.forgotVisible = false
this.$emit('open')
},
backFormRegister() {
this.registerVisible = false
this.$emit('open')
},
login() { login() {
if (!this.username) { if (!this.username) {
this.$message.error(this.$t('common.userEmailPlaceholder')) this.$message.error(this.$t('common.userEmailPlaceholder'))
@ -191,10 +141,6 @@ export default {
.catch((_) => { .catch((_) => {
this.loading = false this.loading = false
}) })
},
showForgot() {
this.forgotVisible = true
this.$emit('cancel')
} }
} }
} }
@ -324,36 +270,8 @@ export default {
justify-content: center; justify-content: center;
margin-top: 30px; margin-top: 30px;
.mf-button { .mf-button {
width: 160px; width: 220px;
border-radius: 10px; border-radius: 10px;
margin: 0 14px;
&:first-child {
color: #ffffff;
background-color: #1a1a1a;
&:hover {
background-color: #262626;
}
&:active {
background-color: #0d0d0d;
}
}
}
}
.login-link {
display: flex;
justify-content: flex-end;
color: #999;
margin-top: 10px;
.mf-button {
padding: 0;
color: #fff;
font-size: 12px;
&:hover {
background-color: transparent;
}
} }
} }
} }

View File

@ -42,22 +42,10 @@
</a-tabs> </a-tabs>
</div> </div>
</mf-dialog> </mf-dialog>
<Forgot
:visible="forgotVisible"
@open="forgotVisible = true"
@back="back"
@cancel="forgotVisible = false" />
<Register
:visible="registerVisible"
@cancel="registerVisible = false"
@back="backFormRegister"
@open="registerVisible = true" />
</div> </div>
</template> </template>
<script> <script>
import Forgot from './Forgot.vue'
import Register from './Register.vue'
import UserAccount from './UserAccount.vue' import UserAccount from './UserAccount.vue'
import ResumeRecord from './ResumeRecord.vue' import ResumeRecord from './ResumeRecord.vue'
@ -65,9 +53,7 @@ export default {
data() { data() {
return { return {
email: '', email: '',
forgotVisible: false,
emailVisible: false, emailVisible: false,
registerVisible: false,
loading: false, loading: false,
username: '', username: '',
password: '', password: '',
@ -75,22 +61,13 @@ export default {
} }
}, },
props: { props: {
visible: Boolean, visible: Boolean
register: Boolean
}, },
components: { components: {
Forgot,
Register,
UserAccount, UserAccount,
ResumeRecord ResumeRecord
}, },
mounted() { mounted() {
if (this.register) {
this.showRegister()
setTimeout((_) => {
this.$emit('cancel')
}, 300)
}
window.addEventListener('message', (event) => { window.addEventListener('message', (event) => {
if (event.data.type === 'google-login') { if (event.data.type === 'google-login') {
const { token } = event.data const { token } = event.data
@ -104,18 +81,6 @@ export default {
}) })
}, },
methods: { methods: {
showRegister() {
this.registerVisible = true
this.$emit('cancel')
},
back() {
this.forgotVisible = false
this.$emit('open')
},
backFormRegister() {
this.registerVisible = false
this.$emit('open')
},
backEmail() { backEmail() {
this.emailVisible = false this.emailVisible = false
this.$emit('open') this.$emit('open')
@ -148,10 +113,6 @@ export default {
.catch((_) => { .catch((_) => {
this.loading = false this.loading = false
}) })
},
showForgot() {
this.forgotVisible = true
this.$emit('cancel')
} }
} }
} }

View File

@ -78,7 +78,6 @@
</div> </div>
<Login <Login
:register="!$datas.isEmpty(inviteCode)"
:visible="showLogin" :visible="showLogin"
@open="openLogin" @open="openLogin"
@cancel="cancelLogin" /> @cancel="cancelLogin" />
@ -118,7 +117,6 @@ export default {
} }
], ],
publishVisible: false, publishVisible: false,
inviteCode: this.$route.query?.inviteCode,
userVisible: false, userVisible: false,
logoUrl: null logoUrl: null
} }
@ -179,9 +177,6 @@ export default {
} }
}, },
mounted() { mounted() {
if (this.inviteCode) {
this.openLogin()
}
this.getLogo() this.getLogo()
if (getToken()) { if (getToken()) {
this.$store.dispatch('user/getInfo').catch(() => {}) this.$store.dispatch('user/getInfo').catch(() => {})

View File

@ -0,0 +1,104 @@
package com.ruoyi.ai.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.ruoyi.ai.service.IAiChargeRefundOrderService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
/**
* 团队部门充值退款订单Controller
*
* @author shi
* @date 2026-04-17
*/
@RestController
@RequestMapping("/ai/order")
public class AiChargeRefundOrderController extends BaseController
{
@Autowired
private IAiChargeRefundOrderService aiChargeRefundOrderService;
/**
* 查询团队部门充值退款订单列表
*/
@PreAuthorize("@ss.hasPermi('ai:order:list')")
@GetMapping("/list")
public TableDataInfo list(AiChargeRefundOrder aiChargeRefundOrder)
{
startPage();
List<AiChargeRefundOrder> list = aiChargeRefundOrderService.selectAiChargeRefundOrderList(aiChargeRefundOrder);
return getDataTable(list);
}
/**
* 导出团队部门充值退款订单列表
*/
@PreAuthorize("@ss.hasPermi('ai:order: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, "团队(部门)充值退款订单数据");
}
/**
* 获取团队部门充值退款订单详细信息
*/
@PreAuthorize("@ss.hasPermi('ai:order:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(aiChargeRefundOrderService.selectAiChargeRefundOrderById(id));
}
/**
* 新增团队部门充值退款订单
*/
@PreAuthorize("@ss.hasPermi('ai:order:add')")
@Log(title = "团队(部门)充值退款订单", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody AiChargeRefundOrder aiChargeRefundOrder)
{
return toAjax(aiChargeRefundOrderService.insertAiChargeRefundOrder(aiChargeRefundOrder));
}
/**
* 修改团队部门充值退款订单
*/
@PreAuthorize("@ss.hasPermi('ai:order:edit')")
@Log(title = "团队(部门)充值退款订单", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody AiChargeRefundOrder aiChargeRefundOrder)
{
return toAjax(aiChargeRefundOrderService.updateAiChargeRefundOrder(aiChargeRefundOrder));
}
/**
* 删除团队部门充值退款订单
*/
@PreAuthorize("@ss.hasPermi('ai:order:remove')")
@Log(title = "团队(部门)充值退款订单", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(aiChargeRefundOrderService.deleteAiChargeRefundOrderByIds(ids));
}
}

View File

@ -0,0 +1,104 @@
package com.ruoyi.ai.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.ai.domain.AiDeptArkConfig;
import com.ruoyi.ai.service.IAiDeptArkConfigService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
/**
* 团队部门对应火山引擎配置Controller
*
* @author shi
* @date 2026-04-17
*/
@RestController
@RequestMapping("/ai/config")
public class AiDeptArkConfigController extends BaseController
{
@Autowired
private IAiDeptArkConfigService aiDeptArkConfigService;
/**
* 查询团队部门对应火山引擎配置列表
*/
@PreAuthorize("@ss.hasPermi('ai:config:list')")
@GetMapping("/list")
public TableDataInfo list(AiDeptArkConfig aiDeptArkConfig)
{
startPage();
List<AiDeptArkConfig> list = aiDeptArkConfigService.selectAiDeptArkConfigList(aiDeptArkConfig);
return getDataTable(list);
}
/**
* 导出团队部门对应火山引擎配置列表
*/
@PreAuthorize("@ss.hasPermi('ai:config: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, "团队(部门)对应火山引擎配置数据");
}
/**
* 获取团队部门对应火山引擎配置详细信息
*/
@PreAuthorize("@ss.hasPermi('ai:config:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id)
{
return success(aiDeptArkConfigService.selectAiDeptArkConfigById(id));
}
/**
* 新增团队部门对应火山引擎配置
*/
@PreAuthorize("@ss.hasPermi('ai:config:add')")
@Log(title = "团队(部门)对应火山引擎配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody AiDeptArkConfig aiDeptArkConfig)
{
return toAjax(aiDeptArkConfigService.insertAiDeptArkConfig(aiDeptArkConfig));
}
/**
* 修改团队部门对应火山引擎配置
*/
@PreAuthorize("@ss.hasPermi('ai:config:edit')")
@Log(title = "团队(部门)对应火山引擎配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody AiDeptArkConfig aiDeptArkConfig)
{
return toAjax(aiDeptArkConfigService.updateAiDeptArkConfig(aiDeptArkConfig));
}
/**
* 删除团队部门对应火山引擎配置
*/
@PreAuthorize("@ss.hasPermi('ai:config:remove')")
@Log(title = "团队(部门)对应火山引擎配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{
return toAjax(aiDeptArkConfigService.deleteAiDeptArkConfigByIds(ids));
}
}

View File

@ -0,0 +1,104 @@
package com.ruoyi.ai.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.ruoyi.ai.service.IAiGroupBalanceChangeRecordService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
/**
* 团队部门余额变动Controller
*
* @author shi
* @date 2026-04-17
*/
@RestController
@RequestMapping("/ai/record")
public class AiGroupBalanceChangeRecordController extends BaseController
{
@Autowired
private IAiGroupBalanceChangeRecordService aiGroupBalanceChangeRecordService;
/**
* 查询团队部门余额变动列表
*/
@PreAuthorize("@ss.hasPermi('ai:record:list')")
@GetMapping("/list")
public TableDataInfo list(AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord)
{
startPage();
List<AiGroupBalanceChangeRecord> list = aiGroupBalanceChangeRecordService.selectAiGroupBalanceChangeRecordList(aiGroupBalanceChangeRecord);
return getDataTable(list);
}
/**
* 导出团队部门余额变动列表
*/
@PreAuthorize("@ss.hasPermi('ai:record:export')")
@Log(title = "团队(部门)余额变动", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord)
{
List<AiGroupBalanceChangeRecord> list = aiGroupBalanceChangeRecordService.selectAiGroupBalanceChangeRecordList(aiGroupBalanceChangeRecord);
ExcelUtil<AiGroupBalanceChangeRecord> util = new ExcelUtil<AiGroupBalanceChangeRecord>(AiGroupBalanceChangeRecord.class);
util.exportExcel(response, list, "团队(部门)余额变动数据");
}
/**
* 获取团队部门余额变动详细信息
*/
@PreAuthorize("@ss.hasPermi('ai:record:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id)
{
return success(aiGroupBalanceChangeRecordService.selectAiGroupBalanceChangeRecordById(id));
}
/**
* 新增团队部门余额变动
*/
@PreAuthorize("@ss.hasPermi('ai:record:add')")
@Log(title = "团队(部门)余额变动", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord)
{
return toAjax(aiGroupBalanceChangeRecordService.insertAiGroupBalanceChangeRecord(aiGroupBalanceChangeRecord));
}
/**
* 修改团队部门余额变动
*/
@PreAuthorize("@ss.hasPermi('ai:record:edit')")
@Log(title = "团队(部门)余额变动", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord)
{
return toAjax(aiGroupBalanceChangeRecordService.updateAiGroupBalanceChangeRecord(aiGroupBalanceChangeRecord));
}
/**
* 删除团队部门余额变动
*/
@PreAuthorize("@ss.hasPermi('ai:record:remove')")
@Log(title = "团队(部门)余额变动", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{
return toAjax(aiGroupBalanceChangeRecordService.deleteAiGroupBalanceChangeRecordByIds(ids));
}
}

View File

@ -0,0 +1,110 @@
package com.ruoyi.ai.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.ai.domain.AiVideoReportData;
import com.ruoyi.ai.service.IAiVideoReportDataService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.StringUtils;
/**
* AI视频生成统计数据作为其他统计报的数据源Controller
*
* @author shi
* @date 2026-04-17
*/
@RestController
@RequestMapping("/ai/data")
public class AiVideoReportDataController extends BaseController
{
@Autowired
private IAiVideoReportDataService aiVideoReportDataService;
/**
* 查询AI视频生成统计数据作为其他统计报的数据源列表
*/
@PreAuthorize("@ss.hasPermi('ai:data:list')")
@GetMapping("/list")
public TableDataInfo list(AiVideoReportData aiVideoReportData)
{
if (StringUtils.isEmpty(aiVideoReportData.getStatDate()) || aiVideoReportData.getDeptId() == null)
{
return getDataTable(java.util.Collections.emptyList());
}
startPage();
List<AiVideoReportData> list = aiVideoReportDataService.selectTeamDailyConsumeList(
aiVideoReportData.getStatDate(), aiVideoReportData.getDeptId());
return getDataTable(list);
}
/**
* 导出AI视频生成统计数据作为其他统计报的数据源列表
*/
@PreAuthorize("@ss.hasPermi('ai:data:export')")
@Log(title = "AI视频生成统计数据作为其他统计报的数据源", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, AiVideoReportData aiVideoReportData)
{
List<AiVideoReportData> list = aiVideoReportDataService.selectAiVideoReportDataList(aiVideoReportData);
ExcelUtil<AiVideoReportData> util = new ExcelUtil<AiVideoReportData>(AiVideoReportData.class);
util.exportExcel(response, list, "AI视频生成统计数据作为其他统计报的数据源数据");
}
/**
* 获取AI视频生成统计数据作为其他统计报的数据源详细信息
*/
@PreAuthorize("@ss.hasPermi('ai:data:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id)
{
return success(aiVideoReportDataService.selectAiVideoReportDataById(id));
}
/**
* 新增AI视频生成统计数据作为其他统计报的数据源
*/
@PreAuthorize("@ss.hasPermi('ai:data:add')")
@Log(title = "AI视频生成统计数据作为其他统计报的数据源", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody AiVideoReportData aiVideoReportData)
{
return toAjax(aiVideoReportDataService.insertAiVideoReportData(aiVideoReportData));
}
/**
* 修改AI视频生成统计数据作为其他统计报的数据源
*/
@PreAuthorize("@ss.hasPermi('ai:data:edit')")
@Log(title = "AI视频生成统计数据作为其他统计报的数据源", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody AiVideoReportData aiVideoReportData)
{
return toAjax(aiVideoReportDataService.updateAiVideoReportData(aiVideoReportData));
}
/**
* 删除AI视频生成统计数据作为其他统计报的数据源
*/
@PreAuthorize("@ss.hasPermi('ai:data:remove')")
@Log(title = "AI视频生成统计数据作为其他统计报的数据源", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{
return toAjax(aiVideoReportDataService.deleteAiVideoReportDataByIds(ids));
}
}

View File

@ -162,7 +162,7 @@ public class PortalVideoController extends BaseController {
List<PortalVideoProperties.ModelOption> allowed = loadModelOptionsForAiUser(uid, secondDeptId); List<PortalVideoProperties.ModelOption> allowed = loadModelOptionsForAiUser(uid, secondDeptId);
String modelId = StringUtils.isNotEmpty(req.getModel()) ? req.getModel() : resolveDefaultModelForAiUser(uid, secondDeptId, allowed); String modelId = StringUtils.isNotEmpty(req.getModel()) ? req.getModel() : resolveDefaultModelForAiUser(uid, secondDeptId, allowed);
if (StringUtils.isEmpty(modelId)) { if (StringUtils.isEmpty(modelId)) {
throw new ServiceException("未配置门户视频模型:请在后台为该用户所属二级部门配置 model_parm或在 portal.video.models 中配置全局列表"); throw new ServiceException("未配置门户视频模型:请在后台为该用户所属二级部门配置");
} }
if (StringUtils.isNotEmpty(req.getModel()) && !isModelInOptions(modelId, allowed)) { if (StringUtils.isNotEmpty(req.getModel()) && !isModelInOptions(modelId, allowed)) {
throw new ServiceException("所选模型对当前部门不可用"); throw new ServiceException("所选模型对当前部门不可用");
@ -517,7 +517,7 @@ public class PortalVideoController extends BaseController {
} }
@GetMapping("/options") @GetMapping("/options")
@ApiOperation("门户视频生成可选参数(模型/比例/时长等,来自配置") @ApiOperation("门户视频生成可选参数(模型/比例/时长等,来自系统参数")
public AjaxResult videoParamOptions() { public AjaxResult videoParamOptions() {
Long uid = SecurityUtils.getAiUserId(); Long uid = SecurityUtils.getAiUserId();
Long secondDeptId = byteDeptApiKeyService.resolveSecondLevelDeptId(uid); Long secondDeptId = byteDeptApiKeyService.resolveSecondLevelDeptId(uid);

View File

@ -1,115 +1,102 @@
package com.ruoyi.config; package com.ruoyi.config;
import org.springframework.boot.context.properties.ConfigurationProperties; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysConfigService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Resource;
/** /**
* 门户视频生成参数模型比例时长分辨率等从配置读取不在代码中写死业务默认值 * 门户视频生成参数 sys_config 读取
*/ */
@Component @Component
@ConfigurationProperties(prefix = "portal.video")
public class PortalVideoProperties { public class PortalVideoProperties {
private Defaults defaults = new Defaults(); private static final ObjectMapper OM = new ObjectMapper();
private List<ModelOption> models = new ArrayList<>(); /** 默认值:{"model":"...","duration":5,"resolution":"720p","ratio":"16:9"} */
private static final String CFG_DEFAULTS = "portal.video.defaults";
/** 模型列表:[{"label":"Seedance 2.0","value":"ep-xxx"}] */
private static final String CFG_MODELS = "portal.video.models";
/** 二级部门模型映射:{"101":[{"label":"...","value":"..."}]} */
private static final String CFG_MODELS_BY_SECOND_DEPT = "portal.video.modelsBySecondDept";
/** 二级部门默认值覆盖:{"101":{"duration":5,"resolution":"720p"}} */
private static final String CFG_DEFAULTS_BY_SECOND_DEPT = "portal.video.defaultsBySecondDept";
/** 比例列表:["16:9","9:16"] */
private static final String CFG_RATIOS = "portal.video.ratios";
/** 时长列表:[5,10] */
private static final String CFG_DURATIONS = "portal.video.durations";
/** 分辨率列表:["720p","1080p"] */
private static final String CFG_RESOLUTIONS = "portal.video.resolutions";
/** 功能类型:与 ai_manager.type 对应 */
private static final String CFG_FUNCTION_TYPE = "portal.video.functionType";
private static final String DEFAULT_FUNCTION_TYPE = "21";
/** @Resource
* 按二级部门 {@code dept_id}字符串 {@code "101"}配置专属模型列表未命中时使用全局 {@link #models} private ISysConfigService sysConfigService;
*/
private Map<String, List<ModelOption>> modelsBySecondDept = new LinkedHashMap<>();
/**
* 按二级部门覆盖 {@link #defaults} 中的字段仅配置需要覆盖的项即可
*/
private Map<String, Defaults> defaultsBySecondDept = new LinkedHashMap<>();
private List<String> ratios = new ArrayList<>();
private List<Integer> durations = new ArrayList<>();
private List<String> resolutions = new ArrayList<>();
/**
* ai_manager.type 一致用于扣费与订单库中需存在对应记录且 status=0del_flag=0
*/
private String functionType = "21";
public String getFunctionType() { public String getFunctionType() {
return functionType; String functionType = trimToNull(sysConfigService.selectConfigByKey(CFG_FUNCTION_TYPE));
} return functionType != null ? functionType : DEFAULT_FUNCTION_TYPE;
public void setFunctionType(String functionType) {
this.functionType = functionType != null ? functionType : "21";
} }
public Defaults getDefaults() { public Defaults getDefaults() {
return defaults; Defaults defaults = parseObjectConfig(CFG_DEFAULTS, Defaults.class);
} return defaults != null ? defaults : new Defaults();
public void setDefaults(Defaults defaults) {
if (defaults != null) {
this.defaults = defaults;
}
} }
public List<ModelOption> getModels() { public List<ModelOption> getModels() {
return models; List<ModelOption> models = parseListConfig(CFG_MODELS, new TypeReference<List<ModelOption>>() {
} });
return models != null ? models : new ArrayList<>();
public void setModels(List<ModelOption> models) {
this.models = models != null ? models : new ArrayList<>();
} }
public Map<String, List<ModelOption>> getModelsBySecondDept() { public Map<String, List<ModelOption>> getModelsBySecondDept() {
return modelsBySecondDept; Map<String, List<ModelOption>> map = parseObjectConfig(CFG_MODELS_BY_SECOND_DEPT,
} new TypeReference<Map<String, List<ModelOption>>>() {
});
public void setModelsBySecondDept(Map<String, List<ModelOption>> modelsBySecondDept) { return map != null ? map : new LinkedHashMap<>();
this.modelsBySecondDept = modelsBySecondDept != null ? modelsBySecondDept : new LinkedHashMap<>();
} }
public Map<String, Defaults> getDefaultsBySecondDept() { public Map<String, Defaults> getDefaultsBySecondDept() {
return defaultsBySecondDept; Map<String, Defaults> map = parseObjectConfig(CFG_DEFAULTS_BY_SECOND_DEPT,
} new TypeReference<Map<String, Defaults>>() {
});
public void setDefaultsBySecondDept(Map<String, Defaults> defaultsBySecondDept) { return map != null ? map : new LinkedHashMap<>();
this.defaultsBySecondDept = defaultsBySecondDept != null ? defaultsBySecondDept : new LinkedHashMap<>();
} }
public List<String> getRatios() { public List<String> getRatios() {
return ratios; List<String> ratios = parseListConfig(CFG_RATIOS, new TypeReference<List<String>>() {
} });
return ratios != null ? ratios : Collections.emptyList();
public void setRatios(List<String> ratios) {
this.ratios = ratios != null ? ratios : new ArrayList<>();
} }
public List<Integer> getDurations() { public List<Integer> getDurations() {
return durations; List<Integer> durations = parseListConfig(CFG_DURATIONS, new TypeReference<List<Integer>>() {
} });
return durations != null ? durations : Collections.emptyList();
public void setDurations(List<Integer> durations) {
this.durations = durations != null ? durations : new ArrayList<>();
} }
public List<String> getResolutions() { public List<String> getResolutions() {
return resolutions; List<String> resolutions = parseListConfig(CFG_RESOLUTIONS, new TypeReference<List<String>>() {
} });
return resolutions != null ? resolutions : Collections.emptyList();
public void setResolutions(List<String> resolutions) {
this.resolutions = resolutions != null ? resolutions : new ArrayList<>();
} }
/** /**
* defaults.model 为空时 models 第一项的 value * defaults.model 为空时 models 第一项的 value
*/ */
public String resolveDefaultModelId() { public String resolveDefaultModelId() {
Defaults defaults = getDefaults();
List<ModelOption> models = getModels();
if (defaults.getModel() != null && !defaults.getModel().isEmpty()) { if (defaults.getModel() != null && !defaults.getModel().isEmpty()) {
return defaults.getModel(); return defaults.getModel();
} }
@ -120,6 +107,8 @@ public class PortalVideoProperties {
} }
public List<ModelOption> resolveModelsForSecondDept(Long secondDeptId) { public List<ModelOption> resolveModelsForSecondDept(Long secondDeptId) {
Map<String, List<ModelOption>> modelsBySecondDept = getModelsBySecondDept();
List<ModelOption> models = getModels();
if (secondDeptId != null) { if (secondDeptId != null) {
List<ModelOption> byDept = modelsBySecondDept.get(String.valueOf(secondDeptId)); List<ModelOption> byDept = modelsBySecondDept.get(String.valueOf(secondDeptId));
if (byDept != null && !byDept.isEmpty()) { if (byDept != null && !byDept.isEmpty()) {
@ -131,12 +120,13 @@ public class PortalVideoProperties {
public Defaults resolveEffectiveDefaults(Long secondDeptId) { public Defaults resolveEffectiveDefaults(Long secondDeptId) {
Defaults out = new Defaults(); Defaults out = new Defaults();
Defaults g = this.defaults; Defaults g = this.getDefaults();
out.setModel(g.getModel()); out.setModel(g.getModel());
out.setDuration(g.getDuration()); out.setDuration(g.getDuration());
out.setResolution(g.getResolution()); out.setResolution(g.getResolution());
out.setRatio(g.getRatio()); out.setRatio(g.getRatio());
if (secondDeptId != null) { if (secondDeptId != null) {
Map<String, Defaults> defaultsBySecondDept = getDefaultsBySecondDept();
Defaults o = defaultsBySecondDept.get(String.valueOf(secondDeptId)); Defaults o = defaultsBySecondDept.get(String.valueOf(secondDeptId));
if (o != null) { if (o != null) {
if (o.getModel() != null && !o.getModel().isEmpty()) { if (o.getModel() != null && !o.getModel().isEmpty()) {
@ -183,6 +173,51 @@ public class PortalVideoProperties {
return false; return false;
} }
private <T> T parseObjectConfig(String key, Class<T> clazz) {
String raw = trimToNull(sysConfigService.selectConfigByKey(key));
if (raw == null) {
return null;
}
try {
return OM.readValue(raw, clazz);
} catch (Exception e) {
return null;
}
}
private <T> T parseObjectConfig(String key, TypeReference<T> typeReference) {
String raw = trimToNull(sysConfigService.selectConfigByKey(key));
if (raw == null) {
return null;
}
try {
return OM.readValue(raw, typeReference);
} catch (Exception e) {
return null;
}
}
private <T> List<T> parseListConfig(String key, TypeReference<List<T>> typeReference) {
String raw = trimToNull(sysConfigService.selectConfigByKey(key));
if (raw == null) {
return null;
}
try {
List<T> list = OM.readValue(raw, typeReference);
return list != null ? list : new ArrayList<>();
} catch (Exception e) {
return null;
}
}
private String trimToNull(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
String out = value.trim();
return out.isEmpty() ? null : out;
}
public static class Defaults { public static class Defaults {
private String model; private String model;
private Integer duration; private Integer duration;

View File

@ -24,7 +24,7 @@ import java.util.List;
* @date 2025-11-13 * @date 2025-11-13
*/ */
@RestController @RestController
@RequestMapping("/ai/order") @RequestMapping("/ai/video/order")
public class AiOrderController extends BaseController public class AiOrderController extends BaseController
{ {
@Autowired @Autowired

View File

@ -0,0 +1,45 @@
package com.ruoyi.web.controller.subteam;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.service.subteam.ISubteamDataQueryService;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
import com.ruoyi.ai.service.IAiChargeRefundOrderService;
@RestController
@RequestMapping("/subteam/charge-order")
public class SubteamChargeOrderController extends BaseController {
@Autowired
private ISubteamDataQueryService subteamDataQueryService;
@Autowired
private ISubteamScopeService subteamScopeService;
@Autowired
private IAiChargeRefundOrderService aiChargeRefundOrderService;
@PreAuthorize("@ss.hasPermi('subteam:charge:list')")
@GetMapping("/list")
public TableDataInfo list(AiChargeRefundOrder query) {
startPage();
List<AiChargeRefundOrder> list = subteamDataQueryService.selectChargeRefundOrders(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('subteam:charge:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
subteamScopeService.assertChargeRefundBelongsToTeam(id);
return success(aiChargeRefundOrderService.selectAiChargeRefundOrderById(id));
}
}

View File

@ -0,0 +1,32 @@
package com.ruoyi.web.controller.subteam;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ai.domain.AiVideoReportData;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.subteam.ISubteamDataQueryService;
@RestController
@RequestMapping("/subteam/consume-stat")
public class SubteamConsumeStatController extends BaseController {
@Autowired
private ISubteamDataQueryService subteamDataQueryService;
@PreAuthorize("@ss.hasPermi('subteam:consume:list')")
@GetMapping("/list")
public TableDataInfo list(AiVideoReportData query) {
if (StringUtils.isEmpty(query.getStatDate())) {
return getDataTable(java.util.Collections.emptyList());
}
startPage();
List<AiVideoReportData> list = subteamDataQueryService.selectTeamDailyConsume(query.getStatDate());
return getDataTable(list);
}
}

View File

@ -0,0 +1,45 @@
package com.ruoyi.web.controller.subteam;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.ruoyi.ai.service.IAiGroupBalanceChangeRecordService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.service.subteam.ISubteamDataQueryService;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
@RestController
@RequestMapping("/subteam/group-balance")
public class SubteamGroupBalanceController extends BaseController {
@Autowired
private ISubteamDataQueryService subteamDataQueryService;
@Autowired
private ISubteamScopeService subteamScopeService;
@Autowired
private IAiGroupBalanceChangeRecordService aiGroupBalanceChangeRecordService;
@PreAuthorize("@ss.hasPermi('subteam:groupBalance:list')")
@GetMapping("/list")
public TableDataInfo list(AiGroupBalanceChangeRecord query) {
startPage();
List<AiGroupBalanceChangeRecord> list = subteamDataQueryService.selectGroupBalanceRecords(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('subteam:groupBalance:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable String id) {
subteamScopeService.assertGroupBalanceRecordBelongsToTeam(id);
return success(aiGroupBalanceChangeRecordService.selectAiGroupBalanceChangeRecordById(id));
}
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.web.controller.subteam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.subteam.SubteamOverviewVO;
import com.ruoyi.system.service.subteam.ISubteamOverviewService;
@RestController
@RequestMapping("/subteam/overview")
public class SubteamOverviewController extends BaseController {
@Autowired
private ISubteamOverviewService subteamOverviewService;
@PreAuthorize("@ss.hasPermi('subteam:overview:view')")
@GetMapping
public AjaxResult overview() {
SubteamOverviewVO vo = subteamOverviewService.loadOverview();
return success(vo);
}
}

View File

@ -0,0 +1,45 @@
package com.ruoyi.web.controller.subteam;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import com.ruoyi.ai.service.IAiBalanceChangeRecordService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.service.subteam.ISubteamDataQueryService;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
@RestController
@RequestMapping("/subteam/user-balance")
public class SubteamUserBalanceController extends BaseController {
@Autowired
private ISubteamDataQueryService subteamDataQueryService;
@Autowired
private ISubteamScopeService subteamScopeService;
@Autowired
private IAiBalanceChangeRecordService aiBalanceChangeRecordService;
@PreAuthorize("@ss.hasPermi('subteam:userBalance:list')")
@GetMapping("/list")
public TableDataInfo list(AiBalanceChangeRecord query) {
startPage();
List<AiBalanceChangeRecord> list = subteamDataQueryService.selectUserBalanceRecords(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('subteam:userBalance:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
subteamScopeService.assertAiBalanceRecordVisible(id, subteamScopeService.currentTeamDeptId());
return success(aiBalanceChangeRecordService.selectAiBalanceChangeRecordById(id));
}
}

View File

@ -0,0 +1,118 @@
package com.ruoyi.web.controller.subteam;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.AiUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.ai.service.IAiUserService;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
@RestController
@RequestMapping("/subteam/user")
public class SubteamUserController extends BaseController {
@Autowired
private IAiUserService aiUserService;
@Autowired
private ISubteamScopeService subteamScopeService;
@PreAuthorize("@ss.hasPermi('subteam:user:list')")
@GetMapping("/list")
public TableDataInfo list(AiUser aiUser) {
aiUser.setDeptId(subteamScopeService.currentTeamDeptId());
startPage();
List<AiUser> list = aiUserService.selectAiUserList(aiUser);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('subteam:user:query')")
@GetMapping(value = {"/", "/{userId}"})
public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) {
if (StringUtils.isNotNull(userId)) {
subteamScopeService.assertAiUserInTeam(userId);
return success(aiUserService.selectAiUserById(userId));
}
return success();
}
@PreAuthorize("@ss.hasPermi('subteam:user:add')")
@Log(title = "团队后台用户", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody AiUser aiUser) {
if (StringUtils.isEmpty(aiUser.getUsername())) {
return error("登录账号不能为空");
}
if (StringUtils.isEmpty(aiUser.getPassword())) {
return error("密码不能为空");
}
aiUser.setDeptId(subteamScopeService.currentTeamDeptId());
aiUser.setCreateBy(getUsername());
return toAjax(aiUserService.insertAiUser(aiUser));
}
@PreAuthorize("@ss.hasPermi('subteam:user:edit')")
@Log(title = "团队后台用户", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody AiUser aiUser) {
if (aiUser.getId() == null) {
return error("用户主键不能为空");
}
subteamScopeService.assertAiUserInTeam(aiUser.getId());
aiUser.setDeptId(subteamScopeService.currentTeamDeptId());
aiUser.setUpdateBy(getUsername());
return toAjax(aiUserService.updateAiUser(aiUser));
}
@PreAuthorize("@ss.hasPermi('subteam:user:remove')")
@Log(title = "团队后台用户", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds) {
for (Long userId : userIds) {
subteamScopeService.assertAiUserInTeam(userId);
}
return toAjax(aiUserService.deleteAiUserByIds(userIds));
}
@PreAuthorize("@ss.hasPermi('subteam:user:resetPwd')")
@Log(title = "团队后台用户", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd")
public AjaxResult resetPwd(@RequestBody AiUser aiUser) {
if (aiUser.getId() == null) {
return error("用户主键不能为空");
}
if (StringUtils.isEmpty(aiUser.getPassword())) {
return error("新密码不能为空");
}
subteamScopeService.assertAiUserInTeam(aiUser.getId());
aiUser.setNewPassword(aiUser.getPassword());
return toAjax(aiUserService.updatePassword(aiUser));
}
@PreAuthorize("@ss.hasPermi('subteam:user:edit')")
@Log(title = "团队后台用户", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public AjaxResult changeStatus(@RequestBody AiUser aiUser) {
if (aiUser.getId() == null) {
return error("用户主键不能为空");
}
subteamScopeService.assertAiUserInTeam(aiUser.getId());
aiUser.setUpdateBy(getUsername());
return toAjax(aiUserService.updateUserStatus(aiUser));
}
}

View File

@ -0,0 +1,45 @@
package com.ruoyi.web.controller.subteam;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ai.domain.AiOrder;
import com.ruoyi.ai.service.IAiOrderService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.service.subteam.ISubteamDataQueryService;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
@RestController
@RequestMapping("/subteam/video-order")
public class SubteamVideoOrderController extends BaseController {
@Autowired
private ISubteamDataQueryService subteamDataQueryService;
@Autowired
private ISubteamScopeService subteamScopeService;
@Autowired
private IAiOrderService aiOrderService;
@PreAuthorize("@ss.hasPermi('subteam:videoOrder:list')")
@GetMapping("/list")
public TableDataInfo list(AiOrder query) {
startPage();
List<AiOrder> list = subteamDataQueryService.selectVideoOrders(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('subteam:videoOrder:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
subteamScopeService.assertAiOrderBelongsToTeam(id);
return success(aiOrderService.selectAiOrderById(id));
}
}

View File

@ -74,20 +74,13 @@ public class BalanceChangerConstants {
public static final int SYSTEM_OPERATION = 11; public static final int SYSTEM_OPERATION = 11;
/** /**
* 部门积分下放至用户sys_dept.balance ai_user.balance * 部门下发积分给用户
*/ */
public static final int DEPT_SCORE_ISSUE = 12; public static final int DEPT_SCORE_ISSUE = 12;
/** /**
* 用户积分回收至部门ai_user.balance sys_dept.balance * 从用户回收积分到部门
*/ */
public static final int DEPT_SCORE_RECLAIM = 13; public static final int DEPT_SCORE_RECLAIM = 13;
public static class OrderNoPrefix {
// 团队充值退款订单号前缀
public static final String RECHARGE_REFUND_PREFIX = "RE";
// 团队积分下放回收单号前缀
public static final String ISSUE_RECLAIM_PREFIX = "IS";
}
} }

View File

@ -15,7 +15,7 @@
<el-form-item label="${comment}" prop="${column.javaField}"> <el-form-item label="${comment}" prop="${column.javaField}">
<el-input <el-input
v-model="queryParams.${column.javaField}" v-model="queryParams.${column.javaField}"
placeholder="请输入${comment}" placeholder="请输入ID"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />

View File

@ -1,56 +1,67 @@
package com.ruoyi.ai.domain; package com.ruoyi.ai.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
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.fasterxml.jackson.annotation.JsonFormat; import java.math.BigDecimal;
import com.ruoyi.common.annotation.Excel;
import lombok.Data; import lombok.Data;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/** /**
* 充值/退款订单 ai_charge_refund_order * 团队部门充值退款订单对象 ai_charge_refund_order
*
* @author shi
* @date 2026-04-17
*/ */
@Data @Data
@TableName("ai_charge_refund_order") @TableName("ai_charge_refund_order")
public class AiChargeRefundOrder implements Serializable { public class AiChargeRefundOrder extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
private Long id; private Long id;
/** 删除标志0代表存在 2代表删除 */ /** 删除标志0代表存在 2代表删除 */
private String delFlag; private String delFlag;
private Long createBy; /** 订单编号 */
@Excel(name = "订单编号")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private String orderNum; private String orderNum;
/** 第三方单号(预留) */
@Excel(name = "第三方单号(预留)")
private String thirdPartyOrderNum; private String thirdPartyOrderNum;
/** 部门ID */
@Excel(name = "部门ID")
private Long deptId; private Long deptId;
/** 订单类型0-充值 1-退款 */ /** 团队名称(列表/导出关联查询,非表字段) */
// ChargeRefundOrderType @TableField(exist = false)
private Integer orderType; @Excel(name = "团队名称")
private String deptName;
/** 订单类型(0-充值;1-退款;2-手动修改) */
@Excel(name = "订单类型", readConverterExp = "0=充值,1=退款,2=手动修改")
private Long orderType;
/** 金额(元) */
@Excel(name = "金额(元)")
private BigDecimal money; private BigDecimal money;
/**
* 积分变动充值/退款为绝对值退款在统计中按负数计入
* 手动修改可为正增加或负扣减
*/
@Excel(name = "积分")
private BigDecimal amount; private BigDecimal amount;
private String remark;
/** 状态0-进行中 1-已完成 2-失败 */ /** 状态0-进行中 1-已完成 2-失败 */
// ChargeRefundOrderStatusType @Excel(name = "状态", readConverterExp = "0=进行中,1=已完成,2=失败")
private Integer status; private Integer status;
} }

View File

@ -1,42 +1,44 @@
package com.ruoyi.ai.domain; package com.ruoyi.ai.domain;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
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.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/** /**
* 部门方舟配置 ai_dept_ark_config * 团队部门对应火山引擎配置对象 ai_dept_ark_config
*
* @author shi
* @date 2026-04-17
*/ */
@Data @Data
@TableName("ai_dept_ark_config") @TableName("ai_dept_ark_config")
public class AiDeptArkConfig implements Serializable { public class AiDeptArkConfig extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** $column.columnComment */
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
private Long id; private String id;
/** 部门ID */
@Excel(name = "部门ID")
private Long deptId; private Long deptId;
/** 模型参数 JSON */ /** 视频模型列表JSON(label+value) */
@Excel(name = "视频模型列表JSON(label+value)")
private String modelParm; private String modelParm;
/** Byte project加密 */
@Excel(name = "Byte project加密")
private String project; private String project;
/** Byte API Key加密 */
@Excel(name = "Byte API Key加密")
private String byteApiKey; private String byteApiKey;
private Long createBy;
private Long updateBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
} }

View File

@ -1,49 +1,49 @@
package com.ruoyi.ai.domain; package com.ruoyi.ai.domain;
import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import com.baomidou.mybatisplus.annotation.EnumValue; import org.apache.commons.lang3.builder.ToStringStyle;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
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.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
import lombok.Data; import com.ruoyi.common.core.domain.BaseEntity;
/** /**
* 集团余额变动记录 ai_group_balance_change_record * 团队部门余额变动对象 ai_group_balance_change_record
*
* @author shi
* @date 2026-04-17
*/ */
@Data @Data
@TableName("ai_group_balance_change_record") @TableName("ai_group_balance_change_record")
public class AiGroupBalanceChangeRecord implements Serializable { public class AiGroupBalanceChangeRecord extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** $column.columnComment */
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
private Long id; private String id;
/** 关联订单号 */ /** 关联(充值/退款)订单号 */
@Excel(name = "关联(充值/退款)订单号")
private String relationOrderNo; private String relationOrderNo;
/** 部门ID */
@Excel(name = "部门ID")
private Long deptId; private Long deptId;
/** 类型0-充值 1-退款 2-下发 3-消费 4-手动修改 */ /** 操作类型0-充值、1-退款、2-下发、3-回收、4-手动修改) */
// GroupBalanceChangeType @Excel(name = "操作类型", readConverterExp = "0=-充值、1-退款、2-下发、3-回收、4-手动修改")
private Integer type; private Long type;
/** 变更金额 */
@Excel(name = "变更金额")
private BigDecimal changeAmount; private BigDecimal changeAmount;
/** 变更后金额 */
@Excel(name = "变更后金额")
private BigDecimal resultAmount; private BigDecimal resultAmount;
private String remark;
private Long createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
} }

View File

@ -1,50 +1,63 @@
package com.ruoyi.ai.domain; package com.ruoyi.ai.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
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.fasterxml.jackson.annotation.JsonFormat; import java.math.BigDecimal;
import com.ruoyi.common.annotation.Excel;
import lombok.Data; import lombok.Data;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/** /**
* 视频报表数据 ai_video_report_data * AI视频生成统计数据作为其他统计报的数据源对象 ai_video_report_data
*
* @author shi
* @date 2026-04-17
*/ */
@Data @Data
@TableName("ai_video_report_data") @TableName("ai_video_report_data")
public class AiVideoReportData implements Serializable { public class AiVideoReportData extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 主键 */ /** $column.columnComment */
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
private Long id; private String id;
/** 统计日期键varchar */ /** 统计时间到小时yyyyMMddHH */
@Excel(name = "统计时间,到小时('%Y-%m-%d %H')")
private String dateKey; private String dateKey;
/** 部门ID */ /** 部门ID */
@Excel(name = "部门ID")
private Long deptId; private Long deptId;
/** 用户ID */ /** 用户ID用户表的ID延用其他表设计 */
@Excel(name = "用户ID用户表的ID延用其他表设计 ")
private Long userId; private Long userId;
/** 积分/分数统计 */ /** 消耗积分,按任务创建时间统计 */
@Excel(name = "消耗积分,按任务创建时间统计")
private BigDecimal score; private BigDecimal score;
/** 订单数 */ /** 实际订单数,只统计已生成成功的任务 */
@Excel(name = "实际订单数,只统计已生成成功的任务")
private Long orderCount; private Long orderCount;
/** 消耗 tokens */ /** 三方消耗tokens数量按任务创建时间统计 */
@Excel(name = "三方消耗tokens数量按任务创建时间统计")
private Long useTokens; private Long useTokens;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") /** 实际充值积分(充值-退款) */
private Date createTime; @Excel(name = "实际充值积分(充值-退款)")
private BigDecimal rechargeScore;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") /** 团队名称(查询结果展示字段) */
private Date updateTime; @TableField(exist = false)
private String deptName;
/** 查询日期yyyyMMdd */
@TableField(exist = false)
private String statDate;
} }

View File

@ -4,6 +4,7 @@ import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.ai.domain.AiBalanceChangeRecord; import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import org.apache.ibatis.annotations.Param;
/** /**
* 余额使用记录Mapper接口 * 余额使用记录Mapper接口
@ -17,4 +18,7 @@ public interface AiBalanceChangeRecordMapper extends BaseMapper<AiBalanceChangeR
int deleteAiBalanceChangeRecordById(Long id); int deleteAiBalanceChangeRecordById(Long id);
List<AiBalanceChangeRecord> selectAiBalanceChangeRecordList(AiBalanceChangeRecord aiBalanceChangeRecord); List<AiBalanceChangeRecord> selectAiBalanceChangeRecordList(AiBalanceChangeRecord aiBalanceChangeRecord);
List<AiBalanceChangeRecord> selectAiBalanceChangeRecordListByAiUserDept(@Param("q") AiBalanceChangeRecord q,
@Param("deptId") Long deptId);
} }

View File

@ -1,10 +1,24 @@
package com.ruoyi.ai.mapper; package com.ruoyi.ai.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.ai.domain.AiChargeRefundOrder; import com.ruoyi.ai.domain.AiChargeRefundOrder;
/** /**
* 充值/退款订单 Mapper * 团队部门充值退款订单Mapper接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface AiChargeRefundOrderMapper extends BaseMapper<AiChargeRefundOrder> { public interface AiChargeRefundOrderMapper extends BaseMapper<AiChargeRefundOrder> {
/**
* 列表关联团队名称支持按 deptName 模糊查
*/
List<AiChargeRefundOrder> selectAiChargeRefundOrderList(AiChargeRefundOrder query);
AiChargeRefundOrder selectAiChargeRefundOrderById(Long id);
int deleteAiChargeRefundOrderByIds(Long[] ids);
} }

View File

@ -1,10 +1,15 @@
package com.ruoyi.ai.mapper; package com.ruoyi.ai.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.ai.domain.AiDeptArkConfig; import com.ruoyi.ai.domain.AiDeptArkConfig;
/** /**
* 部门方舟配置 Mapper * 团队部门对应火山引擎配置Mapper接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface AiDeptArkConfigMapper extends BaseMapper<AiDeptArkConfig> { public interface AiDeptArkConfigMapper extends BaseMapper<AiDeptArkConfig> {
} }

View File

@ -1,10 +1,15 @@
package com.ruoyi.ai.mapper; package com.ruoyi.ai.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord; import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
/** /**
* 集团余额变动记录 Mapper * 团队部门余额变动Mapper接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface AiGroupBalanceChangeRecordMapper extends BaseMapper<AiGroupBalanceChangeRecord> { public interface AiGroupBalanceChangeRecordMapper extends BaseMapper<AiGroupBalanceChangeRecord> {
} }

View File

@ -1,19 +1,74 @@
package com.ruoyi.ai.mapper; package com.ruoyi.ai.mapper;
import java.util.List;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.ai.domain.AiOrder; import com.ruoyi.ai.domain.AiOrder;
import com.ruoyi.ai.domain.AiVideoReportData; import com.ruoyi.ai.domain.AiVideoReportData;
import com.ruoyi.common.core.dto.DeptSummaryDTO; import com.ruoyi.system.domain.subteam.SubteamVideoMetrics;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Update;
/** /**
* 视频报表数据 Mapper * AI视频生成统计数据作为其他统计报的数据源Mapper接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface AiVideoReportDataMapper extends BaseMapper<AiVideoReportData> { public interface AiVideoReportDataMapper extends BaseMapper<AiVideoReportData> {
@Select("SELECT SUM(order_count) as order_count,SUM(score) as score FROM ai_video_report_data " + /**
" where dept_id=#{deptId} and date_key>=#{startHour} and date_key<=#{endHour}") * 团队每日消耗统计查询按天团队聚合
DeptSummaryDTO selectOneDeptSummaryData(Long deptId, String startHour, String endHour); *
* @param statDate 统计日期yyyyMMdd
* @param deptId 团队部门ID精确匹配
* @return 聚合结果
*/
List<AiVideoReportData> selectTeamDailyConsumeList(@Param("statDate") String statDate,
@Param("deptId") Long deptId);
/**
* 按团队部门日期聚合团队后台消耗统计
*/
List<AiVideoReportData> selectTeamDailyConsumeByDeptId(@Param("statDate") String statDate,
@Param("deptId") Long deptId);
/**
* N date_key 日维度汇总消耗积分与成功订单数
*/
SubteamVideoMetrics selectDeptVideoMetricsBetween(@Param("deptId") Long deptId,
@Param("startDay") String startDay,
@Param("endDay") String endDay);
/**
* date_key, dept_id, user_id聚合累加视频消耗统计
*
* @param dateKey 小时KeyyyyyMMddHH
* @param deptId 部门ID
* @param userId 用户ID
* @param score 消耗积分增量
* @param orderCount 订单数增量
* @param useTokens 三方tokens增量
* @return 影响行数
*/
int upsertVideoConsumeIncrement(@Param("dateKey") String dateKey,
@Param("deptId") Long deptId,
@Param("userId") Long userId,
@Param("score") BigDecimal score,
@Param("orderCount") Long orderCount,
@Param("useTokens") Long useTokens);
/**
* date_key, dept_id, user_id=0聚合累加充值积分统计
*
* @param dateKey 小时KeyyyyyMMddHH
* @param deptId 部门ID
* @param rechargeScore 充值积分增量
* @return 影响行数
*/
int upsertRechargeScoreIncrement(@Param("dateKey") String dateKey,
@Param("deptId") Long deptId,
@Param("rechargeScore") BigDecimal rechargeScore);
void addNewOrderReportData(AiOrder aiOrder); void addNewOrderReportData(AiOrder aiOrder);
} }

View File

@ -1,17 +1,71 @@
package com.ruoyi.ai.service; package com.ruoyi.ai.service;
import java.util.List;
import com.ruoyi.ai.domain.AiChargeRefundOrder; import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/** /**
* 充值/退款订单 Service * 团队部门充值退款订单Service接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface IAiChargeRefundOrderService { public interface IAiChargeRefundOrderService {
AiChargeRefundOrder selectById(Long id); /**
* 查询团队部门充值退款订单
*
* @param id 团队部门充值退款订单主键
* @return 团队部门充值退款订单
*/
AiChargeRefundOrder selectAiChargeRefundOrderById(Long id);
int insert(AiChargeRefundOrder entity); /**
* 查询团队部门充值退款订单列表
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 团队部门充值退款订单集合
*/
List<AiChargeRefundOrder> selectAiChargeRefundOrderList(AiChargeRefundOrder aiChargeRefundOrder);
int updateById(AiChargeRefundOrder entity); /**
* 分页查询团队部门充值退款订单列表
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 团队部门充值退款订单集合
*/
IPage<AiChargeRefundOrder> selectAiChargeRefundOrderPage(Page page, AiChargeRefundOrder aiChargeRefundOrder);
int deleteById(Long id); /**
* 新增团队部门充值退款订单
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 结果
*/
int insertAiChargeRefundOrder(AiChargeRefundOrder aiChargeRefundOrder);
/**
* 修改团队部门充值退款订单
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 结果
*/
int updateAiChargeRefundOrder(AiChargeRefundOrder aiChargeRefundOrder);
/**
* 批量删除团队部门充值退款订单
*
* @param ids 需要删除的团队部门充值退款订单主键集合
* @return 结果
*/
int deleteAiChargeRefundOrderByIds(Long[] ids);
/**
* 删除团队部门充值退款订单信息
*
* @param id 团队部门充值退款订单主键
* @return 结果
*/
int deleteAiChargeRefundOrderById(Long id);
} }

View File

@ -1,17 +1,71 @@
package com.ruoyi.ai.service; package com.ruoyi.ai.service;
import java.util.List;
import com.ruoyi.ai.domain.AiDeptArkConfig; import com.ruoyi.ai.domain.AiDeptArkConfig;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/** /**
* 部门方舟配置 Service * 团队部门对应火山引擎配置Service接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface IAiDeptArkConfigService { public interface IAiDeptArkConfigService {
AiDeptArkConfig selectById(Long id); /**
* 查询团队部门对应火山引擎配置
*
* @param id 团队部门对应火山引擎配置主键
* @return 团队部门对应火山引擎配置
*/
AiDeptArkConfig selectAiDeptArkConfigById(String id);
int insert(AiDeptArkConfig entity); /**
* 查询团队部门对应火山引擎配置列表
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 团队部门对应火山引擎配置集合
*/
List<AiDeptArkConfig> selectAiDeptArkConfigList(AiDeptArkConfig aiDeptArkConfig);
int updateById(AiDeptArkConfig entity); /**
* 分页查询团队部门对应火山引擎配置列表
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 团队部门对应火山引擎配置集合
*/
IPage<AiDeptArkConfig> selectAiDeptArkConfigPage(Page page, AiDeptArkConfig aiDeptArkConfig);
int deleteById(Long id); /**
* 新增团队部门对应火山引擎配置
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 结果
*/
int insertAiDeptArkConfig(AiDeptArkConfig aiDeptArkConfig);
/**
* 修改团队部门对应火山引擎配置
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 结果
*/
int updateAiDeptArkConfig(AiDeptArkConfig aiDeptArkConfig);
/**
* 批量删除团队部门对应火山引擎配置
*
* @param ids 需要删除的团队部门对应火山引擎配置主键集合
* @return 结果
*/
int deleteAiDeptArkConfigByIds(String[] ids);
/**
* 删除团队部门对应火山引擎配置信息
*
* @param id 团队部门对应火山引擎配置主键
* @return 结果
*/
int deleteAiDeptArkConfigById(String id);
} }

View File

@ -1,17 +1,71 @@
package com.ruoyi.ai.service; package com.ruoyi.ai.service;
import java.util.List;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord; import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/** /**
* 集团余额变动记录 Service * 团队部门余额变动Service接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface IAiGroupBalanceChangeRecordService { public interface IAiGroupBalanceChangeRecordService {
AiGroupBalanceChangeRecord selectById(Long id); /**
* 查询团队部门余额变动
*
* @param id 团队部门余额变动主键
* @return 团队部门余额变动
*/
AiGroupBalanceChangeRecord selectAiGroupBalanceChangeRecordById(String id);
int insert(AiGroupBalanceChangeRecord entity); /**
* 查询团队部门余额变动列表
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 团队部门余额变动集合
*/
List<AiGroupBalanceChangeRecord> selectAiGroupBalanceChangeRecordList(AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord);
int updateById(AiGroupBalanceChangeRecord entity); /**
* 分页查询团队部门余额变动列表
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 团队部门余额变动集合
*/
IPage<AiGroupBalanceChangeRecord> selectAiGroupBalanceChangeRecordPage(Page page, AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord);
int deleteById(Long id); /**
* 新增团队部门余额变动
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 结果
*/
int insertAiGroupBalanceChangeRecord(AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord);
/**
* 修改团队部门余额变动
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 结果
*/
int updateAiGroupBalanceChangeRecord(AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord);
/**
* 批量删除团队部门余额变动
*
* @param ids 需要删除的团队部门余额变动主键集合
* @return 结果
*/
int deleteAiGroupBalanceChangeRecordByIds(String[] ids);
/**
* 删除团队部门余额变动信息
*
* @param id 团队部门余额变动主键
* @return 结果
*/
int deleteAiGroupBalanceChangeRecordById(String id);
} }

View File

@ -1,17 +1,107 @@
package com.ruoyi.ai.service; package com.ruoyi.ai.service;
import com.ruoyi.ai.domain.AiVideoReportData; import java.util.List;
import com.ruoyi.common.core.dto.DeptSummaryDTO; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import com.ruoyi.ai.domain.AiVideoReportData;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/** /**
* 视频报表数据 Service * AI视频生成统计数据作为其他统计报的数据源Service接口
*
* @author shi
* @date 2026-04-17
*/ */
public interface IAiVideoReportDataService { public interface IAiVideoReportDataService {
AiVideoReportData selectById(Long id);
int insert(AiVideoReportData entity); /**
* 查询AI视频生成统计数据作为其他统计报的数据源
*
* @param id AI视频生成统计数据作为其他统计报的数据源主键
* @return AI视频生成统计数据作为其他统计报的数据源
*/
AiVideoReportData selectAiVideoReportDataById(String id);
DeptSummaryDTO getSevenDayDeptSummaryData(Long deptId); /**
* 查询AI视频生成统计数据作为其他统计报的数据源列表
*
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return AI视频生成统计数据作为其他统计报的数据源集合
*/
List<AiVideoReportData> selectAiVideoReportDataList(AiVideoReportData aiVideoReportData);
/**
* 分页查询AI视频生成统计数据作为其他统计报的数据源列表
*
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return AI视频生成统计数据作为其他统计报的数据源集合
*/
IPage<AiVideoReportData> selectAiVideoReportDataPage(Page page, AiVideoReportData aiVideoReportData);
/**
* 新增AI视频生成统计数据作为其他统计报的数据源
*
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return 结果
*/
int insertAiVideoReportData(AiVideoReportData aiVideoReportData);
/**
* 修改AI视频生成统计数据作为其他统计报的数据源
*
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return 结果
*/
int updateAiVideoReportData(AiVideoReportData aiVideoReportData);
/**
* 批量删除AI视频生成统计数据作为其他统计报的数据源
*
* @param ids 需要删除的AI视频生成统计数据作为其他统计报的数据源主键集合
* @return 结果
*/
int deleteAiVideoReportDataByIds(String[] ids);
/**
* 删除AI视频生成统计数据作为其他统计报的数据源信息
*
* @param id AI视频生成统计数据作为其他统计报的数据源主键
* @return 结果
*/
int deleteAiVideoReportDataById(String id);
/**
* 团队每日消耗统计查询按天团队聚合
*
* @param statDate 统计日期yyyyMMdd必填
* @param deptId 团队部门ID必填精确匹配
* @return 聚合后的统计数据
*/
List<AiVideoReportData> selectTeamDailyConsumeList(String statDate, Long deptId);
/**
* 团队每日消耗按部门 ID团队后台
*/
List<AiVideoReportData> selectTeamDailyConsumeByDeptId(String statDate, Long deptId);
/**
* 按视频订单成功结果回写统计来源表按小时部门账号聚合累加
*
* @param createTime 任务创建时间
* @param deptId 部门ID
* @param userId 账号ID
* @param score 消耗积分增量
* @param useTokens 三方tokens增量
*/
void syncVideoConsumeIncrement(Date createTime, Long deptId, Long userId, BigDecimal score, Long useTokens);
/**
* 按充值/退款成功结果回写统计来源表按小时部门聚合累加
*
* @param createTime 订单创建时间
* @param deptId 部门ID
* @param rechargeScore 实际充值积分增量充值为正退款为负
*/
void syncRechargeScoreIncrement(Date createTime, Long deptId, BigDecimal rechargeScore);
} }

View File

@ -1,14 +1,30 @@
package com.ruoyi.ai.service.impl; package com.ruoyi.ai.service.impl;
import java.util.List;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.enums.ChargeRefundOrderStatusType;
import com.ruoyi.common.enums.ChargeRefundOrderType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.ruoyi.ai.mapper.AiChargeRefundOrderMapper; import com.ruoyi.ai.mapper.AiChargeRefundOrderMapper;
import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.ruoyi.ai.service.IAiVideoReportDataService;
import com.ruoyi.ai.service.IAiChargeRefundOrderService; import com.ruoyi.ai.service.IAiChargeRefundOrderService;
/** /**
* 充值/退款订单 Service 实现 * 团队部门充值退款订单Service业务层处理
*
* @author shi
* @date 2026-04-17
*/ */
@Service @Service
public class AiChargeRefundOrderServiceImpl implements IAiChargeRefundOrderService { public class AiChargeRefundOrderServiceImpl implements IAiChargeRefundOrderService {
@ -16,23 +32,149 @@ public class AiChargeRefundOrderServiceImpl implements IAiChargeRefundOrderServi
@Autowired @Autowired
private AiChargeRefundOrderMapper aiChargeRefundOrderMapper; private AiChargeRefundOrderMapper aiChargeRefundOrderMapper;
@Autowired
private IAiVideoReportDataService aiVideoReportDataService;
/**
* 查询团队部门充值退款订单
*
* @param id 团队部门充值退款订单主键
* @return 团队部门充值退款订单
*/
@Override @Override
public AiChargeRefundOrder selectById(Long id) { public AiChargeRefundOrder selectAiChargeRefundOrderById(Long id) {
return aiChargeRefundOrderMapper.selectById(id); return aiChargeRefundOrderMapper.selectAiChargeRefundOrderById(id);
} }
/**
* 查询团队部门充值退款订单列表
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 团队部门充值退款订单
*/
@Override @Override
public int insert(AiChargeRefundOrder entity) { public List<AiChargeRefundOrder> selectAiChargeRefundOrderList(AiChargeRefundOrder aiChargeRefundOrder) {
return aiChargeRefundOrderMapper.insert(entity); return aiChargeRefundOrderMapper.selectAiChargeRefundOrderList(aiChargeRefundOrder);
} }
/**
* 分页查询团队部门充值退款订单列表
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 团队部门充值退款订单
*/
@Override @Override
public int updateById(AiChargeRefundOrder entity) { public IPage<AiChargeRefundOrder> selectAiChargeRefundOrderPage(Page page, AiChargeRefundOrder aiChargeRefundOrder) {
return aiChargeRefundOrderMapper.updateById(entity); if (aiChargeRefundOrder != null) {
aiChargeRefundOrder.setDeptName(null);
}
LambdaQueryWrapper<AiChargeRefundOrder> query = Wrappers.lambdaQuery(aiChargeRefundOrder);
query.eq(AiChargeRefundOrder::getDelFlag, "0");
return aiChargeRefundOrderMapper.selectPage(page, query);
} }
/**
* 新增团队部门充值退款订单
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 结果
*/
@Override @Override
public int deleteById(Long id) { public int insertAiChargeRefundOrder(AiChargeRefundOrder aiChargeRefundOrder) {
validateChargeAmount(aiChargeRefundOrder);
if (StringUtils.isEmpty(aiChargeRefundOrder.getOrderNum())) {
aiChargeRefundOrder.setOrderNum("CR" + IdUtils.fastSimpleUUID());
}
if (aiChargeRefundOrder.getStatus() == null) {
aiChargeRefundOrder.setStatus(ChargeRefundOrderStatusType.SUCCESS.getCode());
}
aiChargeRefundOrder.setDelFlag("0");
aiChargeRefundOrder.setCreateBy(SecurityUtils.getUsername());
aiChargeRefundOrder.setCreateTime(DateUtils.getNowDate());
int rows = aiChargeRefundOrderMapper.insert(aiChargeRefundOrder);
syncRechargeReportData(aiChargeRefundOrder);
return rows;
}
/**
* 修改团队部门充值退款订单
*
* @param aiChargeRefundOrder 团队部门充值退款订单
* @return 结果
*/
@Override
public int updateAiChargeRefundOrder(AiChargeRefundOrder aiChargeRefundOrder) {
validateChargeAmount(aiChargeRefundOrder);
aiChargeRefundOrder.setUpdateTime(DateUtils.getNowDate());
int rows = aiChargeRefundOrderMapper.updateById(aiChargeRefundOrder);
if (rows > 0) {
AiChargeRefundOrder fresh = aiChargeRefundOrderMapper.selectById(aiChargeRefundOrder.getId());
syncRechargeReportData(fresh);
}
return rows;
}
/**
* 批量删除团队部门充值退款订单
*
* @param ids 需要删除的团队部门充值退款订单主键
* @return 结果
*/
@Override
public int deleteAiChargeRefundOrderByIds(Long[] ids)
{
return aiChargeRefundOrderMapper.deleteAiChargeRefundOrderByIds(ids);
}
/**
* 删除团队部门充值退款订单信息
*
* @param id 团队部门充值退款订单主键
* @return 结果
*/
@Override
public int deleteAiChargeRefundOrderById(Long id)
{
return aiChargeRefundOrderMapper.deleteById(id); return aiChargeRefundOrderMapper.deleteById(id);
} }
/**
* 充值/退款单完成后订单创建时间同步到团队统计来源表
*
* @param order 充值退款订单
*/
private void syncRechargeReportData(AiChargeRefundOrder order) {
if (order == null || order.getCreateTime() == null || order.getDeptId() == null) {
return;
}
if (order.getStatus() == null || order.getStatus() != ChargeRefundOrderStatusType.SUCCESS.getCode()) {
return;
}
if (order.getOrderType() == null || order.getAmount() == null) {
return;
}
BigDecimal rechargeScore;
int type = order.getOrderType().intValue();
if (type == ChargeRefundOrderType.REFUND.getCode()) {
rechargeScore = order.getAmount().negate();
} else {
rechargeScore = order.getAmount();
}
aiVideoReportDataService.syncRechargeScoreIncrement(order.getCreateTime(), order.getDeptId(), rechargeScore);
}
/**
* 充值退款积分须为非负数手动修改允许正负
*/
private void validateChargeAmount(AiChargeRefundOrder o) {
if (o.getOrderType() == null || o.getAmount() == null) {
return;
}
int t = o.getOrderType().intValue();
if (t == ChargeRefundOrderType.CHARGE.getCode() || t == ChargeRefundOrderType.REFUND.getCode()) {
if (o.getAmount().compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("充值、退款类型的积分须填写非负数");
}
}
}
} }

View File

@ -1,14 +1,23 @@
package com.ruoyi.ai.service.impl; package com.ruoyi.ai.service.impl;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.ai.domain.AiDeptArkConfig;
import com.ruoyi.ai.mapper.AiDeptArkConfigMapper; import com.ruoyi.ai.mapper.AiDeptArkConfigMapper;
import com.ruoyi.ai.domain.AiDeptArkConfig;
import com.ruoyi.ai.service.IAiDeptArkConfigService; import com.ruoyi.ai.service.IAiDeptArkConfigService;
/** /**
* 部门方舟配置 Service 实现 * 团队部门对应火山引擎配置Service业务层处理
*
* @author shi
* @date 2026-04-17
*/ */
@Service @Service
public class AiDeptArkConfigServiceImpl implements IAiDeptArkConfigService { public class AiDeptArkConfigServiceImpl implements IAiDeptArkConfigService {
@ -16,23 +25,89 @@ public class AiDeptArkConfigServiceImpl implements IAiDeptArkConfigService {
@Autowired @Autowired
private AiDeptArkConfigMapper aiDeptArkConfigMapper; private AiDeptArkConfigMapper aiDeptArkConfigMapper;
/**
* 查询团队部门对应火山引擎配置
*
* @param id 团队部门对应火山引擎配置主键
* @return 团队部门对应火山引擎配置
*/
@Override @Override
public AiDeptArkConfig selectById(Long id) { public AiDeptArkConfig selectAiDeptArkConfigById(String id) {
return aiDeptArkConfigMapper.selectById(id); return aiDeptArkConfigMapper.selectById(id);
} }
/**
* 查询团队部门对应火山引擎配置列表
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 团队部门对应火山引擎配置
*/
@Override @Override
public int insert(AiDeptArkConfig entity) { public List<AiDeptArkConfig> selectAiDeptArkConfigList(AiDeptArkConfig aiDeptArkConfig) {
return aiDeptArkConfigMapper.insert(entity); LambdaQueryWrapper<AiDeptArkConfig> query = Wrappers.lambdaQuery(aiDeptArkConfig);
query.orderByDesc(AiDeptArkConfig::getId);
return aiDeptArkConfigMapper.selectList(query);
} }
/**
* 分页查询团队部门对应火山引擎配置列表
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 团队部门对应火山引擎配置
*/
@Override @Override
public int updateById(AiDeptArkConfig entity) { public IPage<AiDeptArkConfig> selectAiDeptArkConfigPage(Page page, AiDeptArkConfig aiDeptArkConfig) {
return aiDeptArkConfigMapper.updateById(entity); LambdaQueryWrapper<AiDeptArkConfig> query = Wrappers.lambdaQuery(aiDeptArkConfig);
return aiDeptArkConfigMapper.selectPage(page, query);
} }
/**
* 新增团队部门对应火山引擎配置
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 结果
*/
@Override @Override
public int deleteById(Long id) { public int insertAiDeptArkConfig(AiDeptArkConfig aiDeptArkConfig) {
aiDeptArkConfig.setCreateBy(SecurityUtils.getUsername());
aiDeptArkConfig.setCreateTime(DateUtils.getNowDate());
return aiDeptArkConfigMapper.insert(aiDeptArkConfig);
}
/**
* 修改团队部门对应火山引擎配置
*
* @param aiDeptArkConfig 团队部门对应火山引擎配置
* @return 结果
*/
@Override
public int updateAiDeptArkConfig(AiDeptArkConfig aiDeptArkConfig) {
aiDeptArkConfig.setUpdateBy(SecurityUtils.getUsername());
aiDeptArkConfig.setUpdateTime(DateUtils.getNowDate());
return aiDeptArkConfigMapper.updateById(aiDeptArkConfig);
}
/**
* 批量删除团队部门对应火山引擎配置
*
* @param ids 需要删除的团队部门对应火山引擎配置主键
* @return 结果
*/
@Override
public int deleteAiDeptArkConfigByIds(String[] ids)
{
return aiDeptArkConfigMapper.deleteBatchIds(java.util.Arrays.asList(ids));
}
/**
* 删除团队部门对应火山引擎配置信息
*
* @param id 团队部门对应火山引擎配置主键
* @return 结果
*/
@Override
public int deleteAiDeptArkConfigById(String id)
{
return aiDeptArkConfigMapper.deleteById(id); return aiDeptArkConfigMapper.deleteById(id);
} }
} }

View File

@ -1,14 +1,22 @@
package com.ruoyi.ai.service.impl; package com.ruoyi.ai.service.impl;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.ruoyi.ai.mapper.AiGroupBalanceChangeRecordMapper; import com.ruoyi.ai.mapper.AiGroupBalanceChangeRecordMapper;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.ruoyi.ai.service.IAiGroupBalanceChangeRecordService; import com.ruoyi.ai.service.IAiGroupBalanceChangeRecordService;
/** /**
* 集团余额变动记录 Service 实现 * 团队部门余额变动Service业务层处理
*
* @author shi
* @date 2026-04-17
*/ */
@Service @Service
public class AiGroupBalanceChangeRecordServiceImpl implements IAiGroupBalanceChangeRecordService { public class AiGroupBalanceChangeRecordServiceImpl implements IAiGroupBalanceChangeRecordService {
@ -16,23 +24,87 @@ public class AiGroupBalanceChangeRecordServiceImpl implements IAiGroupBalanceCha
@Autowired @Autowired
private AiGroupBalanceChangeRecordMapper aiGroupBalanceChangeRecordMapper; private AiGroupBalanceChangeRecordMapper aiGroupBalanceChangeRecordMapper;
/**
* 查询团队部门余额变动
*
* @param id 团队部门余额变动主键
* @return 团队部门余额变动
*/
@Override @Override
public AiGroupBalanceChangeRecord selectById(Long id) { public AiGroupBalanceChangeRecord selectAiGroupBalanceChangeRecordById(String id) {
return aiGroupBalanceChangeRecordMapper.selectById(id); return aiGroupBalanceChangeRecordMapper.selectById(id);
} }
/**
* 查询团队部门余额变动列表
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 团队部门余额变动
*/
@Override @Override
public int insert(AiGroupBalanceChangeRecord entity) { public List<AiGroupBalanceChangeRecord> selectAiGroupBalanceChangeRecordList(AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord) {
return aiGroupBalanceChangeRecordMapper.insert(entity); LambdaQueryWrapper<AiGroupBalanceChangeRecord> query = Wrappers.lambdaQuery(aiGroupBalanceChangeRecord);
query.orderByDesc(AiGroupBalanceChangeRecord::getId);
return aiGroupBalanceChangeRecordMapper.selectList(query);
} }
/**
* 分页查询团队部门余额变动列表
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 团队部门余额变动
*/
@Override @Override
public int updateById(AiGroupBalanceChangeRecord entity) { public IPage<AiGroupBalanceChangeRecord> selectAiGroupBalanceChangeRecordPage(Page page, AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord) {
return aiGroupBalanceChangeRecordMapper.updateById(entity); LambdaQueryWrapper<AiGroupBalanceChangeRecord> query = Wrappers.lambdaQuery(aiGroupBalanceChangeRecord);
return aiGroupBalanceChangeRecordMapper.selectPage(page, query);
} }
/**
* 新增团队部门余额变动
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 结果
*/
@Override @Override
public int deleteById(Long id) { public int insertAiGroupBalanceChangeRecord(AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord) {
aiGroupBalanceChangeRecord.setCreateTime(DateUtils.getNowDate());
return aiGroupBalanceChangeRecordMapper.insert(aiGroupBalanceChangeRecord);
}
/**
* 修改团队部门余额变动
*
* @param aiGroupBalanceChangeRecord 团队部门余额变动
* @return 结果
*/
@Override
public int updateAiGroupBalanceChangeRecord(AiGroupBalanceChangeRecord aiGroupBalanceChangeRecord) {
aiGroupBalanceChangeRecord.setUpdateTime(DateUtils.getNowDate());
return aiGroupBalanceChangeRecordMapper.updateById(aiGroupBalanceChangeRecord);
}
/**
* 批量删除团队部门余额变动
*
* @param ids 需要删除的团队部门余额变动主键
* @return 结果
*/
@Override
public int deleteAiGroupBalanceChangeRecordByIds(String[] ids)
{
return aiGroupBalanceChangeRecordMapper.deleteBatchIds(java.util.Arrays.asList(ids));
}
/**
* 删除团队部门余额变动信息
*
* @param id 团队部门余额变动主键
* @return 结果
*/
@Override
public int deleteAiGroupBalanceChangeRecordById(String id)
{
return aiGroupBalanceChangeRecordMapper.deleteById(id); return aiGroupBalanceChangeRecordMapper.deleteById(id);
} }
} }

View File

@ -63,6 +63,9 @@ public class AiOrderServiceImpl implements IAiOrderService {
@Autowired @Autowired
private IByteDeptApiKeyService byteDeptApiKeyService; private IByteDeptApiKeyService byteDeptApiKeyService;
@Autowired
private IAiVideoReportDataService aiVideoReportDataService;
// 流水表任务成功时回补 // 流水表任务成功时回补
private static final String TASK_SUCCESS_BACK_FILL_REMARK = "order.number.generation.successbackfill"; private static final String TASK_SUCCESS_BACK_FILL_REMARK = "order.number.generation.successbackfill";
@ -248,6 +251,7 @@ public class AiOrderServiceImpl implements IAiOrderService {
public void orderSuccess(AiOrder aiOrder) { public void orderSuccess(AiOrder aiOrder) {
aiOrder.setStatus(1); aiOrder.setStatus(1);
aiOrderMapper.updateById(aiOrder); aiOrderMapper.updateById(aiOrder);
syncVideoReportData(aiOrder);
AiStatistics aiStatistics = new AiStatistics(); AiStatistics aiStatistics = new AiStatistics();
aiStatistics.setSource(aiOrder.getSource()); aiStatistics.setSource(aiOrder.getSource());
aiStatistics.setGenerateCount(1L); aiStatistics.setGenerateCount(1L);
@ -356,4 +360,18 @@ public class AiOrderServiceImpl implements IAiOrderService {
// BalanceChangerConstants.QUICK_VIDEO_GENERATION, TASK_SUCCESS_BALANCE_REMARK); // BalanceChangerConstants.QUICK_VIDEO_GENERATION, TASK_SUCCESS_BALANCE_REMARK);
return AjaxResult.success("callback success"); return AjaxResult.success("callback success");
} }
/**
* 任务成功后按提交时间写入团队消耗统计来源表
*
* @param aiOrder 已成功订单
*/
private void syncVideoReportData(AiOrder aiOrder) {
if (aiOrder == null || aiOrder.getCreateTime() == null || aiOrder.getDeptId() == null || aiOrder.getUserId() == null) {
return;
}
BigDecimal score = aiOrder.getAmount() != null ? aiOrder.getAmount() : BigDecimal.ZERO;
Long useTokens = aiOrder.getTotalUsage() != null ? aiOrder.getTotalUsage().longValue() : 0L;
aiVideoReportDataService.syncVideoConsumeIncrement(aiOrder.getCreateTime(), aiOrder.getDeptId(), aiOrder.getUserId(), score, useTokens);
}
} }

View File

@ -303,7 +303,7 @@ public class AiUserServiceImpl implements IAiUserService {
private AiUser getUserByInvitationCode(String invitationCode) { private AiUser getUserByInvitationCode(String invitationCode) {
LambdaQueryWrapper<AiUser> query = Wrappers.lambdaQuery(); LambdaQueryWrapper<AiUser> query = Wrappers.lambdaQuery();
query.eq(AiUser::getInvitationCode, invitationCode); query.eq(AiUser::getInvitationCode, invitationCode);
query.eq(AiUser::getDelFlag, 0); query.eq(AiUser::getDelFlag, "0");
return aiUserMapper.selectOne(query); return aiUserMapper.selectOne(query);
} }

View File

@ -1,61 +1,141 @@
package com.ruoyi.ai.service.impl; package com.ruoyi.ai.service.impl;
import com.ruoyi.ai.domain.AiVideoReportData; import java.util.List;
import com.ruoyi.ai.mapper.AiVideoReportDataMapper; import java.math.BigDecimal;
import com.ruoyi.ai.service.IAiVideoReportDataService; import java.text.SimpleDateFormat;
import com.ruoyi.common.constant.RedisKey; import java.util.Date;
import com.ruoyi.common.core.dto.DeptSummaryDTO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.ai.mapper.AiVideoReportDataMapper;
import java.time.LocalDateTime; import com.ruoyi.ai.domain.AiVideoReportData;
import java.time.ZoneId; import com.ruoyi.ai.service.IAiVideoReportDataService;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
/** /**
* 视频报表数据 Service 实现 * AI视频生成统计数据作为其他统计报的数据源Service业务层处理
*
* @author shi
* @date 2026-04-17
*/ */
@Service @Service
public class AiVideoReportDataServiceImpl implements IAiVideoReportDataService { public class AiVideoReportDataServiceImpl implements IAiVideoReportDataService {
/** 与表字段 date_key 一致yyyy-MM-dd HH线程安全 */
private static final DateTimeFormatter DATE_KEY_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
@Autowired @Autowired
private AiVideoReportDataMapper videoReportDataMapper; private AiVideoReportDataMapper aiVideoReportDataMapper;
/**
* 查询AI视频生成统计数据作为其他统计报的数据源
*
* @param id AI视频生成统计数据作为其他统计报的数据源主键
* @return AI视频生成统计数据作为其他统计报的数据源
*/
@Override @Override
public AiVideoReportData selectById(Long id) { public AiVideoReportData selectAiVideoReportDataById(String id) {
return videoReportDataMapper.selectById(id); return aiVideoReportDataMapper.selectById(id);
}
@Override
public int insert(AiVideoReportData entity) {
return videoReportDataMapper.insert(entity);
} }
/** /**
* 部门近七日汇总数据 * 查询AI视频生成统计数据作为其他统计报的数据源列表
*
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return AI视频生成统计数据作为其他统计报的数据源
*/ */
@Override @Override
@Cacheable(cacheNames = RedisKey.CACHE_DEPT_SUMMARY, key = "#deptId") public List<AiVideoReportData> selectAiVideoReportDataList(AiVideoReportData aiVideoReportData) {
public DeptSummaryDTO getSevenDayDeptSummaryData(Long deptId) { LambdaQueryWrapper<AiVideoReportData> query = Wrappers.lambdaQuery(aiVideoReportData);
Date endTime = new Date(); query.orderByDesc(AiVideoReportData::getId);
// 获取今天的0点再减去7天 return aiVideoReportDataMapper.selectList(query);
Date todayZero = DateUtils.truncate(endTime, Calendar.DAY_OF_MONTH);
Date startTime = DateUtils.addDays(todayZero, -7);
String startHour = formatDateKey(startTime);
String endHour = formatDateKey(endTime);
return videoReportDataMapper.selectOneDeptSummaryData(deptId, startHour, endHour);
} }
private static String formatDateKey(Date date) { /**
LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); * 分页查询AI视频生成统计数据作为其他统计报的数据源列表
return DATE_KEY_FORMAT.format(ldt); *
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return AI视频生成统计数据作为其他统计报的数据源
*/
@Override
public IPage<AiVideoReportData> selectAiVideoReportDataPage(Page page, AiVideoReportData aiVideoReportData) {
LambdaQueryWrapper<AiVideoReportData> query = Wrappers.lambdaQuery(aiVideoReportData);
return aiVideoReportDataMapper.selectPage(page, query);
}
/**
* 新增AI视频生成统计数据作为其他统计报的数据源
*
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return 结果
*/
@Override
public int insertAiVideoReportData(AiVideoReportData aiVideoReportData) {
aiVideoReportData.setCreateTime(DateUtils.getNowDate());
return aiVideoReportDataMapper.insert(aiVideoReportData);
}
/**
* 修改AI视频生成统计数据作为其他统计报的数据源
*
* @param aiVideoReportData AI视频生成统计数据作为其他统计报的数据源
* @return 结果
*/
@Override
public int updateAiVideoReportData(AiVideoReportData aiVideoReportData) {
aiVideoReportData.setUpdateTime(DateUtils.getNowDate());
return aiVideoReportDataMapper.updateById(aiVideoReportData);
}
/**
* 批量删除AI视频生成统计数据作为其他统计报的数据源
*
* @param ids 需要删除的AI视频生成统计数据作为其他统计报的数据源主键
* @return 结果
*/
@Override
public int deleteAiVideoReportDataByIds(String[] ids)
{
return aiVideoReportDataMapper.deleteBatchIds(java.util.Arrays.asList(ids));
}
/**
* 删除AI视频生成统计数据作为其他统计报的数据源信息
*
* @param id AI视频生成统计数据作为其他统计报的数据源主键
* @return 结果
*/
@Override
public int deleteAiVideoReportDataById(String id)
{
return aiVideoReportDataMapper.deleteById(id);
}
@Override
public List<AiVideoReportData> selectTeamDailyConsumeList(String statDate, Long deptId) {
return aiVideoReportDataMapper.selectTeamDailyConsumeList(statDate, deptId);
}
@Override
public List<AiVideoReportData> selectTeamDailyConsumeByDeptId(String statDate, Long deptId) {
return aiVideoReportDataMapper.selectTeamDailyConsumeByDeptId(statDate, deptId);
}
@Override
public void syncVideoConsumeIncrement(Date createTime, Long deptId, Long userId, BigDecimal score, Long useTokens) {
if (createTime == null || deptId == null || userId == null || score == null || useTokens == null) {
return;
}
String dateKey = new SimpleDateFormat("yyyyMMddHH").format(createTime);
aiVideoReportDataMapper.upsertVideoConsumeIncrement(dateKey, deptId, userId, score, 1L, useTokens);
}
@Override
public void syncRechargeScoreIncrement(Date createTime, Long deptId, BigDecimal rechargeScore) {
if (createTime == null || deptId == null || rechargeScore == null) {
return;
}
String dateKey = new SimpleDateFormat("yyyyMMddHH").format(createTime);
aiVideoReportDataMapper.upsertRechargeScoreIncrement(dateKey, deptId, rechargeScore);
} }
} }

View File

@ -6,7 +6,6 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.UUID; import java.util.UUID;
import com.ruoyi.common.constant.BalanceChangerConstants;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -60,19 +59,19 @@ public class DeptChargeRefundServiceImpl implements IDeptChargeRefundService {
// 1) 手工入账订单状态直接为已完成 // 1) 手工入账订单状态直接为已完成
AiChargeRefundOrder order = new AiChargeRefundOrder(); AiChargeRefundOrder order = new AiChargeRefundOrder();
order.setDelFlag("0"); order.setDelFlag("0");
order.setCreateBy(SecurityUtils.getUserId()); order.setCreateBy(SecurityUtils.getUsername());
order.setCreateTime(now); order.setCreateTime(now);
order.setUpdateTime(now); order.setUpdateTime(now);
order.setOrderNum(orderNum); order.setOrderNum(orderNum);
order.setDeptId(request.getDeptId()); order.setDeptId(request.getDeptId());
order.setOrderType(request.getOrderType()); order.setOrderType(Long.valueOf(request.getOrderType()));
// 金额两位小数向零截断非四舍五入避免入账金额被抬高 // 金额两位小数向零截断非四舍五入避免入账金额被抬高
order.setMoney(request.getMoney().setScale(2, RoundingMode.DOWN)); order.setMoney(request.getMoney().setScale(2, RoundingMode.DOWN));
order.setAmount(amountBd); order.setAmount(amountBd);
order.setRemark(request.getRemark()); order.setRemark(request.getRemark());
order.setStatus(ChargeRefundOrderStatusType.SUCCESS.getCode()); order.setStatus(ChargeRefundOrderStatusType.SUCCESS.getCode());
aiChargeRefundOrderService.insert(order); aiChargeRefundOrderService.insertAiChargeRefundOrder(order);
// 2) 原子更新部门积分退款时 rows==0 表示余额不足或部门无效依赖事务回滚撤销上一 INSERT // 2) 原子更新部门积分退款时 rows==0 表示余额不足或部门无效依赖事务回滚撤销上一 INSERT
int rows; int rows;
@ -102,22 +101,22 @@ public class DeptChargeRefundServiceImpl implements IDeptChargeRefundService {
record.setRelationOrderNo(orderNum); record.setRelationOrderNo(orderNum);
record.setDeptId(request.getDeptId()); record.setDeptId(request.getDeptId());
record.setType(orderType == ChargeRefundOrderType.CHARGE record.setType(orderType == ChargeRefundOrderType.CHARGE
? GroupBalanceChangeType.RECHARGE.getCode() ? Long.valueOf(GroupBalanceChangeType.RECHARGE.getCode())
: GroupBalanceChangeType.REFUND.getCode()); : Long.valueOf(GroupBalanceChangeType.REFUND.getCode()));
record.setChangeAmount(signedChange); record.setChangeAmount(signedChange);
record.setResultAmount(dept.getBalance()); record.setResultAmount(dept.getBalance());
record.setRemark(request.getRemark()); record.setRemark(request.getRemark());
record.setCreateBy(SecurityUtils.getUserId()); record.setCreateBy(SecurityUtils.getUsername());
record.setCreateTime(now); record.setCreateTime(now);
record.setUpdateTime(now); record.setUpdateTime(now);
aiGroupBalanceChangeRecordService.insert(record); aiGroupBalanceChangeRecordService.insertAiGroupBalanceChangeRecord(record);
} }
/** 业务单号前缀 + 日序 + 随机后缀,保证与流水 relation_order_no 一致。 */ /** 业务单号前缀 + 日序 + 随机后缀,保证与流水 relation_order_no 一致。 */
private static String buildOrderNum() { private static String buildOrderNum() {
String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8); String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date()); String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
return BalanceChangerConstants.OrderNoPrefix.RECHARGE_REFUND_PREFIX + dateTime + uuid; return "CG" + dateTime + uuid;
} }
} }

View File

@ -19,7 +19,6 @@ import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.request.ai.AiUserDeptScoreRequest; import com.ruoyi.common.core.request.ai.AiUserDeptScoreRequest;
import com.ruoyi.common.enums.GroupBalanceChangeType; import com.ruoyi.common.enums.GroupBalanceChangeType;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysDeptService;
@ -78,7 +77,9 @@ public class DeptUserScoreTransferTxService {
} }
String orderNum = buildOrderNum(); String orderNum = buildOrderNum();
aiUserService.addUserBalance(orderNum, user.getId(), amount.negate(), BalanceChangerConstants.DEPT_SCORE_RECLAIM, request.getRemark()); String remark = buildRemark(request.getRemark(), "用户积分回收至部门");
aiUserService.addUserBalance(orderNum, user.getId(), amount.negate(), BalanceChangerConstants.DEPT_SCORE_RECLAIM, remark);
int rows = deptService.addDeptBalance(deptId, amount); int rows = deptService.addDeptBalance(deptId, amount);
if (rows == 0) { if (rows == 0) {
@ -86,7 +87,7 @@ public class DeptUserScoreTransferTxService {
} }
BigDecimal deptBalAfter = getDeptBalance(deptId); BigDecimal deptBalAfter = getDeptBalance(deptId);
insertGroupRecord(orderNum, deptId, GroupBalanceChangeType.RECLAIM.getCode(), amount, deptBalAfter, request.getRemark()); insertGroupRecord(orderNum, deptId, GroupBalanceChangeType.RECLAIM.getCode(), amount, deptBalAfter, remark);
} }
private AiUser requireUserWithDept(Long id) { private AiUser requireUserWithDept(Long id) {
@ -131,17 +132,17 @@ public class DeptUserScoreTransferTxService {
AiGroupBalanceChangeRecord record = new AiGroupBalanceChangeRecord(); AiGroupBalanceChangeRecord record = new AiGroupBalanceChangeRecord();
record.setRelationOrderNo(orderNum); record.setRelationOrderNo(orderNum);
record.setDeptId(deptId); record.setDeptId(deptId);
record.setType(type); record.setType(Long.valueOf(type));
record.setChangeAmount(signedChange); record.setChangeAmount(signedChange);
record.setResultAmount(resultBalance); record.setResultAmount(resultBalance);
record.setRemark(remark); record.setRemark(remark);
record.setCreateBy(SecurityUtils.getUserId()); record.setCreateBy(SecurityUtils.getUsername());
aiGroupBalanceChangeRecordService.insert(record); aiGroupBalanceChangeRecordService.insertAiGroupBalanceChangeRecord(record);
} }
private static String buildOrderNum() { private static String buildOrderNum() {
String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8); String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date()); String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
return BalanceChangerConstants.OrderNoPrefix.ISSUE_RECLAIM_PREFIX + dateTime + uuid; return "DU" + dateTime + uuid;
} }
} }

View File

@ -0,0 +1,30 @@
package com.ruoyi.system.domain.subteam;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
/**
* 团队后台首页概览
*/
@Data
public class SubteamOverviewVO implements Serializable {
private static final long serialVersionUID = 1L;
private Long deptId;
private String deptName;
/** 团队积分余额sys_dept.balance */
private BigDecimal balance;
/** AI 用户数量ai_user实时 */
private Integer aiUserCount;
/** 近七日消耗积分ai_video_report_data可能来自缓存 */
private BigDecimal last7DaysConsumeScore;
/** 近七日成功订单数ai_video_report_data可能来自缓存 */
private Long last7DaysOrderCount;
}

View File

@ -0,0 +1,18 @@
package com.ruoyi.system.domain.subteam;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
/**
* 团队视频统计汇总近七日消耗/订单数
*/
@Data
public class SubteamVideoMetrics implements Serializable {
private static final long serialVersionUID = 1L;
private BigDecimal consumeScore;
private Long orderCount;
}

View File

@ -421,13 +421,13 @@ public class SysDeptServiceImpl implements ISysDeptService
AiGroupBalanceChangeRecord record = new AiGroupBalanceChangeRecord(); AiGroupBalanceChangeRecord record = new AiGroupBalanceChangeRecord();
record.setRelationOrderNo(null); record.setRelationOrderNo(null);
record.setDeptId(request.getDeptId()); record.setDeptId(request.getDeptId());
record.setType(GroupBalanceChangeType.MANUAL_ADJUST.getCode()); record.setType(Long.valueOf(GroupBalanceChangeType.MANUAL_ADJUST.getCode()));
record.setChangeAmount(delta); record.setChangeAmount(delta);
record.setResultAmount(dept.getBalance()); record.setResultAmount(dept.getBalance());
record.setRemark(request.getRemark()); record.setRemark(request.getRemark());
record.setCreateBy(SecurityUtils.getUserId()); record.setCreateBy(SecurityUtils.getUsername());
aiGroupBalanceChangeRecordService.insert(record); aiGroupBalanceChangeRecordService.insertAiGroupBalanceChangeRecord(record);
} }
/** /**

View File

@ -0,0 +1,21 @@
package com.ruoyi.system.service.subteam;
import java.util.List;
import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.ruoyi.ai.domain.AiOrder;
import com.ruoyi.ai.domain.AiVideoReportData;
public interface ISubteamDataQueryService {
List<AiOrder> selectVideoOrders(AiOrder query);
List<AiChargeRefundOrder> selectChargeRefundOrders(AiChargeRefundOrder query);
List<AiBalanceChangeRecord> selectUserBalanceRecords(AiBalanceChangeRecord query);
List<AiGroupBalanceChangeRecord> selectGroupBalanceRecords(AiGroupBalanceChangeRecord query);
List<AiVideoReportData> selectTeamDailyConsume(String statDate);
}

View File

@ -0,0 +1,8 @@
package com.ruoyi.system.service.subteam;
import com.ruoyi.system.domain.subteam.SubteamOverviewVO;
public interface ISubteamOverviewService {
SubteamOverviewVO loadOverview();
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.system.service.subteam;
/**
* 团队后台数据范围当前登录人所属部门团队
*/
public interface ISubteamScopeService {
/**
* 当前账号所属团队部门 IDsys_user.dept_id
*/
Long currentTeamDeptId();
void assertSysUserInTeam(Long sysUserId);
void assertAiUserInTeam(Long aiUserId);
void assertAiOrderBelongsToTeam(Long orderId);
void assertChargeRefundBelongsToTeam(Long orderPkId);
void assertGroupBalanceRecordBelongsToTeam(String recordId);
void assertAiBalanceRecordVisible(Long recordId, Long teamDeptId);
}

View File

@ -0,0 +1,78 @@
package com.ruoyi.system.service.subteam.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.ruoyi.ai.domain.AiOrder;
import com.ruoyi.ai.domain.AiVideoReportData;
import com.ruoyi.ai.mapper.AiBalanceChangeRecordMapper;
import com.ruoyi.ai.mapper.AiChargeRefundOrderMapper;
import com.ruoyi.ai.mapper.AiGroupBalanceChangeRecordMapper;
import com.ruoyi.ai.mapper.AiOrderMapper;
import com.ruoyi.ai.service.IAiVideoReportDataService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.subteam.ISubteamDataQueryService;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
@Service
public class SubteamDataQueryServiceImpl implements ISubteamDataQueryService {
@Autowired
private ISubteamScopeService subteamScopeService;
@Autowired
private AiOrderMapper aiOrderMapper;
@Autowired
private AiChargeRefundOrderMapper aiChargeRefundOrderMapper;
@Autowired
private AiBalanceChangeRecordMapper aiBalanceChangeRecordMapper;
@Autowired
private AiGroupBalanceChangeRecordMapper aiGroupBalanceChangeRecordMapper;
@Autowired
private IAiVideoReportDataService aiVideoReportDataService;
@Override
public List<AiOrder> selectVideoOrders(AiOrder query) {
Long deptId = subteamScopeService.currentTeamDeptId();
query.setDeptId(deptId);
return aiOrderMapper.selectAiOrderList(query);
}
@Override
public List<AiChargeRefundOrder> selectChargeRefundOrders(AiChargeRefundOrder query) {
Long deptId = subteamScopeService.currentTeamDeptId();
query.setDeptId(deptId);
return aiChargeRefundOrderMapper.selectAiChargeRefundOrderList(query);
}
@Override
public List<AiBalanceChangeRecord> selectUserBalanceRecords(AiBalanceChangeRecord query) {
Long deptId = subteamScopeService.currentTeamDeptId();
return aiBalanceChangeRecordMapper.selectAiBalanceChangeRecordListByAiUserDept(query, deptId);
}
@Override
public List<AiGroupBalanceChangeRecord> selectGroupBalanceRecords(AiGroupBalanceChangeRecord query) {
Long deptId = subteamScopeService.currentTeamDeptId();
query.setDeptId(deptId);
return aiGroupBalanceChangeRecordMapper.selectList(
Wrappers.lambdaQuery(query).orderByDesc(AiGroupBalanceChangeRecord::getId));
}
@Override
public List<AiVideoReportData> selectTeamDailyConsume(String statDate) {
if (StringUtils.isEmpty(statDate)) {
return java.util.Collections.emptyList();
}
Long deptId = subteamScopeService.currentTeamDeptId();
return aiVideoReportDataService.selectTeamDailyConsumeByDeptId(statDate, deptId);
}
}

View File

@ -0,0 +1,72 @@
package com.ruoyi.system.service.subteam.impl;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.ai.mapper.AiVideoReportDataMapper;
import com.ruoyi.ai.service.IAiUserService;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.subteam.SubteamOverviewVO;
import com.ruoyi.system.domain.subteam.SubteamVideoMetrics;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.subteam.ISubteamOverviewService;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
import com.ruoyi.common.core.redis.RedisCache;
@Service
public class SubteamOverviewServiceImpl implements ISubteamOverviewService {
private static final int METRICS_CACHE_MINUTES = 5;
@Autowired
private ISubteamScopeService subteamScopeService;
@Autowired
private SysDeptMapper sysDeptMapper;
@Autowired
private IAiUserService aiUserService;
@Autowired
private AiVideoReportDataMapper aiVideoReportDataMapper;
@Autowired
private RedisCache redisCache;
@Override
public SubteamOverviewVO loadOverview() {
Long deptId = subteamScopeService.currentTeamDeptId();
SysDept dept = sysDeptMapper.selectDeptById(deptId);
SubteamOverviewVO vo = new SubteamOverviewVO();
vo.setDeptId(deptId);
vo.setDeptName(dept != null ? dept.getDeptName() : "");
vo.setBalance(dept != null && dept.getBalance() != null ? dept.getBalance() : BigDecimal.ZERO);
vo.setAiUserCount(aiUserService.countAiUserByDeptId(deptId));
LocalDate end = LocalDate.now();
LocalDate start = end.minusDays(6);
String startDay = start.format(DateTimeFormatter.BASIC_ISO_DATE);
String endDay = end.format(DateTimeFormatter.BASIC_ISO_DATE);
String cacheKey = "subteam:videoMetrics:" + deptId + ":" + startDay + ":" + endDay;
SubteamVideoMetrics metrics = redisCache.getCacheObject(cacheKey);
if (metrics == null) {
metrics = aiVideoReportDataMapper.selectDeptVideoMetricsBetween(deptId, startDay, endDay);
if (metrics == null) {
metrics = new SubteamVideoMetrics();
}
if (metrics.getConsumeScore() == null) {
metrics.setConsumeScore(BigDecimal.ZERO);
}
if (metrics.getOrderCount() == null) {
metrics.setOrderCount(0L);
}
redisCache.setCacheObject(cacheKey, metrics, METRICS_CACHE_MINUTES, TimeUnit.MINUTES);
}
vo.setLast7DaysConsumeScore(metrics.getConsumeScore());
vo.setLast7DaysOrderCount(metrics.getOrderCount());
return vo;
}
}

View File

@ -0,0 +1,107 @@
package com.ruoyi.system.service.subteam.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.ai.domain.AiChargeRefundOrder;
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
import com.ruoyi.ai.domain.AiOrder;
import com.ruoyi.ai.domain.AiBalanceChangeRecord;
import com.ruoyi.ai.mapper.AiChargeRefundOrderMapper;
import com.ruoyi.ai.mapper.AiGroupBalanceChangeRecordMapper;
import com.ruoyi.ai.mapper.AiOrderMapper;
import com.ruoyi.ai.mapper.AiBalanceChangeRecordMapper;
import com.ruoyi.common.core.domain.entity.AiUser;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
import com.ruoyi.ai.mapper.AiUserMapper;
@Service
public class SubteamScopeServiceImpl implements ISubteamScopeService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private AiOrderMapper aiOrderMapper;
@Autowired
private AiChargeRefundOrderMapper aiChargeRefundOrderMapper;
@Autowired
private AiGroupBalanceChangeRecordMapper aiGroupBalanceChangeRecordMapper;
@Autowired
private AiBalanceChangeRecordMapper aiBalanceChangeRecordMapper;
@Autowired
private AiUserMapper aiUserMapper;
@Override
public Long currentTeamDeptId() {
Long deptId = SecurityUtils.getDeptId();
if (deptId == null || deptId <= 0) {
throw new ServiceException("当前账号未绑定团队,无法使用团队后台");
}
return deptId;
}
@Override
public void assertSysUserInTeam(Long sysUserId) {
Long teamDeptId = currentTeamDeptId();
SysUser u = sysUserMapper.selectUserById(sysUserId);
if (u == null || u.getDeptId() == null || !teamDeptId.equals(u.getDeptId())) {
throw new ServiceException("无权操作该用户");
}
}
@Override
public void assertAiUserInTeam(Long aiUserId) {
Long teamDeptId = currentTeamDeptId();
AiUser user = aiUserMapper.selectById(aiUserId);
if (user == null || user.getDeptId() == null || !teamDeptId.equals(user.getDeptId())) {
throw new ServiceException("无权操作该用户");
}
}
@Override
public void assertAiOrderBelongsToTeam(Long orderId) {
Long teamDeptId = currentTeamDeptId();
AiOrder o = aiOrderMapper.selectById(orderId);
if (o == null || o.getDeptId() == null || !teamDeptId.equals(o.getDeptId())) {
throw new ServiceException("无权查看该订单");
}
}
@Override
public void assertChargeRefundBelongsToTeam(Long orderPkId) {
Long teamDeptId = currentTeamDeptId();
AiChargeRefundOrder o = aiChargeRefundOrderMapper.selectAiChargeRefundOrderById(orderPkId);
if (o == null || o.getDeptId() == null || !teamDeptId.equals(o.getDeptId())) {
throw new ServiceException("无权查看该充值记录");
}
}
@Override
public void assertGroupBalanceRecordBelongsToTeam(String recordId) {
Long teamDeptId = currentTeamDeptId();
AiGroupBalanceChangeRecord r = aiGroupBalanceChangeRecordMapper.selectById(recordId);
if (r == null || r.getDeptId() == null || !teamDeptId.equals(r.getDeptId())) {
throw new ServiceException("无权查看该记录");
}
}
@Override
public void assertAiBalanceRecordVisible(Long recordId, Long teamDeptId) {
AiBalanceChangeRecord r = aiBalanceChangeRecordMapper.selectById(recordId);
if (r == null) {
throw new ServiceException("记录不存在");
}
AiUser u = aiUserMapper.selectAiUserById(r.getUserId());
if (u == null || u.getDeptId() == null || !teamDeptId.equals(u.getDeptId())) {
throw new ServiceException("无权查看该余额变动");
}
}
}

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ai.mapper.AiChargeRefundOrderMapper">
<resultMap type="AiChargeRefundOrder" id="AiChargeRefundOrderResult">
<result property="id" column="id" />
<result property="delFlag" column="del_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="orderNum" column="order_num" />
<result property="thirdPartyOrderNum" column="third_party_order_num" />
<result property="deptId" column="dept_id" />
<result property="deptName" column="dept_name" />
<result property="orderType" column="order_type" />
<result property="money" column="money" />
<result property="amount" column="amount" />
<result property="remark" column="remark" />
<result property="status" column="status" />
</resultMap>
<sql id="selectAiChargeRefundOrderVo">
select o.id, o.del_flag, o.create_by, o.create_time, o.update_time, o.order_num, o.third_party_order_num, o.dept_id,
d.dept_name as dept_name, o.order_type, o.money, o.amount, o.remark, o.status
from ai_charge_refund_order o
left join sys_dept d on d.dept_id = o.dept_id and d.del_flag = '0'
</sql>
<select id="selectAiChargeRefundOrderList" parameterType="AiChargeRefundOrder" resultMap="AiChargeRefundOrderResult">
<include refid="selectAiChargeRefundOrderVo"/>
<where>
and o.del_flag = '0'
<if test="orderNum != null and orderNum != ''"> and o.order_num = #{orderNum}</if>
<if test="thirdPartyOrderNum != null and thirdPartyOrderNum != ''"> and o.third_party_order_num = #{thirdPartyOrderNum}</if>
<if test="deptId != null "> and o.dept_id = #{deptId}</if>
<if test="deptName != null and deptName != ''"> and d.dept_name like concat('%', #{deptName}, '%')</if>
<if test="orderType != null "> and o.order_type = #{orderType}</if>
<if test="money != null "> and o.money = #{money}</if>
<if test="amount != null "> and o.amount = #{amount}</if>
<if test="status != null "> and o.status = #{status}</if>
</where>
order by o.id desc
</select>
<select id="selectAiChargeRefundOrderById" parameterType="Long" resultMap="AiChargeRefundOrderResult">
<include refid="selectAiChargeRefundOrderVo"/>
where o.id = #{id}
</select>
<insert id="insertAiChargeRefundOrder" parameterType="AiChargeRefundOrder" useGeneratedKeys="true" keyProperty="id">
insert into ai_charge_refund_order
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="delFlag != null and delFlag != ''">del_flag,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="orderNum != null and orderNum != ''">order_num,</if>
<if test="thirdPartyOrderNum != null">third_party_order_num,</if>
<if test="deptId != null">dept_id,</if>
<if test="orderType != null">order_type,</if>
<if test="money != null">money,</if>
<if test="amount != null">amount,</if>
<if test="remark != null">remark,</if>
<if test="status != null">status,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="orderNum != null and orderNum != ''">#{orderNum},</if>
<if test="thirdPartyOrderNum != null">#{thirdPartyOrderNum},</if>
<if test="deptId != null">#{deptId},</if>
<if test="orderType != null">#{orderType},</if>
<if test="money != null">#{money},</if>
<if test="amount != null">#{amount},</if>
<if test="remark != null">#{remark},</if>
<if test="status != null">#{status},</if>
</trim>
</insert>
<update id="updateAiChargeRefundOrder" parameterType="AiChargeRefundOrder">
update ai_charge_refund_order
<trim prefix="SET" suffixOverrides=",">
<if test="delFlag != null and delFlag != ''">del_flag = #{delFlag},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="orderNum != null and orderNum != ''">order_num = #{orderNum},</if>
<if test="thirdPartyOrderNum != null">third_party_order_num = #{thirdPartyOrderNum},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="orderType != null">order_type = #{orderType},</if>
<if test="money != null">money = #{money},</if>
<if test="amount != null">amount = #{amount},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="status != null">status = #{status},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteAiChargeRefundOrderById" parameterType="Long">
delete from ai_charge_refund_order where id = #{id}
</delete>
<delete id="deleteAiChargeRefundOrderByIds" parameterType="String">
delete from ai_charge_refund_order where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ai.mapper.AiDeptArkConfigMapper">
<resultMap type="AiDeptArkConfig" id="AiDeptArkConfigResult">
<result property="id" column="id" />
<result property="deptId" column="dept_id" />
<result property="modelParm" column="model_parm" />
<result property="project" column="project" />
<result property="byteApiKey" column="byte_api_key" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectAiDeptArkConfigVo">
select id, dept_id, model_parm, project, byte_api_key, create_by, create_time, update_by, update_time from ai_dept_ark_config
</sql>
<select id="selectAiDeptArkConfigList" parameterType="AiDeptArkConfig" resultMap="AiDeptArkConfigResult">
<include refid="selectAiDeptArkConfigVo"/>
<where>
<if test="deptId != null "> and dept_id = #{deptId}</if>
<if test="modelParm != null and modelParm != ''"> and model_parm = #{modelParm}</if>
<if test="project != null and project != ''"> and project = #{project}</if>
<if test="byteApiKey != null and byteApiKey != ''"> and byte_api_key = #{byteApiKey}</if>
</where>
</select>
<select id="selectAiDeptArkConfigById" parameterType="String" resultMap="AiDeptArkConfigResult">
<include refid="selectAiDeptArkConfigVo"/>
where id = #{id}
</select>
<insert id="insertAiDeptArkConfig" parameterType="AiDeptArkConfig" useGeneratedKeys="true" keyProperty="id">
insert into ai_dept_ark_config
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deptId != null">dept_id,</if>
<if test="modelParm != null">model_parm,</if>
<if test="project != null">project,</if>
<if test="byteApiKey != null">byte_api_key,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deptId != null">#{deptId},</if>
<if test="modelParm != null">#{modelParm},</if>
<if test="project != null">#{project},</if>
<if test="byteApiKey != null">#{byteApiKey},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<update id="updateAiDeptArkConfig" parameterType="AiDeptArkConfig">
update ai_dept_ark_config
<trim prefix="SET" suffixOverrides=",">
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="modelParm != null">model_parm = #{modelParm},</if>
<if test="project != null">project = #{project},</if>
<if test="byteApiKey != null">byte_api_key = #{byteApiKey},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteAiDeptArkConfigById" parameterType="String">
delete from ai_dept_ark_config where id = #{id}
</delete>
<delete id="deleteAiDeptArkConfigByIds" parameterType="String">
delete from ai_dept_ark_config where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ai.mapper.AiGroupBalanceChangeRecordMapper">
<resultMap type="AiGroupBalanceChangeRecord" id="AiGroupBalanceChangeRecordResult">
<result property="id" column="id" />
<result property="relationOrderNo" column="relation_order_no" />
<result property="deptId" column="dept_id" />
<result property="type" column="type" />
<result property="changeAmount" column="change_amount" />
<result property="resultAmount" column="result_amount" />
<result property="remark" column="remark" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectAiGroupBalanceChangeRecordVo">
select id, relation_order_no, dept_id, type, change_amount, result_amount, remark, create_time, update_time from ai_group_balance_change_record
</sql>
<select id="selectAiGroupBalanceChangeRecordList" parameterType="AiGroupBalanceChangeRecord" resultMap="AiGroupBalanceChangeRecordResult">
<include refid="selectAiGroupBalanceChangeRecordVo"/>
<where>
<if test="relationOrderNo != null and relationOrderNo != ''"> and relation_order_no = #{relationOrderNo}</if>
<if test="deptId != null "> and dept_id = #{deptId}</if>
<if test="type != null "> and type = #{type}</if>
<if test="changeAmount != null "> and change_amount = #{changeAmount}</if>
<if test="resultAmount != null "> and result_amount = #{resultAmount}</if>
</where>
</select>
<select id="selectAiGroupBalanceChangeRecordById" parameterType="String" resultMap="AiGroupBalanceChangeRecordResult">
<include refid="selectAiGroupBalanceChangeRecordVo"/>
where id = #{id}
</select>
<insert id="insertAiGroupBalanceChangeRecord" parameterType="AiGroupBalanceChangeRecord" useGeneratedKeys="true" keyProperty="id">
insert into ai_group_balance_change_record
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="relationOrderNo != null">relation_order_no,</if>
<if test="deptId != null">dept_id,</if>
<if test="type != null">type,</if>
<if test="changeAmount != null">change_amount,</if>
<if test="resultAmount != null">result_amount,</if>
<if test="remark != null">remark,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="relationOrderNo != null">#{relationOrderNo},</if>
<if test="deptId != null">#{deptId},</if>
<if test="type != null">#{type},</if>
<if test="changeAmount != null">#{changeAmount},</if>
<if test="resultAmount != null">#{resultAmount},</if>
<if test="remark != null">#{remark},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<update id="updateAiGroupBalanceChangeRecord" parameterType="AiGroupBalanceChangeRecord">
update ai_group_balance_change_record
<trim prefix="SET" suffixOverrides=",">
<if test="relationOrderNo != null">relation_order_no = #{relationOrderNo},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="type != null">type = #{type},</if>
<if test="changeAmount != null">change_amount = #{changeAmount},</if>
<if test="resultAmount != null">result_amount = #{resultAmount},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteAiGroupBalanceChangeRecordById" parameterType="String">
delete from ai_group_balance_change_record where id = #{id}
</delete>
<delete id="deleteAiGroupBalanceChangeRecordByIds" parameterType="String">
delete from ai_group_balance_change_record where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ai.mapper.AiVideoReportDataMapper">
<resultMap type="AiVideoReportData" id="AiVideoReportDataResult">
<result property="id" column="id" />
<result property="dateKey" column="date_key" />
<result property="deptId" column="dept_id" />
<result property="userId" column="user_id" />
<result property="score" column="score" />
<result property="orderCount" column="order_count" />
<result property="useTokens" column="use_tokens" />
<result property="rechargeScore" column="recharge_score" />
<result property="deptName" column="dept_name" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectAiVideoReportDataVo">
select id, date_key, dept_id, user_id, score, order_count, use_tokens, recharge_score, create_time, update_time from ai_video_report_data
</sql>
<select id="selectAiVideoReportDataList" parameterType="AiVideoReportData" resultMap="AiVideoReportDataResult">
<include refid="selectAiVideoReportDataVo"/>
<where>
<if test="dateKey != null and dateKey != ''"> and date_key = #{dateKey}</if>
<if test="deptId != null "> and dept_id = #{deptId}</if>
<if test="userId != null "> and user_id = #{userId}</if>
<if test="score != null "> and score = #{score}</if>
<if test="orderCount != null "> and order_count = #{orderCount}</if>
<if test="useTokens != null "> and use_tokens = #{useTokens}</if>
<if test="rechargeScore != null "> and recharge_score = #{rechargeScore}</if>
</where>
</select>
<select id="selectAiVideoReportDataById" parameterType="String" resultMap="AiVideoReportDataResult">
<include refid="selectAiVideoReportDataVo"/>
where id = #{id}
</select>
<insert id="insertAiVideoReportData" parameterType="AiVideoReportData" useGeneratedKeys="true" keyProperty="id">
insert into ai_video_report_data
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="dateKey != null and dateKey != ''">date_key,</if>
<if test="deptId != null">dept_id,</if>
<if test="userId != null">user_id,</if>
<if test="score != null">score,</if>
<if test="orderCount != null">order_count,</if>
<if test="useTokens != null">use_tokens,</if>
<if test="rechargeScore != null">recharge_score,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="dateKey != null and dateKey != ''">#{dateKey},</if>
<if test="deptId != null">#{deptId},</if>
<if test="userId != null">#{userId},</if>
<if test="score != null">#{score},</if>
<if test="orderCount != null">#{orderCount},</if>
<if test="useTokens != null">#{useTokens},</if>
<if test="rechargeScore != null">#{rechargeScore},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<update id="updateAiVideoReportData" parameterType="AiVideoReportData">
update ai_video_report_data
<trim prefix="SET" suffixOverrides=",">
<if test="dateKey != null and dateKey != ''">date_key = #{dateKey},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="userId != null">user_id = #{userId},</if>
<if test="score != null">score = #{score},</if>
<if test="orderCount != null">order_count = #{orderCount},</if>
<if test="useTokens != null">use_tokens = #{useTokens},</if>
<if test="rechargeScore != null">recharge_score = #{rechargeScore},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where id = #{id}
</update>
<select id="selectTeamDailyConsumeList" resultMap="AiVideoReportDataResult">
select
concat(substr(vrd.date_key, 1, 4), '-', substr(vrd.date_key, 5, 2), '-', substr(vrd.date_key, 7, 2)) as date_key,
vrd.dept_id,
d.dept_name,
sum(vrd.recharge_score) as recharge_score,
sum(vrd.score) as score,
sum(vrd.order_count) as order_count,
sum(vrd.use_tokens) as use_tokens
from ai_video_report_data vrd
left join sys_dept d on d.dept_id = vrd.dept_id
where vrd.date_key like concat(#{statDate}, '%')
and vrd.dept_id = #{deptId}
group by substr(vrd.date_key, 1, 8), vrd.dept_id, d.dept_name
order by substr(vrd.date_key, 1, 8) desc, vrd.dept_id desc
</select>
<select id="selectTeamDailyConsumeByDeptId" resultMap="AiVideoReportDataResult">
select
concat(substr(vrd.date_key, 1, 4), '-', substr(vrd.date_key, 5, 2), '-', substr(vrd.date_key, 7, 2)) as date_key,
vrd.dept_id,
d.dept_name,
sum(vrd.recharge_score) as recharge_score,
sum(vrd.score) as score,
sum(vrd.order_count) as order_count,
sum(vrd.use_tokens) as use_tokens
from ai_video_report_data vrd
left join sys_dept d on d.dept_id = vrd.dept_id
where vrd.date_key like concat(#{statDate}, '%')
and vrd.dept_id = #{deptId}
group by substr(vrd.date_key, 1, 8), vrd.dept_id, d.dept_name
order by substr(vrd.date_key, 1, 8) desc, vrd.dept_id desc
</select>
<select id="selectDeptVideoMetricsBetween" resultType="com.ruoyi.system.domain.subteam.SubteamVideoMetrics">
select
coalesce(sum(vrd.score), 0) as consumeScore,
coalesce(sum(vrd.order_count), 0) as orderCount
from ai_video_report_data vrd
where vrd.dept_id = #{deptId}
and substr(vrd.date_key, 1, 8) &gt;= #{startDay}
and substr(vrd.date_key, 1, 8) &lt;= #{endDay}
</select>
<insert id="upsertVideoConsumeIncrement">
insert into ai_video_report_data
(date_key, dept_id, user_id, score, order_count, use_tokens, recharge_score, create_time, update_time)
values
(#{dateKey}, #{deptId}, #{userId}, #{score}, #{orderCount}, #{useTokens}, 0, now(), now())
on duplicate key update
score = score + values(score),
order_count = order_count + values(order_count),
use_tokens = use_tokens + values(use_tokens),
update_time = now()
</insert>
<insert id="upsertRechargeScoreIncrement">
insert into ai_video_report_data
(date_key, dept_id, user_id, score, order_count, use_tokens, recharge_score, create_time, update_time)
values
(#{dateKey}, #{deptId}, 0, 0, 0, 0, #{rechargeScore}, now(), now())
on duplicate key update
recharge_score = recharge_score + values(recharge_score),
update_time = now()
</insert>
<delete id="deleteAiVideoReportDataById" parameterType="String">
delete from ai_video_report_data where id = #{id}
</delete>
<delete id="deleteAiVideoReportDataByIds" parameterType="String">
delete from ai_video_report_data where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -43,6 +43,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where> </where>
order by r.id desc order by r.id desc
</select> </select>
<select id="selectAiBalanceChangeRecordListByAiUserDept" resultMap="AiBalanceChangeRecordResult">
<include refid="selectAiBalanceChangeRecordVo"/>
<where>
and u.dept_id = #{deptId}
<if test="q.nickname != null and q.nickname != '' "> and u.nickname like concat('%', #{q.nickname}, '%') </if>
<if test="q.userId != null "> and r.user_id = #{q.userId}</if>
<if test="q.uuid != null "> and u.user_id = #{q.uuid}</if>
<if test="q.type != null "> and r.type = #{q.type}</if>
<if test="q.changeAmount != null "> and r.change_amount = #{q.changeAmount}</if>
<if test="q.resultAmount != null "> and r.result_amount = #{q.resultAmount}</if>
<if test="q.params != null and q.params.beginTime != null and q.params.beginTime != ''">
AND date_format(r.create_time,'%Y%m%d') &gt;= date_format(#{q.params.beginTime},'%Y%m%d')
</if>
<if test="q.params != null and q.params.endTime != null and q.params.endTime != ''">
AND date_format(r.create_time,'%Y%m%d') &lt;= date_format(#{q.params.endTime},'%Y%m%d')
</if>
</where>
order by r.id desc
</select>
<select id="selectAiBalanceChangeRecordById" parameterType="Long" resultMap="AiBalanceChangeRecordResult"> <select id="selectAiBalanceChangeRecordById" parameterType="Long" resultMap="AiBalanceChangeRecordResult">
<include refid="selectAiBalanceChangeRecordVo"/> <include refid="selectAiBalanceChangeRecordVo"/>

View File

@ -92,7 +92,4 @@ CREATE TABLE `ai_charge_refund_order` (
) )
COMMENT='团队(部门)充值退款订单表' COMMENT='团队(部门)充值退款订单表'
COLLATE='utf8mb4_unicode_ci' COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB; ENGINE=InnoDB;
ALTER TABLE `ai_user`
ADD INDEX `dept_id` (`dept_id`);

View File

@ -193,3 +193,5 @@ values('团队(部门)对应火山引擎配置删除', @parentId, '4', '#',
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('团队(部门)对应火山引擎配置导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'ai:config:export', '#', 'admin', sysdate(), '', null, ''); values('团队(部门)对应火山引擎配置导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'ai:config:export', '#', 'admin', sysdate(), '', null, '');
ALTER TABLE `byteai`.`ai_balance_change_record`
ADD COLUMN `dept_id` bigint NULL COMMENT '部门ID' AFTER `remark`;