Commit 40305b50 authored by 刘斌's avatar 刘斌

fix: 完善消费记录为父子表结构

parent 6ceb318f
......@@ -59,10 +59,10 @@ public class AiConsumeProjectCtrl {
}
/**
* 查询当前筛选条件下的项目总金额合计(跨所有分页的全量结果,口径与列表完全一致)。
* 查询当前筛选条件下的命中明细金额合计与积分合计(跨所有分页的全量结果,口径与列表完全一致)。
*
* @param param 查询条件(与分页查询共用同一条件对象)
* @return 合计载体(amountCny 保留两位小数;无匹配数据时为 0.00)
* @return 合计载体(amountCny 明细金额合计 + consumePoints 明细积分合计,均保留两位小数;无匹配数据时均为 0.00)
*/
@SaCheckPermission("ai:consume:list")
@GetMapping("/summary")
......
......@@ -59,6 +59,11 @@ public class AiConsumeProject extends TenantModel {
*/
private BigDecimal amountCny;
/**
* 消费积分(= Σ 子表明细积分,随明细增删改事务聚合重算)
*/
private BigDecimal consumePoints;
/**
* 每分钟金额(= 项目总金额×60÷成片时长,2位小数HALF_UP;成片时长为0时为NULL;派生计算属 C2)
*/
......
......@@ -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「导出成片时长格式化」)
......
......@@ -69,6 +69,11 @@ public class AiConsumeProjectVo implements Serializable {
*/
private BigDecimal amountCny;
/**
* 消费积分(= Σ 子表明细积分,事务重算冗余存储)
*/
private BigDecimal consumePoints;
/**
* 成片时长(秒)
*/
......
......@@ -24,4 +24,9 @@ public class AiConsumeRecordSummaryVo implements Serializable {
*/
private BigDecimal amountCny;
/**
* 消费积分合计:当前查询条件筛选出的全部命中明细子行积分(跨所有分页)求和,保留两位小数(与 amountCny 同口径)
*/
private BigDecimal consumePoints;
}
......@@ -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);
......
......@@ -107,8 +107,9 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
.build();
List<AiConsumeProject> parents = aiConsumeProjectMapper.selectList(lambdaQuery);
List<AiConsumeExportVo> rows = new ArrayList<>();
//合计 = Σ 命中明细子行金额(口径与列表底部合计一致;无 toolName 时等于父表项目总金额之和);无数据落 ZERO 仍追加合计行
//合计 = Σ 命中明细子行金额 / 积分(口径与列表底部合计一致;无 toolName 时金额合计等于父表项目总金额之和);无数据落 ZERO 仍追加合计行
BigDecimal total = BigDecimal.ZERO;
BigDecimal totalPoints = BigDecimal.ZERO;
if (CollUtil.isNotEmpty(parents)) {
//② project_id IN 批量查明细(toolName 过滤),line_sort 升序
List<Long> projectIds = parents.stream().map(AiConsumeProject::getId).collect(Collectors.toList());
......@@ -121,6 +122,11 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
.filter(ObjectUtil::isNotNull)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.HALF_UP);
//合计 = Σ details.consumePoints(同一命中明细集;NULL 明细积分忽略,spec「导出末尾合计行」)
totalPoints = details.stream().map(AiConsumeRecord::getConsumePoints)
.filter(ObjectUtil::isNotNull)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.HALF_UP);
Map<Long, List<AiConsumeRecord>> detailsByProject = details.stream()
.collect(Collectors.groupingBy(AiConsumeRecord::getProjectId));
//③ 批量回填部门名(导出链不触发 JSON 翻译,spec「部门名称展示与导出翻译」)
......@@ -128,20 +134,27 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
.filter(ObjectUtil::isNotNull).collect(Collectors.toSet());
Map<Long, String> deptNames = CollUtil.isEmpty(deptIds)
? Collections.emptyMap() : sysDeptServ.selectDeptNamesByIds(deptIds);
//④ JOIN 展开扁平行:每明细一行 + 父级汇总列重复
//④ JOIN 展开扁平行:每明细一行;父级汇总列仅该项目首条明细行(line_sort 升序的首条命中明细)写入,其余明细行留空(spec「消费记录导出末尾合计行」)
for (AiConsumeProject parent : parents) {
String deptName = deptNames.get(parent.getDeptId());
for (AiConsumeRecord child : detailsByProject.getOrDefault(parent.getId(), Collections.emptyList())) {
List<AiConsumeRecord> projectDetails = detailsByProject.getOrDefault(parent.getId(), Collections.emptyList());
for (int i = 0; i < projectDetails.size(); i++) {
AiConsumeRecord child = projectDetails.get(i);
AiConsumeExportVo row = new AiConsumeExportVo();
if (i == 0) {
//首条明细行:写入父级汇总列(含部门名);非首行不写入,保持 null 由 Fesod 输出空单元格
row.setDeptId(parent.getDeptId());
row.setDeptName(deptName);
row.setProjectName(parent.getProjectName());
row.setUserName(parent.getUserName());
row.setConsumeTime(parent.getConsumeTime());
row.setAmountCny(parent.getAmountCny());
row.setProjectConsumePoints(parent.getConsumePoints());
row.setDurationText(formatDuration(parent.getDurationSeconds()));
row.setAmountPerMinute(parent.getAmountPerMinute());
row.setRemark(parent.getRemark());
}
//明细列逐行照写
row.setToolName(child.getToolName());
row.setRegion(child.getRegion());
row.setConsumePoints(child.getConsumePoints());
......@@ -151,28 +164,30 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
}
}
//⑤ 末尾追加合计行(无数据时 total=0.00 仍追加,spec「导出文件末尾含合计行」)
rows.add(buildTotalRow(total));
rows.add(buildTotalRow(total, totalPoints));
return rows;
}
/**
* 查询当前条件下的命中明细金额合计(跨所有分页的全量筛选结果,口径与列表/导出展示明细逐一等价)
* 查询当前条件下的命中明细金额合计与积分合计(跨所有分页的全量筛选结果,口径与列表/导出展示明细逐一等价)
*
* <p>合计 = Σ 命中明细子行 {@code ai_consume_record.amount_cny}:先复用 {@link #buildProjectCondition}
* 取命中父行 id 集合(含 toolName EXISTS 命中),再对子表按 {@code project_id IN + toolName LIKE} 做 SQL SUM。
* 按 toolName 过滤时合计仅覆盖匹配明细,不回退父行全项目总金额(spec「消费记录合计查询接口」)。</p>
* <p>合计 = Σ 命中明细子行 {@code ai_consume_record.amount_cny} 与 {@code consume_points}:先复用
* {@link #buildProjectCondition} 取命中父行 id 集合(含 toolName EXISTS 命中),再对子表按
* {@code project_id IN + toolName LIKE} 做 SQL SUM。按 toolName 过滤时合计仅覆盖匹配明细,
* 不回退父行全项目汇总值(spec「消费记录合计查询接口」双字段)。</p>
*
* @param param 查询条件(与分页查询共用同一条件对象)
* @return 合计载体(amountCny 为明细金额合计,保留两位小数;无匹配数据时为 0.00)
* @return 合计载体(amountCny 明细金额合计 + consumePoints 明细积分合计,均保留两位小数;无匹配数据时均为 0.00)
*/
@Override
public AiConsumeRecordSummaryVo querySummary(AiConsumeProjectListParam param) {
AiConsumeRecordSummaryVo summary = new AiConsumeRecordSummaryVo();
//① 复用 buildProjectCondition 取命中父行(口径与列表/导出完全一致,含 toolName EXISTS 命中)
List<AiConsumeProject> parents = aiConsumeProjectMapper.selectList(buildProjectCondition(param).build());
//② 命中父行为空 → 合计 0.00,避免 in(空集合) 退化并省去无谓子查询(design D6)
//② 命中父行为空 → 双合计 0.00,避免 in(空集合) 退化并省去无谓子查询(design D6)
if (CollUtil.isEmpty(parents)) {
summary.setAmountCny(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
summary.setConsumePoints(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
return summary;
}
List<Long> projectIds = parents.stream().map(AiConsumeProject::getId).collect(Collectors.toList());
......@@ -189,6 +204,17 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
? BigDecimal.ZERO
: Convert.toBigDecimal(objs.get(0), BigDecimal.ZERO);
summary.setAmountCny(amountCny.setScale(2, RoundingMode.HALF_UP));
//④ 同口径 SQL SUM 命中明细积分(与金额共用命中范围;NULL 由 SUM 忽略,spec「消费记录合计查询接口」双字段)
LambdaQueryWrapper<AiConsumeRecord> pointsSumWrapper = QueryBuilder.lambda(AiConsumeRecord.class)
.in(AiConsumeRecord::getProjectId, projectIds)
.likeIfText(AiConsumeRecord::getToolName, param.getToolName())
.selectSum(AiConsumeRecord::getConsumePoints)
.build();
List<Object> pointsObjs = aiConsumeRecordMapper.selectObjs(pointsSumWrapper);
BigDecimal consumePoints = CollUtil.isEmpty(pointsObjs)
? BigDecimal.ZERO
: Convert.toBigDecimal(pointsObjs.get(0), BigDecimal.ZERO);
summary.setConsumePoints(consumePoints.setScale(2, RoundingMode.HALF_UP));
return summary;
}
......@@ -321,11 +347,12 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
}
/**
* 全量重算项目总金额 + 派生每分钟金额并回写父表(design D4,Clarify Q2 全量 SUM 非增量)。
* 全量重算项目总金额 / 消费积分 + 派生每分钟金额并回写父表(design D2/D4,全量 SUM 非增量)。
*
* <p>项目总金额 = SUM(子表 amount_cny WHERE project_id AND deleted=0)(租户由拦截器注入,NULL 落 ZERO);
* 消费积分 = SUM(子表 consume_points WHERE project_id AND deleted=0)(同口径,NULL 由 SUM 忽略);
* 每分钟金额 = 总金额×60÷成片时长(2 位 HALF_UP),成片时长为 0/null 时置 NULL(避免除零,spec「每分钟金额派生」)。
* 用 LambdaUpdateWrapper 显式 set 以强制写入 NULL(绕过 updateById 的 NOT_NULL 策略)。</p>
* 金额 / 积分 / 每分钟金额以同一条 UPDATE 回写(spec「消费项目消费积分事务重算」);用 LambdaUpdateWrapper 显式 set 以强制写入 NULL(绕过 updateById 的 NOT_NULL 策略)。</p>
*
* @param projectId 项目id
* @param durationSeconds 成片时长(秒)
......@@ -340,6 +367,16 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
? BigDecimal.ZERO
: Convert.toBigDecimal(objs.get(0), BigDecimal.ZERO))
.setScale(2, RoundingMode.HALF_UP);
//消费积分 = SUM(consume_points)(与金额同模式独立 SUM;NULL 由 SQL SUM 忽略,空落 ZERO)
LambdaQueryWrapper<AiConsumeRecord> pointsSumWrapper = QueryBuilder.lambda(AiConsumeRecord.class)
.eq(AiConsumeRecord::getProjectId, projectId)
.selectSum(AiConsumeRecord::getConsumePoints)
.build();
List<Object> pointsObjs = aiConsumeRecordMapper.selectObjs(pointsSumWrapper);
BigDecimal points = (CollUtil.isEmpty(pointsObjs)
? BigDecimal.ZERO
: Convert.toBigDecimal(pointsObjs.get(0), BigDecimal.ZERO))
.setScale(2, RoundingMode.HALF_UP);
BigDecimal amountPerMinute = null;
if (durationSeconds != null && durationSeconds > 0) {
amountPerMinute = total.multiply(BigDecimal.valueOf(60))
......@@ -348,19 +385,23 @@ public class AiConsumeProjectServImpl extends ServiceImpl<AiConsumeProjectMapper
aiConsumeProjectMapper.update(null, new LambdaUpdateWrapper<AiConsumeProject>()
.eq(AiConsumeProject::getId, projectId)
.set(AiConsumeProject::getAmountCny, total)
.set(AiConsumeProject::getConsumePoints, points)
.set(AiConsumeProject::getAmountPerMinute, amountPerMinute));
}
/**
* 构建导出末尾合计行:所属部门列固定「合计」、项目总金额列填全量命中明细金额合计,其余列(含金额(人民币)明细列)留空(spec「导出末尾合计行」,design D5)。
* 构建导出末尾合计行:所属部门列固定「合计」、项目总金额列填全量命中明细金额合计、消费积分列填全量命中明细积分合计,
* 其余列(含金额(人民币)明细列)留空(spec「导出末尾合计行」,design D4)。
*
* @param total 全量命中明细金额合计(两位小数)
* @param totalPoints 全量命中明细积分合计(两位小数)
* @return 合计行
*/
private AiConsumeExportVo buildTotalRow(BigDecimal total) {
private AiConsumeExportVo buildTotalRow(BigDecimal total, BigDecimal totalPoints) {
AiConsumeExportVo totalRow = new AiConsumeExportVo();
totalRow.setDeptName("合计");
totalRow.setAmountCny(total);
totalRow.setProjectConsumePoints(totalPoints);
return totalRow;
}
......
......@@ -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 });
......
......@@ -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;
}
......@@ -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)}
......
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 }) => amountText(cellValue),
formatter: ({ cellValue }) => cnyText(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 }) => amountText(cellValue),
formatter: ({ cellValue }) => cnyText(cellValue),
},
];
......@@ -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 判别
......
......@@ -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: '开通账号数量',
......
......@@ -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;
......
......@@ -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"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment