Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
an-plus-hr-ai
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
anjia-hr
an-plus-hr-ai
Commits
40305b50
Commit
40305b50
authored
Sep 21, 2026
by
刘斌
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 完善消费记录为父子表结构
parent
6ceb318f
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
162 additions
and
46 deletions
+162
-46
AiConsumeProjectCtrl.java
...ava/com/anplus/hr/ai/controller/AiConsumeProjectCtrl.java
+2
-2
AiConsumeProject.java
.../java/com/anplus/hr/ai/domain/model/AiConsumeProject.java
+5
-0
AiConsumeExportVo.java
...in/java/com/anplus/hr/ai/domain/vo/AiConsumeExportVo.java
+10
-3
AiConsumeProjectVo.java
...n/java/com/anplus/hr/ai/domain/vo/AiConsumeProjectVo.java
+5
-0
AiConsumeRecordSummaryVo.java
.../com/anplus/hr/ai/domain/vo/AiConsumeRecordSummaryVo.java
+5
-0
AiConsumeProjectServ.java
...n/java/com/anplus/hr/ai/service/AiConsumeProjectServ.java
+2
-2
AiConsumeProjectServImpl.java
...m/anplus/hr/ai/service/impl/AiConsumeProjectServImpl.java
+65
-24
index.ts
frontend/src/api/ai/consume/index.ts
+2
-2
model.d.ts
frontend/src/api/ai/consume/model.d.ts
+9
-1
consume-drawer.vue
frontend/src/views/ai/consume/consume-drawer.vue
+3
-0
data.ts
frontend/src/views/ai/consume/data.ts
+25
-6
index.vue
frontend/src/views/ai/consume/index.vue
+8
-3
data.ts
frontend/src/views/ai/recharge/data.ts
+15
-0
index.vue
frontend/src/views/ai/recharge/index.vue
+4
-3
recharge-drawer.vue
frontend/src/views/ai/recharge/recharge-drawer.vue
+2
-0
No files found.
admin/src/main/java/com/anplus/hr/ai/controller/AiConsumeProjectCtrl.java
View file @
40305b50
...
...
@@ -59,10 +59,10 @@ public class AiConsumeProjectCtrl {
}
/**
* 查询当前筛选条件下的
项目总金额
合计(跨所有分页的全量结果,口径与列表完全一致)。
* 查询当前筛选条件下的
命中明细金额合计与积分
合计(跨所有分页的全量结果,口径与列表完全一致)。
*
* @param param 查询条件(与分页查询共用同一条件对象)
* @return 合计载体(amountCny
保留两位小数;无匹配数据时
为 0.00)
* @return 合计载体(amountCny
明细金额合计 + consumePoints 明细积分合计,均保留两位小数;无匹配数据时均
为 0.00)
*/
@SaCheckPermission
(
"ai:consume:list"
)
@GetMapping
(
"/summary"
)
...
...
admin/src/main/java/com/anplus/hr/ai/domain/model/AiConsumeProject.java
View file @
40305b50
...
...
@@ -59,6 +59,11 @@ public class AiConsumeProject extends TenantModel {
*/
private
BigDecimal
amountCny
;
/**
* 消费积分(= Σ 子表明细积分,随明细增删改事务聚合重算)
*/
private
BigDecimal
consumePoints
;
/**
* 每分钟金额(= 项目总金额×60÷成片时长,2位小数HALF_UP;成片时长为0时为NULL;派生计算属 C2)
*/
...
...
admin/src/main/java/com/anplus/hr/ai/domain/vo/AiConsumeExportVo.java
View file @
40305b50
...
...
@@ -13,11 +13,11 @@ import java.time.LocalDate;
* AI工具消费记录扁平导出行视图对象(导出专用,D8)
*
* <p>导出({@code POST /ai/consume/export})将父子表 JOIN 展开为扁平明细行:每行 = 一条明细子行
* + 该项目父级汇总列(所属部门 / 项目名称 / 姓名 / 消费时间 / 项目总金额 / 成片时长 / 每分钟金额 /
* + 该项目父级汇总列(所属部门 / 项目名称 / 姓名 / 消费时间 / 项目总金额 /
消费积分 /
成片时长 / 每分钟金额 /
* 备注)重复展示,其后接明细列(AI工具名称 / 国内外 / 消耗积分 / 金额)。列序与标签对齐 spec
* 「导出末尾合计行」,并保持与既有导出列口径稳定;部门名由 ServiceImpl 批量回填(导出链不触发
* JSON 翻译)。末尾合计行:所属部门 = 「合计」、项目总金额 = 全量命中明细金额合计
,其余列(含「金额(人民币)」
* 明细列)留空。成片时长列以分秒文本输出(spec「导出成片时长格式化」)。</p>
* JSON 翻译)。末尾合计行:所属部门 = 「合计」、项目总金额 = 全量命中明细金额合计
、消费积分 = 全量命中
* 明细
积分合计,其余列(含「金额(人民币)」明细
列)留空。成片时长列以分秒文本输出(spec「导出成片时长格式化」)。</p>
*
* <p>非导出辅助字段 {@code deptId} 无 {@code @ExcelProperty},被 {@code @ExcelIgnoreUnannotated}
* 忽略,仅用于批量查询部门名回填 {@code deptName}。两个金额列语义不同:{@code amountCny} 为父级
...
...
@@ -68,6 +68,13 @@ public class AiConsumeExportVo implements Serializable {
@ExcelProperty
(
value
=
"项目总金额"
)
private
BigDecimal
amountCny
;
/**
* 消费积分(父级汇总列,= Σ 子表明细积分,每明细行重复;末尾合计行此列填全量命中明细积分合计;
* 与明细列 {@link #consumePoints}「消耗积分」配对,两者语义不同勿混)
*/
@ExcelProperty
(
value
=
"消费积分"
)
private
BigDecimal
projectConsumePoints
;
/**
* 成片时长(父级汇总列,每明细行重复):导出层格式化为分秒文本({@code >60 秒→「X分Y秒」、
* 1..60 秒→「X秒」、0→「0秒」、NULL→留白}),对齐前端列表 durationText 约定(spec「导出成片时长格式化」)
...
...
admin/src/main/java/com/anplus/hr/ai/domain/vo/AiConsumeProjectVo.java
View file @
40305b50
...
...
@@ -69,6 +69,11 @@ public class AiConsumeProjectVo implements Serializable {
*/
private
BigDecimal
amountCny
;
/**
* 消费积分(= Σ 子表明细积分,事务重算冗余存储)
*/
private
BigDecimal
consumePoints
;
/**
* 成片时长(秒)
*/
...
...
admin/src/main/java/com/anplus/hr/ai/domain/vo/AiConsumeRecordSummaryVo.java
View file @
40305b50
...
...
@@ -24,4 +24,9 @@ public class AiConsumeRecordSummaryVo implements Serializable {
*/
private
BigDecimal
amountCny
;
/**
* 消费积分合计:当前查询条件筛选出的全部命中明细子行积分(跨所有分页)求和,保留两位小数(与 amountCny 同口径)
*/
private
BigDecimal
consumePoints
;
}
admin/src/main/java/com/anplus/hr/ai/service/AiConsumeProjectServ.java
View file @
40305b50
...
...
@@ -40,10 +40,10 @@ public interface AiConsumeProjectServ extends IService<AiConsumeProject> {
List
<
AiConsumeExportVo
>
queryList
(
AiConsumeProjectListParam
param
);
/**
* 查询当前条件下的
项目总金额
合计(跨所有分页的全量筛选结果,口径与列表完全一致)
* 查询当前条件下的
命中明细金额合计与积分
合计(跨所有分页的全量筛选结果,口径与列表完全一致)
*
* @param param 查询条件(与分页查询共用同一条件对象 AiConsumeProjectListParam)
* @return 合计载体(amountCny
保留两位小数;无匹配数据时
为 0.00)
* @return 合计载体(amountCny
明细金额合计 + consumePoints 明细积分合计,均保留两位小数;无匹配数据时均
为 0.00)
*/
AiConsumeRecordSummaryVo
querySummary
(
AiConsumeProjectListParam
param
);
...
...
admin/src/main/java/com/anplus/hr/ai/service/impl/AiConsumeProjectServImpl.java
View file @
40305b50
This diff is collapsed.
Click to expand it.
frontend/src/api/ai/consume/index.ts
View file @
40305b50
...
...
@@ -23,9 +23,9 @@ export function consumeList(params?: PageQuery) {
}
/**
* 查询消费记录金额合计(跨所有分页的全量筛选结果,口径与列表一致)
* 查询消费记录金额
与积分
合计(跨所有分页的全量筛选结果,口径与列表一致)
* @param params 请求参数(与列表查询共用同一条件)
* @returns 合计载体(amountCny)
* @returns 合计载体(amountCny
明细金额合计 + consumePoints 明细积分合计
)
*/
export
function
consumeSummary
(
params
?:
PageQuery
)
{
return
alovaInstance
.
get
<
ConsumeSummary
>
(
Api
.
consumeSummary
,
{
params
});
...
...
frontend/src/api/ai/consume/model.d.ts
View file @
40305b50
...
...
@@ -30,6 +30,10 @@ export interface Consume {
* 项目总金额(= Σ 明细金额,人民币;列表「项目总金额」列绑定字段)
*/
amountCny
:
number
;
/**
* 消费积分(= Σ 明细积分,列表「消费积分」列绑定字段;存量未回填行为 null 时列留白)
*/
consumePoints
?:
number
;
/**
* 成片时长(秒,非负整数)
*/
...
...
@@ -144,7 +148,11 @@ export interface ConsumeForm {
*/
export
interface
ConsumeSummary
{
/**
*
项目总金额全量合计(
两位小数)
*
明细金额合计(命中明细子行金额之和,
两位小数)
*/
amountCny
:
number
;
/**
* 明细积分合计(命中明细子行积分之和,两位小数;与 amountCny 同口径)
*/
consumePoints
:
number
;
}
frontend/src/views/ai/consume/consume-drawer.vue
View file @
40305b50
...
...
@@ -30,6 +30,7 @@ import {
TimePicker
,
}
from
'antdv-next'
;
import
{
VxeGrid
}
from
'vxe-table'
;
import
{
cnyText
}
from
'./data.ts'
const
emit
=
defineEmits
<
{
reload
:
[]
}
>
();
...
...
@@ -186,12 +187,14 @@ const detailColumns: VxeGridProps['columns'] = [
title
:
'金额(人民币)'
,
minWidth
:
130
,
align
:
'center'
,
formatter
:
({
cellValue
})
=>
cnyText
(
cellValue
),
editRender
:
{},
slots
:
{
edit
:
(
props
)
=>
{
const
{
row
,
$grid
}
=
props
;
return
(
<
InputNumber
prefix
=
"¥"
class
=
"w-full"
min
=
{
0
}
onChange
=
{()
=>
$grid
?.
updateStatus
(
props
)}
...
...
frontend/src/views/ai/consume/data.ts
View file @
40305b50
import
type
{
VxeGridProps
}
from
'vxe-table'
;
/**
* 金额格式化:空值留白(合并列中父/子行在对方列天然为 null),有值保留 2 位小数。
* 同时用于父行「项目总金额」与子行「金额(人民币)」列(design D8)
* 数值格式化:空值/空串留白,有值保留 2 位小数。用于父行「消费积分」列(design D8/D5)
*/
function
amountText
(
value
?:
null
|
number
|
string
):
string
{
return
value
==
null
||
value
===
''
?
''
:
Number
(
value
).
toFixed
(
2
);
}
/**
* 人民币金额格式化:空值/空串留白;已带「¥」标识的值直通(幂等,兼容 footer 单元格复用列 formatter),
* 否则补「¥ + 2 位小数」。用于父行「项目总金额」/「每分钟金额」与子行「金额(人民币)」列
* (spec「消费记录列表金额列币种标识」)
*/
export
function
cnyText
(
value
?:
null
|
number
|
string
):
string
{
if
(
value
==
null
||
value
===
''
)
{
return
''
;
}
const
text
=
String
(
value
);
return
text
.
startsWith
(
'¥'
)
?
text
:
`¥
${
Number
(
value
).
toFixed
(
2
)}
`
;
}
/**
* 成片时长格式化:>60 秒「X分X秒」、≤60 秒「X秒」、0「0秒」;空值(子行)留白(design D8)
*/
...
...
@@ -25,7 +37,7 @@ function durationText(value?: null | number): string {
/**
* 主网格列集(design D7):仅项目父级列 + 展开列。
* - 展开列 type:'expand':点击在该行下方内嵌明细子表(列见 detailColumns,数据取 row.children)
* - 父级列:所属部门 / 项目名称 / 姓名 / 项目总金额(amountCny) / 成片时长 / 每分钟金额 / 消费时间 / 备注 / 操作
* - 父级列:所属部门 / 项目名称 / 姓名 / 项目总金额(amountCny) /
消费积分(consumePoints) /
成片时长 / 每分钟金额 / 消费时间 / 备注 / 操作
* - 明细字段(AI工具名称 / 国内外 / 消耗积分 / 金额)不再占主网格列,移入展开子表
*/
export
const
columns
:
VxeGridProps
[
'columns'
]
=
[
...
...
@@ -51,6 +63,13 @@ export const columns: VxeGridProps['columns'] = [
field
:
'amountCny'
,
minWidth
:
100
,
align
:
'right'
,
formatter
:
({
cellValue
})
=>
cnyText
(
cellValue
),
},
{
title
:
'消费积分'
,
field
:
'consumePoints'
,
minWidth
:
100
,
align
:
'right'
,
formatter
:
({
cellValue
})
=>
amountText
(
cellValue
),
},
{
...
...
@@ -65,7 +84,7 @@ export const columns: VxeGridProps['columns'] = [
field
:
'amountPerMinute'
,
minWidth
:
90
,
align
:
'right'
,
formatter
:
({
cellValue
})
=>
amount
Text
(
cellValue
),
formatter
:
({
cellValue
})
=>
cny
Text
(
cellValue
),
},
{
title
:
'消费时间'
,
...
...
@@ -90,7 +109,7 @@ export const columns: VxeGridProps['columns'] = [
/**
* 展开行明细子表列集(design D7 / spec「消费记录列表展开明细子表前端展示」):
* AI工具名称 / 国内外 / 消耗积分 / 金额(人民币)。数据源为父行内嵌 children(ConsumeDetail,
* 金额字段 amountCny);金额列
复用 amountText
保留 2 位、空值留白。仅供主网格展开区嵌套只读子表使用。
* 金额字段 amountCny);金额列
用 cnyText 补「¥」标识
保留 2 位、空值留白。仅供主网格展开区嵌套只读子表使用。
*/
export
const
detailColumns
:
VxeGridProps
[
'columns'
]
=
[
{
...
...
@@ -116,6 +135,6 @@ export const detailColumns: VxeGridProps['columns'] = [
field
:
'amountCny'
,
minWidth
:
120
,
align
:
'center'
,
formatter
:
({
cellValue
})
=>
amount
Text
(
cellValue
),
formatter
:
({
cellValue
})
=>
cny
Text
(
cellValue
),
},
];
frontend/src/views/ai/consume/index.vue
View file @
40305b50
...
...
@@ -75,10 +75,15 @@ const gridOptions = withDefaultVxeGridOptions<Consume>({
}),
consumeSummary
(
values
),
]);
// 合计写入独立 footerData;amountCny 键对齐 data.ts「项目总金额」列 field——
// 合计口径为命中明细金额之和,落「项目总金额」列、其余列留空(design D5,req1);值来源仍是 summary 的 amountCny 单值
// 合计写入独立 footerData;amountCny / consumePoints 键对齐 data.ts「项目总金额」/「消费积分」列 field——
// 合计口径为命中明细金额 / 积分之和,落「项目总金额」与「消费积分」两列、其余列留空(design D3/D5);值来源为 summary 双字段。
// 金额值补「¥」前缀(spec「消费记录列表底部合计行」);积分非金额列不加标识
footerData
.
value
=
[
{
deptName
:
'合计'
,
amountCny
:
Number
(
sum
.
amountCny
).
toFixed
(
2
)
},
{
deptName
:
'合计'
,
amountCny
:
`¥
${
Number
(
sum
.
amountCny
).
toFixed
(
2
)}
`
,
consumePoints
:
Number
(
sum
.
consumePoints
??
0
).
toFixed
(
2
),
},
];
// 主网格每行 = 一个项目父行,children 原样保留供展开子表直接渲染(design D7);
// 明细移入独立子表实例,父子不再同表格 → 无需子行字段改名 / 复合键 _rid / rowType 判别
...
...
frontend/src/views/ai/recharge/data.ts
View file @
40305b50
...
...
@@ -3,6 +3,19 @@ import type { VxeGridProps } from 'vxe-table';
import
{
DictEnum
}
from
'@/constants'
;
import
{
renderDict
}
from
'@/utils/render'
;
/**
* 币种金额格式化:空值/空串留白;已带对应币种标识的值直通(幂等,兼容 footer 单元格复用列 formatter),
* 否则补「标识 + 原始数值」前缀(不强制补位小数,保持后端返回展示精度)。
* 用于「充值金额(美元)」「充值金额(人民币)」列(spec「充值记录列表金额列币种标识」)
*/
function
currencyText
(
value
:
null
|
number
|
string
|
undefined
,
symbol
:
string
):
string
{
if
(
value
==
null
||
value
===
''
)
{
return
''
;
}
const
text
=
String
(
value
);
return
text
.
startsWith
(
symbol
)
?
text
:
`
${
symbol
}${
text
}
`
;
}
export
const
columns
:
VxeGridProps
[
'columns'
]
=
[
{
type
:
'checkbox'
,
width
:
60
},
{
...
...
@@ -41,10 +54,12 @@ export const columns: VxeGridProps['columns'] = [
{
title
:
'充值金额(美元)'
,
field
:
'amountUsd'
,
formatter
:
({
cellValue
})
=>
currencyText
(
cellValue
,
'$'
),
},
{
title
:
'充值金额(人民币)'
,
field
:
'amountCny'
,
formatter
:
({
cellValue
})
=>
currencyText
(
cellValue
,
'¥'
),
},
{
title
:
'开通账号数量'
,
...
...
frontend/src/views/ai/recharge/index.vue
View file @
40305b50
...
...
@@ -73,12 +73,13 @@ const gridOptions = withDefaultVxeGridOptions<Recharge>({
}),
rechargeSummary
(
values
),
]);
// 合计写入独立 footerData;两金额键按 data.ts 列 field 对齐;toFixed(2) 规避 JSON 数值退化(design D6)
// 合计写入独立 footerData;两金额键按 data.ts 列 field 对齐;toFixed(2) 规避 JSON 数值退化(design D6)。
// 合计值补币种标识($ / ¥,spec「充值记录列表底部合计行」)
footerData
.
value
=
[
{
deptName
:
'合计'
,
amountUsd
:
Number
(
sum
.
amountUsd
).
toFixed
(
2
)
,
amountCny
:
Number
(
sum
.
amountCny
).
toFixed
(
2
)
,
amountUsd
:
`$
${
Number
(
sum
.
amountUsd
).
toFixed
(
2
)}
`
,
amountCny
:
`¥
${
Number
(
sum
.
amountCny
).
toFixed
(
2
)}
`
,
},
];
return
listRes
;
...
...
frontend/src/views/ai/recharge/recharge-drawer.vue
View file @
40305b50
...
...
@@ -224,6 +224,7 @@ async function handleClosed() {
</FormItem>
<FormItem
label=
"充值金额(美元)"
name=
"amountUsd"
>
<InputNumber
prefix=
"$"
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
...
...
@@ -233,6 +234,7 @@ async function handleClosed() {
</FormItem>
<FormItem
label=
"充值金额(人民币)"
name=
"amountCny"
:rules=
"formRules.amountCny"
>
<InputNumber
prefix=
"¥"
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment