feat: 团队订单与管理端订单代码重构、合并

This commit is contained in:
yys 2026-04-23 14:43:03 +08:00
parent 9eb8514fdf
commit f7d40414c0
10 changed files with 206 additions and 88 deletions

View File

@ -43,10 +43,17 @@ export function reclaimSubteamUserDeptScore(data) {
return request({ url: '/subteam/user/dept-score/reclaim', method: 'put', data })
}
/** 团队后台视频/生成订单列表GET /subteam/video-order/list */
export function listSubteamVideoOrder(query) {
return request({ url: '/subteam/video-order/list', method: 'get', params: query })
}
/**
* 导出POST /subteam/video-order/export与列表相同查询参数 + 创建时间区间
* 页面一般使用 this.download('subteam/video-order/export', addDateRange(...), filename)勿用 GET
*/
export const subteamVideoOrderExportUrl = 'subteam/video-order/export'
export function getSubteamVideoOrder(id) {
return request({ url: '/subteam/video-order/' + id, method: 'get' })
}

View File

@ -67,10 +67,10 @@
</el-form>
<el-table v-loading="loading" :data="orderList">
<el-table-column label="团队名称" align="center" prop="deptName" width="140" show-overflow-tooltip />
<el-table-column label="用户名称" align="center" prop="userName" width="120" show-overflow-tooltip />
<el-table-column label="订单号" align="center" prop="orderNum" width="200" show-overflow-tooltip />
<el-table-column label="三方订单号" align="center" prop="thirdPartyOrderNum" width="200" show-overflow-tooltip />
<el-table-column label="团队名称" align="center" prop="deptName" width="140" show-overflow-tooltip />
<el-table-column label="用户名称" align="center" prop="userName" width="120" show-overflow-tooltip />
<el-table-column label="消耗积分" align="center" prop="amount" width="120">
<template slot-scope="scope">
<span>{{ formatThousands(scope.row.amount) }}</span>
@ -120,7 +120,7 @@
/>
<el-dialog title="订单详情" :visible.sync="detailVisible" width="720px" append-to-body>
<el-descriptions :column="1" border size="small">
<el-descriptions :column="1" border size="small" :labelStyle="{ width: '150px', minWidth: '150px' }">
<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>

View File

@ -27,39 +27,33 @@
<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-button v-hasPermi="['subteam:user:add']" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
</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 v-loading="loading" :data="userList">
<el-table-column label="主键ID" prop="id" width="100" />
<el-table-column label="用户ID" prop="userId" width="150" show-overflow-tooltip />
<el-table-column label="账号" prop="username" width="150"/>
<el-table-column label="昵称" prop="nickname"/>
<el-table-column label="剩余积分" prop="balance" width="120" align="center" header-align="center">
<el-table-column label="登录时间" prop="loginTime" width="160" align="center" header-align="center">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.loginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="剩余积分" prop="balance" width="150" align="center" header-align="center">
<template slot-scope="scope">
<span>{{ formatThousands(scope.row.balance) }}</span>
</template>
</el-table-column>
<el-table-column label="手机" prop="phone" width="120" />
<el-table-column label="状态" width="80">
<el-table-column label="手机" prop="phone" width="160" />
<el-table-column label="状态" width="100">
<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">
<el-table-column label="操作" width="400">
<template slot-scope="scope">
<el-button
size="mini"

View File

@ -1,14 +1,35 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
<el-form-item label="订单号" prop="orderNum">
<el-input v-model="queryParams.orderNum" clearable @keyup.enter.native="handleQuery" />
<el-input
v-model="queryParams.orderNum"
placeholder="请输入订单号"
style="width: 140px"
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 label="三方订单号" prop="thirdPartyOrderNum">
<el-input
v-model="queryParams.thirdPartyOrderNum"
placeholder="支持模糊搜索"
style="width: 210px"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="支持模糊搜索"
style="width: 120px"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" clearable placeholder="状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
@ -24,26 +45,45 @@
/>
</el-form-item>
<el-form-item>
&nbsp;&nbsp;&nbsp;&nbsp;
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['subteam:videoOrder:export']"
>导出</el-button>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</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="状态" width="80">
<template slot-scope="s">
<span>{{ statusLabel(s.row.status) }}</span>
<el-table v-loading="loading" :data="orderList">
<el-table-column label="订单号" align="center" prop="orderNum" width="200" show-overflow-tooltip />
<el-table-column label="三方订单号" align="center" prop="thirdPartyOrderNum" width="200" show-overflow-tooltip />
<el-table-column label="用户名称" align="center" prop="userName" width="120" show-overflow-tooltip />
<el-table-column label="消耗积分" align="center" prop="amount" width="120">
<template slot-scope="scope">
<span>{{ formatThousands(scope.row.amount) }}</span>
</template>
</el-table-column>
<el-table-column label="结果预览" min-width="220">
<template slot-scope="s">
<div v-if="extractVideoUrl(s.row.result)">
<el-table-column label="使用tokens" align="center" prop="totalUsage" width="120">
<template slot-scope="scope">
<span>{{ formatThousands(scope.row.totalUsage) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="100">
<template slot-scope="scope">
<span>{{ statusLabel(scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column label="结果预览" min-width="185">
<template slot-scope="scope">
<div v-if="extractVideoUrl(scope.row.result)">
<video
:src="extractVideoUrl(s.row.result)"
:src="extractVideoUrl(scope.row.result)"
controls
preload="metadata"
style="width: 180px; max-height: 100px; border-radius: 4px;"
@ -52,17 +92,28 @@
<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="创建时间" align="center" prop="createTime" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</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 slot-scope="scope">
<el-button type="text" size="mini" @click="handleDetail(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="订单详情" :visible.sync="detailVisible" width="720px" append-to-body>
<el-descriptions :column="1" border size="small">
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog title="订单详情" :visible.sync="detailVisible" width="800px" append-to-body>
<el-descriptions :column="1" border size="small" :labelStyle="{ width: '150px', minWidth: '150px' }">
<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>
@ -76,15 +127,16 @@
</template>
<script>
import { listGroupOrder } from '@/api/ai/order'
import { listSubteamVideoOrder, subteamVideoOrderExportUrl } from '@/api/subteam'
export default {
name: 'SubteamVideoOrder',
data() {
return {
loading: true,
showSearch: true,
list: [],
total: 0,
orderList: [],
detailVisible: false,
detailData: {},
statusOptions: [
@ -93,18 +145,50 @@ export default {
{ label: '失败', value: 2 }
],
dateRange: [],
queryParams: { pageNum: 1, pageSize: 10, orderNum: undefined, userId: undefined, status: undefined }
queryParams: {
pageNum: 1,
pageSize: 10,
orderNum: null,
thirdPartyOrderNum: null,
userName: null,
status: null
}
}
},
created() { this.getList() },
created() {
this.initDefaultDateRange()
this.getList()
},
methods: {
getList() {
this.loading = true
listGroupOrder(this.addDateRange(this.queryParams, this.dateRange, 'CreateTime')).then(res => {
this.list = res.rows
this.total = res.total
this.loading = false
})
formatThousands(value) {
if (value === null || value === undefined || value === '') {
return '—'
}
const n = Number(value)
if (Number.isNaN(n)) {
return String(value)
}
return n.toLocaleString('en-US', { maximumFractionDigits: 0 })
},
initDefaultDateRange() {
this.dateRange = this.buildLastMonthDateRange()
},
buildLastMonthDateRange() {
const end = new Date()
const start = new Date(end.getTime())
start.setMonth(start.getMonth() - 1)
return [this.formatYmd(start), this.formatYmd(end)]
},
formatYmd(d) {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
},
statusLabel(status) {
const n = status !== undefined && status !== null ? Number(status) : null
const hit = this.statusOptions.find(o => o.value === n)
return hit ? hit.label : '—'
},
extractVideoUrl(result) {
if (!result) return ''
@ -138,13 +222,12 @@ export default {
}
return ''
},
statusLabel(status) {
const n = status !== undefined && status !== null ? Number(status) : null
const hit = this.statusOptions.find(item => item.value === n)
return hit ? hit.label : '-'
},
handleDetail(row) {
this.detailData = row || {}
const copy = row && typeof row === 'object' ? { ...row } : {}
if (Object.prototype.hasOwnProperty.call(copy, 'deptName')) {
delete copy.deptName
}
this.detailData = copy
this.detailVisible = true
},
isObjectValue(value) {
@ -157,11 +240,30 @@ export default {
return String(value)
}
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
getList() {
this.loading = true
listSubteamVideoOrder(this.addDateRange(this.queryParams, this.dateRange, 'CreateTime')).then(response => {
this.orderList = response.rows
this.total = response.total
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.dateRange = []
this.initDefaultDateRange()
this.resetForm('queryForm')
this.handleQuery()
},
handleExport() {
const q = { ...this.queryParams }
this.download(
subteamVideoOrderExportUrl,
this.addDateRange(q, this.dateRange, 'CreateTime'),
`subteam_video_orders_${new Date().getTime()}.xlsx`
)
}
}
}

View File

@ -1,27 +1,28 @@
package com.ruoyi.web.controller.subteam;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
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.PostMapping;
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.annotation.Log;
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.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
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;
@ -32,10 +33,19 @@ public class SubteamVideoOrderController extends BaseController {
@GetMapping("/list")
public TableDataInfo list(AiOrder query) {
startPage();
List<AiOrder> list = subteamDataQueryService.selectVideoOrders(query);
List<AiOrder> list = aiOrderService.selectAiOrderListForCurrentSubteam(query);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('subteam:videoOrder:export')")
@Log(title = "团队订单记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, AiOrder query) {
List<AiOrder> list = aiOrderService.selectAiOrderListForCurrentSubteam(query);
ExcelUtil<AiOrder> util = new ExcelUtil<AiOrder>(AiOrder.class);
util.exportExcel(response, list, "团队订单记录");
}
@PreAuthorize("@ss.hasPermi('subteam:videoOrder:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {

View File

@ -35,6 +35,13 @@ public interface IAiOrderService {
*/
List<AiOrder> selectAiOrderList(AiOrder aiOrder);
/**
* 团队后台按当前登录人所属团队部门查询订单列表 {@link #selectAiOrderList} 共用 Mapper仅限定 deptId
*
* @param aiOrder 查询条件会写入 deptId不应依赖调用方传入的 deptId
*/
List<AiOrder> selectAiOrderListForCurrentSubteam(AiOrder aiOrder);
/**
* 分页查询订单管理列表
*

View File

@ -23,6 +23,7 @@ import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.subteam.ISubteamScopeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -64,6 +65,9 @@ public class AiOrderServiceImpl implements IAiOrderService {
@Autowired
private IAiVideoReportDataService aiVideoReportDataService;
@Autowired
private ISubteamScopeService subteamScopeService;
// 流水表任务成功时回补
private static final String TASK_SUCCESS_BACK_FILL_REMARK = "order.number.generation.successbackfill";
@ -92,6 +96,13 @@ public class AiOrderServiceImpl implements IAiOrderService {
return aiOrderMapper.selectAiOrderList(aiOrder);
}
@Override
public List<AiOrder> selectAiOrderListForCurrentSubteam(AiOrder aiOrder) {
Long deptId = subteamScopeService.currentTeamDeptId();
aiOrder.setDeptId(deptId);
return selectAiOrderList(aiOrder);
}
/**
* 分页查询订单管理列表
*

View File

@ -4,13 +4,10 @@ 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);

View File

@ -6,12 +6,10 @@ import org.springframework.stereotype.Service;
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;
@ -23,9 +21,6 @@ public class SubteamDataQueryServiceImpl implements ISubteamDataQueryService {
@Autowired
private ISubteamScopeService subteamScopeService;
@Autowired
private AiOrderMapper aiOrderMapper;
@Autowired
private AiChargeRefundOrderMapper aiChargeRefundOrderMapper;
@ -38,13 +33,6 @@ public class SubteamDataQueryServiceImpl implements ISubteamDataQueryService {
@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();

View File

@ -72,6 +72,8 @@ values('订单记录', @subteamRoot, '3', 'video-order', 'subteam/videoOrder/ind
SELECT @m2 := LAST_INSERT_ID();
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('订单查询', @m2, '1', '#', '', 1, 0, 'F', '0', '0', 'subteam:videoOrder:query', '#', 'admin', sysdate(), '', null, '');
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('订单导出', @m2, '2', '#', '', 1, 0, 'F', '0', '0', 'subteam:videoOrder:export', '#', 'admin', sysdate(), '', null, '');
-- 团队充值记录
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)