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
b54c4d35
Commit
b54c4d35
authored
Sep 04, 2026
by
刘斌
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 增加AI管理的充值记录、消费记录
parent
c8b316c7
Show whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
2809 additions
and
11 deletions
+2809
-11
AiConsumeRecordCtrl.java
...java/com/anplus/hr/ai/controller/AiConsumeRecordCtrl.java
+112
-0
AiRechargeRecordCtrl.java
...ava/com/anplus/hr/ai/controller/AiRechargeRecordCtrl.java
+112
-0
AiConsumeRecord.java
...n/java/com/anplus/hr/ai/domain/model/AiConsumeRecord.java
+68
-0
AiRechargeRecord.java
.../java/com/anplus/hr/ai/domain/model/AiRechargeRecord.java
+84
-0
AiConsumeRecordListParam.java
.../anplus/hr/ai/domain/params/AiConsumeRecordListParam.java
+37
-0
AiConsumeRecordParam.java
.../com/anplus/hr/ai/domain/params/AiConsumeRecordParam.java
+79
-0
AiRechargeRecordListParam.java
...anplus/hr/ai/domain/params/AiRechargeRecordListParam.java
+37
-0
AiRechargeRecordParam.java
...com/anplus/hr/ai/domain/params/AiRechargeRecordParam.java
+99
-0
AiConsumeRecordVo.java
...in/java/com/anplus/hr/ai/domain/vo/AiConsumeRecordVo.java
+88
-0
AiRechargeRecordVo.java
...n/java/com/anplus/hr/ai/domain/vo/AiRechargeRecordVo.java
+107
-0
AiConsumeRecordMapper.java
...n/java/com/anplus/hr/ai/mapper/AiConsumeRecordMapper.java
+17
-0
AiRechargeRecordMapper.java
.../java/com/anplus/hr/ai/mapper/AiRechargeRecordMapper.java
+17
-0
AiConsumeRecordServ.java
...in/java/com/anplus/hr/ai/service/AiConsumeRecordServ.java
+68
-0
AiRechargeRecordServ.java
...n/java/com/anplus/hr/ai/service/AiRechargeRecordServ.java
+68
-0
AiConsumeRecordServImpl.java
...om/anplus/hr/ai/service/impl/AiConsumeRecordServImpl.java
+119
-0
AiRechargeRecordServImpl.java
...m/anplus/hr/ai/service/impl/AiRechargeRecordServImpl.java
+119
-0
application.yml
admin/src/main/resources/application.yml
+1
-1
AiConsumeRecordMapper.xml
admin/src/main/resources/mapper/AiConsumeRecordMapper.xml
+7
-0
AiRechargeRecordMapper.xml
admin/src/main/resources/mapper/AiRechargeRecordMapper.xml
+7
-0
.env
frontend/.env
+2
-2
.env.production
frontend/.env.production
+4
-4
index.ts
frontend/src/api/ai/consume/index.ts
+62
-0
model.d.ts
frontend/src/api/ai/consume/model.d.ts
+39
-0
index.ts
frontend/src/api/ai/recharge/index.ts
+62
-0
model.d.ts
frontend/src/api/ai/recharge/model.d.ts
+51
-0
helper.ts
frontend/src/api/helper.ts
+2
-1
dict-enum.ts
frontend/src/constants/dict-enum.ts
+3
-0
authentication.json
frontend/src/locales/langs/zh-CN/authentication.json
+2
-2
ai.ts
frontend/src/router/routes/modules/ai.ts
+43
-0
consume-drawer.vue
frontend/src/views/ai/consume/consume-drawer.vue
+206
-0
consume-search.vue
frontend/src/views/ai/consume/consume-search.vue
+116
-0
data.ts
frontend/src/views/ai/consume/data.ts
+58
-0
index.vue
frontend/src/views/ai/consume/index.vue
+237
-0
data.ts
frontend/src/views/ai/recharge/data.ts
+70
-0
index.vue
frontend/src/views/ai/recharge/index.vue
+237
-0
recharge-drawer.vue
frontend/src/views/ai/recharge/recharge-drawer.vue
+244
-0
recharge-search.vue
frontend/src/views/ai/recharge/recharge-search.vue
+124
-0
vite.config.ts
frontend/vite.config.ts
+1
-1
No files found.
admin/src/main/java/com/anplus/hr/ai/controller/AiConsumeRecordCtrl.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
controller
;
import
cn.dev33.satoken.annotation.SaCheckPermission
;
import
com.alibaba.cola.dto.PageResponse
;
import
com.alibaba.cola.dto.Response
;
import
com.alibaba.cola.dto.SingleResponse
;
import
jakarta.validation.constraints.Min
;
import
jakarta.validation.constraints.NotEmpty
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.*
;
import
com.anplus.hr.ai.domain.params.AiConsumeRecordListParam
;
import
com.anplus.hr.ai.domain.params.AiConsumeRecordParam
;
import
com.anplus.hr.ai.domain.vo.AiConsumeRecordVo
;
import
com.anplus.hr.ai.service.AiConsumeRecordServ
;
import
top.binfast.common.core.constant.BusinessType
;
import
top.binfast.common.core.util.ResponseUtils
;
import
top.binfast.common.excel.annotion.ExcelExport
;
import
top.binfast.common.log.annotation.PinSysLog
;
import
java.util.List
;
/**
* AI工具消费记录
*
* @author LiuBin
* @date 2026-09-04
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping
(
"/ai/consume"
)
public
class
AiConsumeRecordCtrl
{
private
final
AiConsumeRecordServ
aiConsumeRecordServ
;
/**
* 分页查询AI工具消费记录列表。
*
* @param param 查询条件
* @return AI工具消费记录分页结果
*/
@SaCheckPermission
(
"ai:consume:list"
)
@GetMapping
(
"/list"
)
public
PageResponse
<
AiConsumeRecordVo
>
pageList
(
AiConsumeRecordListParam
param
)
{
return
aiConsumeRecordServ
.
queryPageList
(
param
);
}
/**
* 导出AI工具消费记录列表。
*
* @param param 查询条件
*/
@ExcelExport
@SaCheckPermission
(
"ai:consume:export"
)
@PinSysLog
(
value
=
"AI工具消费记录"
,
businessType
=
BusinessType
.
EXPORT
)
@PostMapping
(
"/export"
)
public
List
<
AiConsumeRecordVo
>
export
(
AiConsumeRecordListParam
param
)
{
return
aiConsumeRecordServ
.
queryList
(
param
);
}
/**
* 获取AI工具消费记录详细信息
*
* @param id 主键
*/
@SaCheckPermission
(
"ai:consume:query"
)
@GetMapping
(
"/{id}"
)
public
SingleResponse
<
AiConsumeRecordVo
>
getDetail
(
@PathVariable
@Min
(
1
)
Long
id
)
{
return
SingleResponse
.
of
(
aiConsumeRecordServ
.
queryById
(
id
));
}
/**
* 新增AI工具消费记录。
*
* @param param AI工具消费记录参数
* @return 操作结果
*/
@SaCheckPermission
(
"ai:consume:add"
)
@PinSysLog
(
value
=
"AI工具消费记录"
,
businessType
=
BusinessType
.
INSERT
)
@PostMapping
()
public
Response
add
(
@Validated
@RequestBody
AiConsumeRecordParam
param
)
{
return
ResponseUtils
.
ofResult
(
aiConsumeRecordServ
.
insertByParam
(
param
));
}
/**
* 修改AI工具消费记录。
*
* @param param AI工具消费记录参数
* @return 操作结果
*/
@SaCheckPermission
(
"ai:consume:edit"
)
@PinSysLog
(
value
=
"AI工具消费记录"
,
businessType
=
BusinessType
.
UPDATE
)
@PutMapping
()
public
Response
edit
(
@Validated
@RequestBody
AiConsumeRecordParam
param
)
{
return
ResponseUtils
.
ofResult
(
aiConsumeRecordServ
.
updateByParam
(
param
));
}
/**
* 删除AI工具消费记录
*
* @param ids 主键串
*/
@SaCheckPermission
(
"ai:consume:remove"
)
@PinSysLog
(
value
=
"AI工具消费记录"
,
businessType
=
BusinessType
.
DELETE
)
@DeleteMapping
(
"/{ids}"
)
public
Response
remove
(
@NotEmpty
(
message
=
"主键不能为空"
)
@PathVariable
Long
[]
ids
)
{
return
ResponseUtils
.
ofResult
(
aiConsumeRecordServ
.
delByIds
(
List
.
of
(
ids
)));
}
}
admin/src/main/java/com/anplus/hr/ai/controller/AiRechargeRecordCtrl.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
controller
;
import
cn.dev33.satoken.annotation.SaCheckPermission
;
import
com.alibaba.cola.dto.PageResponse
;
import
com.alibaba.cola.dto.Response
;
import
com.alibaba.cola.dto.SingleResponse
;
import
jakarta.validation.constraints.Min
;
import
jakarta.validation.constraints.NotEmpty
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.*
;
import
com.anplus.hr.ai.domain.params.AiRechargeRecordListParam
;
import
com.anplus.hr.ai.domain.params.AiRechargeRecordParam
;
import
com.anplus.hr.ai.domain.vo.AiRechargeRecordVo
;
import
com.anplus.hr.ai.service.AiRechargeRecordServ
;
import
top.binfast.common.core.constant.BusinessType
;
import
top.binfast.common.core.util.ResponseUtils
;
import
top.binfast.common.excel.annotion.ExcelExport
;
import
top.binfast.common.log.annotation.PinSysLog
;
import
java.util.List
;
/**
* AI工具充值记录
*
* @author LiuBin
* @date 2026-09-04
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping
(
"/ai/recharge"
)
public
class
AiRechargeRecordCtrl
{
private
final
AiRechargeRecordServ
aiRechargeRecordServ
;
/**
* 分页查询AI工具充值记录列表。
*
* @param param 查询条件
* @return AI工具充值记录分页结果
*/
@SaCheckPermission
(
"ai:recharge:list"
)
@GetMapping
(
"/list"
)
public
PageResponse
<
AiRechargeRecordVo
>
pageList
(
AiRechargeRecordListParam
param
)
{
return
aiRechargeRecordServ
.
queryPageList
(
param
);
}
/**
* 导出AI工具充值记录列表。
*
* @param param 查询条件
*/
@ExcelExport
@SaCheckPermission
(
"ai:recharge:export"
)
@PinSysLog
(
value
=
"AI工具充值记录"
,
businessType
=
BusinessType
.
EXPORT
)
@PostMapping
(
"/export"
)
public
List
<
AiRechargeRecordVo
>
export
(
AiRechargeRecordListParam
param
)
{
return
aiRechargeRecordServ
.
queryList
(
param
);
}
/**
* 获取AI工具充值记录详细信息
*
* @param id 主键
*/
@SaCheckPermission
(
"ai:recharge:query"
)
@GetMapping
(
"/{id}"
)
public
SingleResponse
<
AiRechargeRecordVo
>
getDetail
(
@PathVariable
@Min
(
1
)
Long
id
)
{
return
SingleResponse
.
of
(
aiRechargeRecordServ
.
queryById
(
id
));
}
/**
* 新增AI工具充值记录。
*
* @param param AI工具充值记录参数
* @return 操作结果
*/
@SaCheckPermission
(
"ai:recharge:add"
)
@PinSysLog
(
value
=
"AI工具充值记录"
,
businessType
=
BusinessType
.
INSERT
)
@PostMapping
()
public
Response
add
(
@Validated
@RequestBody
AiRechargeRecordParam
param
)
{
return
ResponseUtils
.
ofResult
(
aiRechargeRecordServ
.
insertByParam
(
param
));
}
/**
* 修改AI工具充值记录。
*
* @param param AI工具充值记录参数
* @return 操作结果
*/
@SaCheckPermission
(
"ai:recharge:edit"
)
@PinSysLog
(
value
=
"AI工具充值记录"
,
businessType
=
BusinessType
.
UPDATE
)
@PutMapping
()
public
Response
edit
(
@Validated
@RequestBody
AiRechargeRecordParam
param
)
{
return
ResponseUtils
.
ofResult
(
aiRechargeRecordServ
.
updateByParam
(
param
));
}
/**
* 删除AI工具充值记录
*
* @param ids 主键串
*/
@SaCheckPermission
(
"ai:recharge:remove"
)
@PinSysLog
(
value
=
"AI工具充值记录"
,
businessType
=
BusinessType
.
DELETE
)
@DeleteMapping
(
"/{ids}"
)
public
Response
remove
(
@NotEmpty
(
message
=
"主键不能为空"
)
@PathVariable
Long
[]
ids
)
{
return
ResponseUtils
.
ofResult
(
aiRechargeRecordServ
.
delByIds
(
List
.
of
(
ids
)));
}
}
admin/src/main/java/com/anplus/hr/ai/domain/model/AiConsumeRecord.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
model
;
import
top.binfast.common.mybatis.bean.model.TenantModel
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
lombok.Getter
;
import
lombok.Setter
;
import
lombok.EqualsAndHashCode
;
import
java.io.Serial
;
import
java.math.BigDecimal
;
import
java.time.LocalDateTime
;
/**
* AI工具消费记录对象 ai_consume_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Getter
@Setter
@EqualsAndHashCode
(
callSuper
=
true
)
@TableName
(
"ai_consume_record"
)
public
class
AiConsumeRecord
extends
TenantModel
{
@Serial
private
static
final
long
serialVersionUID
=
1L
;
/**
* 项目名称(字典ai_project_name的dict_value)
*/
private
String
projectName
;
/**
* 使用人姓名(支持模糊查询)
*/
private
String
userName
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
private
String
toolName
;
/**
* 国内/国外(中文原文,固定二值)
*/
private
String
region
;
/**
* 消耗积分
*/
private
BigDecimal
consumePoints
;
/**
* 金额(人民币)
*/
private
BigDecimal
amountCny
;
/**
* 消费发生时间
*/
private
LocalDateTime
consumeTime
;
/**
* 备注
*/
private
String
remark
;
}
admin/src/main/java/com/anplus/hr/ai/domain/model/AiRechargeRecord.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
model
;
import
top.binfast.common.mybatis.bean.model.TenantModel
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
lombok.Getter
;
import
lombok.Setter
;
import
lombok.EqualsAndHashCode
;
import
java.io.Serial
;
import
java.math.BigDecimal
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
/**
* AI工具充值记录对象 ai_recharge_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Getter
@Setter
@EqualsAndHashCode
(
callSuper
=
true
)
@TableName
(
"ai_recharge_record"
)
public
class
AiRechargeRecord
extends
TenantModel
{
@Serial
private
static
final
long
serialVersionUID
=
1L
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
private
String
toolName
;
/**
* 国内/国外(中文原文,固定二值)
*/
private
String
region
;
/**
* 充值分类(字典ai_recharge_category的dict_value编码)
*/
private
String
rechargeCategory
;
/**
* 充值完成时间
*/
private
LocalDateTime
rechargeTime
;
/**
* 到期时间(纯字段,无联动)
*/
private
LocalDate
expireTime
;
/**
* 充值积分
*/
private
BigDecimal
rechargePoints
;
/**
* 充值金额(美元)
*/
private
BigDecimal
amountUsd
;
/**
* 充值金额(人民币)
*/
private
BigDecimal
amountCny
;
/**
* 开通账号数量
*/
private
Integer
accountCount
;
/**
* 剩余积分(手工维护)
*/
private
BigDecimal
remainingPoints
;
/**
* 备注
*/
private
String
remark
;
}
admin/src/main/java/com/anplus/hr/ai/domain/params/AiConsumeRecordListParam.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
params
;
import
lombok.Getter
;
import
lombok.Setter
;
import
top.binfast.common.core.bean.params.PageQueryParam
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* AI工具消费记录分页对象 ai_consume_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Getter
@Setter
public
class
AiConsumeRecordListParam
extends
PageQueryParam
{
/**
* 项目名称(字典ai_project_name的dict_value)
*/
private
String
projectName
;
/**
* 使用人姓名(支持模糊查询)
*/
private
String
userName
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
private
String
toolName
;
private
Map
<
String
,
Object
>
params
=
new
HashMap
<>();
}
admin/src/main/java/com/anplus/hr/ai/domain/params/AiConsumeRecordParam.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
params
;
import
io.github.linpeilie.annotations.AutoMapper
;
import
jakarta.validation.constraints.Digits
;
import
jakarta.validation.constraints.NotBlank
;
import
jakarta.validation.constraints.NotNull
;
import
jakarta.validation.constraints.Pattern
;
import
jakarta.validation.constraints.Size
;
import
lombok.Data
;
import
com.anplus.hr.ai.domain.model.AiConsumeRecord
;
import
java.math.BigDecimal
;
import
java.time.LocalDateTime
;
/**
* AI工具消费记录业务对象 ai_consume_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Data
@AutoMapper
(
target
=
AiConsumeRecord
.
class
,
reverseConvertGenerate
=
false
)
public
class
AiConsumeRecordParam
{
/**
* 主键ID(新增为空,修改必填)
*/
private
Long
id
;
/**
* 项目名称(字典ai_project_name的dict_value)
*/
@NotBlank
(
message
=
"项目名称不能为空"
)
private
String
projectName
;
/**
* 使用人姓名(支持模糊查询)
*/
@NotBlank
(
message
=
"使用人姓名不能为空"
)
private
String
userName
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
@NotBlank
(
message
=
"AI工具/平台名称不能为空"
)
private
String
toolName
;
/**
* 国内/国外(中文原文,固定二值)
*/
@NotBlank
(
message
=
"国内/国外不能为空"
)
@Pattern
(
regexp
=
"国内|国外"
,
message
=
"国内/国外仅接受「国内」或「国外」"
)
private
String
region
;
/**
* 消耗积分
*/
@Digits
(
integer
=
16
,
fraction
=
2
,
message
=
"消耗积分最多2位小数"
)
private
BigDecimal
consumePoints
;
/**
* 金额(人民币)
*/
@Digits
(
integer
=
16
,
fraction
=
2
,
message
=
"金额(人民币)最多2位小数"
)
private
BigDecimal
amountCny
;
/**
* 消费发生时间
*/
@NotNull
(
message
=
"消费发生时间不能为空"
)
private
LocalDateTime
consumeTime
;
/**
* 备注
*/
@Size
(
max
=
200
,
message
=
"备注最长200字符"
)
private
String
remark
;
}
admin/src/main/java/com/anplus/hr/ai/domain/params/AiRechargeRecordListParam.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
params
;
import
lombok.Getter
;
import
lombok.Setter
;
import
top.binfast.common.core.bean.params.PageQueryParam
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* AI工具充值记录分页对象 ai_recharge_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Getter
@Setter
public
class
AiRechargeRecordListParam
extends
PageQueryParam
{
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
private
String
toolName
;
/**
* 国内/国外(中文原文,固定二值)
*/
private
String
region
;
/**
* 充值分类(字典ai_recharge_category的dict_value编码)
*/
private
String
rechargeCategory
;
private
Map
<
String
,
Object
>
params
=
new
HashMap
<>();
}
admin/src/main/java/com/anplus/hr/ai/domain/params/AiRechargeRecordParam.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
params
;
import
io.github.linpeilie.annotations.AutoMapper
;
import
jakarta.validation.constraints.Digits
;
import
jakarta.validation.constraints.Min
;
import
jakarta.validation.constraints.NotBlank
;
import
jakarta.validation.constraints.NotNull
;
import
jakarta.validation.constraints.Pattern
;
import
jakarta.validation.constraints.Size
;
import
lombok.Data
;
import
com.anplus.hr.ai.domain.model.AiRechargeRecord
;
import
java.math.BigDecimal
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
/**
* AI工具充值记录业务对象 ai_recharge_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Data
@AutoMapper
(
target
=
AiRechargeRecord
.
class
,
reverseConvertGenerate
=
false
)
public
class
AiRechargeRecordParam
{
/**
* 主键ID(新增为空,修改必填)
*/
private
Long
id
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
@NotBlank
(
message
=
"AI工具/平台名称不能为空"
)
private
String
toolName
;
/**
* 国内/国外(中文原文,固定二值)
*/
@NotBlank
(
message
=
"国内/国外不能为空"
)
@Pattern
(
regexp
=
"国内|国外"
,
message
=
"国内/国外仅接受「国内」或「国外」"
)
private
String
region
;
/**
* 充值分类(字典ai_recharge_category的dict_value编码)
*/
@NotBlank
(
message
=
"充值分类不能为空"
)
private
String
rechargeCategory
;
/**
* 充值完成时间
*/
@NotNull
(
message
=
"充值完成时间不能为空"
)
private
LocalDateTime
rechargeTime
;
/**
* 到期时间(纯字段,无联动)
*/
private
LocalDate
expireTime
;
/**
* 充值积分
*/
@Digits
(
integer
=
16
,
fraction
=
2
,
message
=
"充值积分最多2位小数"
)
private
BigDecimal
rechargePoints
;
/**
* 充值金额(美元)
*/
@Digits
(
integer
=
16
,
fraction
=
2
,
message
=
"充值金额(美元)最多2位小数"
)
private
BigDecimal
amountUsd
;
/**
* 充值金额(人民币)
*/
@NotNull
(
message
=
"充值金额(人民币)不能为空"
)
@Digits
(
integer
=
16
,
fraction
=
2
,
message
=
"充值金额(人民币)最多2位小数"
)
private
BigDecimal
amountCny
;
/**
* 开通账号数量
*/
@Min
(
value
=
0
,
message
=
"开通账号数量不能为负数"
)
private
Integer
accountCount
;
/**
* 剩余积分(手工维护)
*/
@Digits
(
integer
=
16
,
fraction
=
2
,
message
=
"剩余积分最多2位小数"
)
private
BigDecimal
remainingPoints
;
/**
* 备注
*/
@Size
(
max
=
200
,
message
=
"备注最长200字符"
)
private
String
remark
;
}
admin/src/main/java/com/anplus/hr/ai/domain/vo/AiConsumeRecordVo.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
vo
;
import
io.github.linpeilie.annotations.AutoMapper
;
import
lombok.Data
;
import
org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated
;
import
org.apache.fesod.sheet.annotation.ExcelProperty
;
import
com.anplus.hr.ai.domain.model.AiConsumeRecord
;
import
top.binfast.common.excel.annotion.ExcelDictFormat
;
import
top.binfast.common.excel.converters.ExcelDictConvert
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
java.math.BigDecimal
;
import
java.time.LocalDateTime
;
/**
* AI工具消费记录视图对象 ai_consume_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper
(
target
=
AiConsumeRecord
.
class
)
public
class
AiConsumeRecordVo
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
1L
;
/**
* 主键ID
*/
private
Long
id
;
/**
* 项目名称(字典ai_project_name的dict_value)
*/
@ExcelProperty
(
value
=
"项目名称"
,
converter
=
ExcelDictConvert
.
class
)
@ExcelDictFormat
(
dictType
=
"ai_project_name"
)
private
String
projectName
;
/**
* 使用人姓名
*/
@ExcelProperty
(
value
=
"姓名"
)
private
String
userName
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
@ExcelProperty
(
value
=
"AI工具/平台名称"
,
converter
=
ExcelDictConvert
.
class
)
@ExcelDictFormat
(
dictType
=
"ai_tool_name"
)
private
String
toolName
;
/**
* 国内/国外(中文原文,固定二值)
*/
@ExcelProperty
(
value
=
"国内/国外"
)
private
String
region
;
/**
* 消耗积分
*/
@ExcelProperty
(
value
=
"消耗积分"
)
private
BigDecimal
consumePoints
;
/**
* 金额(人民币)
*/
@ExcelProperty
(
value
=
"金额(人民币)"
)
private
BigDecimal
amountCny
;
/**
* 消费发生时间
*/
@ExcelProperty
(
value
=
"消费时间"
)
private
LocalDateTime
consumeTime
;
/**
* 备注
*/
@ExcelProperty
(
value
=
"备注"
)
private
String
remark
;
private
LocalDateTime
createTime
;
}
admin/src/main/java/com/anplus/hr/ai/domain/vo/AiRechargeRecordVo.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
domain
.
vo
;
import
io.github.linpeilie.annotations.AutoMapper
;
import
lombok.Data
;
import
org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated
;
import
org.apache.fesod.sheet.annotation.ExcelProperty
;
import
com.anplus.hr.ai.domain.model.AiRechargeRecord
;
import
top.binfast.common.excel.annotion.ExcelDictFormat
;
import
top.binfast.common.excel.converters.ExcelDictConvert
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
java.math.BigDecimal
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
/**
* AI工具充值记录视图对象 ai_recharge_record
*
* @author LiuBin
* @date 2026-09-04
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper
(
target
=
AiRechargeRecord
.
class
)
public
class
AiRechargeRecordVo
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
1L
;
/**
* 主键ID
*/
private
Long
id
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
@ExcelProperty
(
value
=
"AI工具/平台名称"
,
converter
=
ExcelDictConvert
.
class
)
@ExcelDictFormat
(
dictType
=
"ai_tool_name"
)
private
String
toolName
;
/**
* 国内/国外(中文原文,固定二值)
*/
@ExcelProperty
(
value
=
"国内/国外"
)
private
String
region
;
/**
* 充值分类(字典ai_recharge_category的dict_value编码)
*/
@ExcelProperty
(
value
=
"充值分类"
,
converter
=
ExcelDictConvert
.
class
)
@ExcelDictFormat
(
dictType
=
"ai_recharge_category"
)
private
String
rechargeCategory
;
/**
* 充值完成时间
*/
@ExcelProperty
(
value
=
"充值完成时间"
)
private
LocalDateTime
rechargeTime
;
/**
* 到期时间(纯字段,无联动)
*/
@ExcelProperty
(
value
=
"到期时间"
)
private
LocalDate
expireTime
;
/**
* 充值积分
*/
@ExcelProperty
(
value
=
"充值积分"
)
private
BigDecimal
rechargePoints
;
/**
* 充值金额(美元)
*/
@ExcelProperty
(
value
=
"充值金额(美元)"
)
private
BigDecimal
amountUsd
;
/**
* 充值金额(人民币)
*/
@ExcelProperty
(
value
=
"充值金额(人民币)"
)
private
BigDecimal
amountCny
;
/**
* 开通账号数量
*/
@ExcelProperty
(
value
=
"开通账号数量"
)
private
Integer
accountCount
;
/**
* 剩余积分(手工维护)
*/
@ExcelProperty
(
value
=
"剩余积分"
)
private
BigDecimal
remainingPoints
;
/**
* 备注
*/
@ExcelProperty
(
value
=
"备注"
)
private
String
remark
;
private
LocalDateTime
createTime
;
}
admin/src/main/java/com/anplus/hr/ai/mapper/AiConsumeRecordMapper.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
mapper
;
import
com.github.yulichang.base.MPJBaseMapper
;
import
org.apache.ibatis.annotations.Mapper
;
import
com.anplus.hr.ai.domain.model.AiConsumeRecord
;
import
top.binfast.common.mybatis.mapper.BinBaseMapper
;
/**
* AI工具消费记录Mapper接口
*
* @author LiuBin
* @date 2026-09-04
*/
@Mapper
public
interface
AiConsumeRecordMapper
extends
BinBaseMapper
<
AiConsumeRecord
>,
MPJBaseMapper
<
AiConsumeRecord
>
{
}
admin/src/main/java/com/anplus/hr/ai/mapper/AiRechargeRecordMapper.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
mapper
;
import
com.github.yulichang.base.MPJBaseMapper
;
import
org.apache.ibatis.annotations.Mapper
;
import
com.anplus.hr.ai.domain.model.AiRechargeRecord
;
import
top.binfast.common.mybatis.mapper.BinBaseMapper
;
/**
* AI工具充值记录Mapper接口
*
* @author LiuBin
* @date 2026-09-04
*/
@Mapper
public
interface
AiRechargeRecordMapper
extends
BinBaseMapper
<
AiRechargeRecord
>,
MPJBaseMapper
<
AiRechargeRecord
>
{
}
admin/src/main/java/com/anplus/hr/ai/service/AiConsumeRecordServ.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
service
;
import
com.alibaba.cola.dto.PageResponse
;
import
com.baomidou.mybatisplus.spring.service.IService
;
import
com.anplus.hr.ai.domain.model.AiConsumeRecord
;
import
com.anplus.hr.ai.domain.params.AiConsumeRecordListParam
;
import
com.anplus.hr.ai.domain.params.AiConsumeRecordParam
;
import
com.anplus.hr.ai.domain.vo.AiConsumeRecordVo
;
import
java.util.List
;
/**
* AI工具消费记录Service接口
*
* @author LiuBin
* @date 2026-09-04
*/
public
interface
AiConsumeRecordServ
extends
IService
<
AiConsumeRecord
>
{
/**
* 分页查询AI工具消费记录列表
*
* @param param 查询条件
* @return AI工具消费记录分页列表
*/
PageResponse
<
AiConsumeRecordVo
>
queryPageList
(
AiConsumeRecordListParam
param
);
/**
* 查询符合条件的AI工具消费记录列表
*
* @param param 查询条件
* @return AI工具消费记录列表
*/
List
<
AiConsumeRecordVo
>
queryList
(
AiConsumeRecordListParam
param
);
/**
* 查询AI工具消费记录
*
* @param id 主键
* @return AI工具消费记录
*/
AiConsumeRecordVo
queryById
(
Long
id
);
/**
* 新增AI工具消费记录
*
* @param param AI工具消费记录
* @return 是否新增成功
*/
Boolean
insertByParam
(
AiConsumeRecordParam
param
);
/**
* 修改AI工具消费记录
*
* @param param AI工具消费记录
* @return 是否修改成功
*/
Boolean
updateByParam
(
AiConsumeRecordParam
param
);
/**
* 校验并批量删除AI工具消费记录信息
*
* @param ids 待删除的主键集合
* @return 是否删除成功
*/
Boolean
delByIds
(
List
<
Long
>
ids
);
}
admin/src/main/java/com/anplus/hr/ai/service/AiRechargeRecordServ.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
service
;
import
com.alibaba.cola.dto.PageResponse
;
import
com.baomidou.mybatisplus.spring.service.IService
;
import
com.anplus.hr.ai.domain.model.AiRechargeRecord
;
import
com.anplus.hr.ai.domain.params.AiRechargeRecordListParam
;
import
com.anplus.hr.ai.domain.params.AiRechargeRecordParam
;
import
com.anplus.hr.ai.domain.vo.AiRechargeRecordVo
;
import
java.util.List
;
/**
* AI工具充值记录Service接口
*
* @author LiuBin
* @date 2026-09-04
*/
public
interface
AiRechargeRecordServ
extends
IService
<
AiRechargeRecord
>
{
/**
* 分页查询AI工具充值记录列表
*
* @param param 查询条件
* @return AI工具充值记录分页列表
*/
PageResponse
<
AiRechargeRecordVo
>
queryPageList
(
AiRechargeRecordListParam
param
);
/**
* 查询符合条件的AI工具充值记录列表
*
* @param param 查询条件
* @return AI工具充值记录列表
*/
List
<
AiRechargeRecordVo
>
queryList
(
AiRechargeRecordListParam
param
);
/**
* 查询AI工具充值记录
*
* @param id 主键
* @return AI工具充值记录
*/
AiRechargeRecordVo
queryById
(
Long
id
);
/**
* 新增AI工具充值记录
*
* @param param AI工具充值记录
* @return 是否新增成功
*/
Boolean
insertByParam
(
AiRechargeRecordParam
param
);
/**
* 修改AI工具充值记录
*
* @param param AI工具充值记录
* @return 是否修改成功
*/
Boolean
updateByParam
(
AiRechargeRecordParam
param
);
/**
* 校验并批量删除AI工具充值记录信息
*
* @param ids 待删除的主键集合
* @return 是否删除成功
*/
Boolean
delByIds
(
List
<
Long
>
ids
);
}
admin/src/main/java/com/anplus/hr/ai/service/impl/AiConsumeRecordServImpl.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
service
.
impl
;
import
com.alibaba.cola.dto.PageResponse
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.extension.plugins.pagination.Page
;
import
com.baomidou.mybatisplus.spring.service.impl.ServiceImpl
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.stereotype.Service
;
import
com.anplus.hr.ai.domain.model.AiConsumeRecord
;
import
com.anplus.hr.ai.domain.params.AiConsumeRecordListParam
;
import
com.anplus.hr.ai.domain.params.AiConsumeRecordParam
;
import
com.anplus.hr.ai.domain.vo.AiConsumeRecordVo
;
import
com.anplus.hr.ai.mapper.AiConsumeRecordMapper
;
import
com.anplus.hr.ai.service.AiConsumeRecordServ
;
import
top.binfast.common.core.util.MapstructUtils
;
import
top.binfast.common.mybatis.query.LambdaQueryBuilder
;
import
top.binfast.common.mybatis.query.QueryBuilder
;
import
top.binfast.common.mybatis.util.QueryUtil
;
import
java.util.List
;
import
java.util.Map
;
/**
* AI工具消费记录Service业务层处理
*
* @author LiuBin
* @date 2026-09-04
*/
@RequiredArgsConstructor
@Service
public
class
AiConsumeRecordServImpl
extends
ServiceImpl
<
AiConsumeRecordMapper
,
AiConsumeRecord
>
implements
AiConsumeRecordServ
{
private
final
AiConsumeRecordMapper
aiConsumeRecordMapper
;
/**
* 分页查询AI工具消费记录列表
*
* @param param 查询条件
* @return AI工具消费记录分页列表
*/
@Override
public
PageResponse
<
AiConsumeRecordVo
>
queryPageList
(
AiConsumeRecordListParam
param
)
{
Page
<
AiConsumeRecord
>
page
=
QueryUtil
.
getPage
(
param
);
LambdaQueryWrapper
<
AiConsumeRecord
>
lambdaQuery
=
this
.
buildQueryWrapper
(
param
);
aiConsumeRecordMapper
.
selectPage
(
page
,
lambdaQuery
);
return
QueryUtil
.
getPageResponse
(
page
,
MapstructUtils
.
convert
(
page
.
getRecords
(),
AiConsumeRecordVo
.
class
));
}
/**
* 查询符合条件的AI工具消费记录列表
*
* @param param 查询条件
* @return AI工具消费记录列表
*/
@Override
public
List
<
AiConsumeRecordVo
>
queryList
(
AiConsumeRecordListParam
param
)
{
LambdaQueryWrapper
<
AiConsumeRecord
>
lambdaQuery
=
this
.
buildQueryWrapper
(
param
);
return
MapstructUtils
.
convert
(
aiConsumeRecordMapper
.
selectList
(
lambdaQuery
),
AiConsumeRecordVo
.
class
);
}
private
LambdaQueryWrapper
<
AiConsumeRecord
>
buildQueryWrapper
(
AiConsumeRecordListParam
param
)
{
Map
<
String
,
Object
>
params
=
param
.
getParams
();
LambdaQueryBuilder
<
AiConsumeRecord
>
builder
=
QueryBuilder
.
lambda
(
AiConsumeRecord
.
class
)
.
eqIfText
(
AiConsumeRecord:
:
getProjectName
,
param
.
getProjectName
())
.
eqIfText
(
AiConsumeRecord:
:
getToolName
,
param
.
getToolName
())
.
likeIfText
(
AiConsumeRecord:
:
getUserName
,
param
.
getUserName
())
.
betweenParams
(
AiConsumeRecord:
:
getConsumeTime
,
params
,
"beginTime"
,
"endTime"
)
.
orderByDesc
(
AiConsumeRecord:
:
getConsumeTime
);
return
builder
.
build
();
}
/**
* 查询AI工具消费记录
*
* @param id 主键
* @return AI工具消费记录
*/
@Override
public
AiConsumeRecordVo
queryById
(
Long
id
)
{
AiConsumeRecord
aiConsumeRecord
=
aiConsumeRecordMapper
.
selectById
(
id
);
return
MapstructUtils
.
convert
(
aiConsumeRecord
,
AiConsumeRecordVo
.
class
);
}
/**
* 新增AI工具消费记录
*
* @param param AI工具消费记录
* @return 是否新增成功
*/
@Override
public
Boolean
insertByParam
(
AiConsumeRecordParam
param
)
{
AiConsumeRecord
aiConsumeRecord
=
MapstructUtils
.
convert
(
param
,
AiConsumeRecord
.
class
);
return
this
.
save
(
aiConsumeRecord
);
}
/**
* 修改AI工具消费记录
*
* @param param AI工具消费记录
* @return 是否修改成功
*/
@Override
public
Boolean
updateByParam
(
AiConsumeRecordParam
param
)
{
AiConsumeRecord
aiConsumeRecord
=
MapstructUtils
.
convert
(
param
,
AiConsumeRecord
.
class
);
return
this
.
updateById
(
aiConsumeRecord
);
}
/**
* 校验并批量删除AI工具消费记录信息
*
* @param ids 待删除的主键集合
* @return 是否删除成功
*/
@Override
public
Boolean
delByIds
(
List
<
Long
>
ids
)
{
//无关联业务校验,直接逻辑删除(@TableLogic)
return
this
.
removeByIds
(
ids
);
}
}
admin/src/main/java/com/anplus/hr/ai/service/impl/AiRechargeRecordServImpl.java
0 → 100644
View file @
b54c4d35
package
com
.
anplus
.
hr
.
ai
.
service
.
impl
;
import
com.alibaba.cola.dto.PageResponse
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.extension.plugins.pagination.Page
;
import
com.baomidou.mybatisplus.spring.service.impl.ServiceImpl
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.stereotype.Service
;
import
com.anplus.hr.ai.domain.model.AiRechargeRecord
;
import
com.anplus.hr.ai.domain.params.AiRechargeRecordListParam
;
import
com.anplus.hr.ai.domain.params.AiRechargeRecordParam
;
import
com.anplus.hr.ai.domain.vo.AiRechargeRecordVo
;
import
com.anplus.hr.ai.mapper.AiRechargeRecordMapper
;
import
com.anplus.hr.ai.service.AiRechargeRecordServ
;
import
top.binfast.common.core.util.MapstructUtils
;
import
top.binfast.common.mybatis.query.LambdaQueryBuilder
;
import
top.binfast.common.mybatis.query.QueryBuilder
;
import
top.binfast.common.mybatis.util.QueryUtil
;
import
java.util.List
;
import
java.util.Map
;
/**
* AI工具充值记录Service业务层处理
*
* @author LiuBin
* @date 2026-09-04
*/
@RequiredArgsConstructor
@Service
public
class
AiRechargeRecordServImpl
extends
ServiceImpl
<
AiRechargeRecordMapper
,
AiRechargeRecord
>
implements
AiRechargeRecordServ
{
private
final
AiRechargeRecordMapper
aiRechargeRecordMapper
;
/**
* 分页查询AI工具充值记录列表
*
* @param param 查询条件
* @return AI工具充值记录分页列表
*/
@Override
public
PageResponse
<
AiRechargeRecordVo
>
queryPageList
(
AiRechargeRecordListParam
param
)
{
Page
<
AiRechargeRecord
>
page
=
QueryUtil
.
getPage
(
param
);
LambdaQueryWrapper
<
AiRechargeRecord
>
lambdaQuery
=
this
.
buildQueryWrapper
(
param
);
aiRechargeRecordMapper
.
selectPage
(
page
,
lambdaQuery
);
return
QueryUtil
.
getPageResponse
(
page
,
MapstructUtils
.
convert
(
page
.
getRecords
(),
AiRechargeRecordVo
.
class
));
}
/**
* 查询符合条件的AI工具充值记录列表
*
* @param param 查询条件
* @return AI工具充值记录列表
*/
@Override
public
List
<
AiRechargeRecordVo
>
queryList
(
AiRechargeRecordListParam
param
)
{
LambdaQueryWrapper
<
AiRechargeRecord
>
lambdaQuery
=
this
.
buildQueryWrapper
(
param
);
return
MapstructUtils
.
convert
(
aiRechargeRecordMapper
.
selectList
(
lambdaQuery
),
AiRechargeRecordVo
.
class
);
}
private
LambdaQueryWrapper
<
AiRechargeRecord
>
buildQueryWrapper
(
AiRechargeRecordListParam
param
)
{
Map
<
String
,
Object
>
params
=
param
.
getParams
();
LambdaQueryBuilder
<
AiRechargeRecord
>
builder
=
QueryBuilder
.
lambda
(
AiRechargeRecord
.
class
)
.
eqIfText
(
AiRechargeRecord:
:
getToolName
,
param
.
getToolName
())
.
eqIfText
(
AiRechargeRecord:
:
getRegion
,
param
.
getRegion
())
.
eqIfText
(
AiRechargeRecord:
:
getRechargeCategory
,
param
.
getRechargeCategory
())
.
betweenParams
(
AiRechargeRecord:
:
getRechargeTime
,
params
,
"beginTime"
,
"endTime"
)
.
orderByDesc
(
AiRechargeRecord:
:
getRechargeTime
);
return
builder
.
build
();
}
/**
* 查询AI工具充值记录
*
* @param id 主键
* @return AI工具充值记录
*/
@Override
public
AiRechargeRecordVo
queryById
(
Long
id
)
{
AiRechargeRecord
aiRechargeRecord
=
aiRechargeRecordMapper
.
selectById
(
id
);
return
MapstructUtils
.
convert
(
aiRechargeRecord
,
AiRechargeRecordVo
.
class
);
}
/**
* 新增AI工具充值记录
*
* @param param AI工具充值记录
* @return 是否新增成功
*/
@Override
public
Boolean
insertByParam
(
AiRechargeRecordParam
param
)
{
AiRechargeRecord
aiRechargeRecord
=
MapstructUtils
.
convert
(
param
,
AiRechargeRecord
.
class
);
return
this
.
save
(
aiRechargeRecord
);
}
/**
* 修改AI工具充值记录
*
* @param param AI工具充值记录
* @return 是否修改成功
*/
@Override
public
Boolean
updateByParam
(
AiRechargeRecordParam
param
)
{
AiRechargeRecord
aiRechargeRecord
=
MapstructUtils
.
convert
(
param
,
AiRechargeRecord
.
class
);
return
this
.
updateById
(
aiRechargeRecord
);
}
/**
* 校验并批量删除AI工具充值记录信息
*
* @param ids 待删除的主键集合
* @return 是否删除成功
*/
@Override
public
Boolean
delByIds
(
List
<
Long
>
ids
)
{
//无关联业务校验,直接逻辑删除(@TableLogic)
return
this
.
removeByIds
(
ids
);
}
}
admin/src/main/resources/application.yml
View file @
b54c4d35
server
:
server
:
port
:
900
0
port
:
809
0
servlet
:
servlet
:
context-path
:
/api
context-path
:
/api
# 开启优雅停机,默认值:immediate 为立即关闭
# 开启优雅停机,默认值:immediate 为立即关闭
...
...
admin/src/main/resources/mapper/AiConsumeRecordMapper.xml
0 → 100644
View file @
b54c4d35
<?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.anplus.hr.ai.mapper.AiConsumeRecordMapper"
>
</mapper>
admin/src/main/resources/mapper/AiRechargeRecordMapper.xml
0 → 100644
View file @
b54c4d35
<?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.anplus.hr.ai.mapper.AiRechargeRecordMapper"
>
</mapper>
frontend/.env
View file @
b54c4d35
# 应用标题
# 应用标题
VITE_APP_TITLE=
Plus Admin
VITE_APP_TITLE=
AI管理系统
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=
dap-plus-vben
VITE_APP_NAMESPACE=
hr-ai
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
frontend/.env.production
View file @
b54c4d35
...
@@ -10,17 +10,17 @@ VITE_ROUTER_HISTORY=history
...
@@ -10,17 +10,17 @@ VITE_ROUTER_HISTORY=history
VITE_INJECT_APP_LOADING=true
VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
# 打包后是否生成dist.zip
VITE_ARCHIVER=
tru
e
VITE_ARCHIVER=
fals
e
# 后端接口地址
# 后端接口地址
VITE_GLOB_API_URL=/
prod-
api
VITE_GLOB_API_URL=/api
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
VITE_GLOB_ENABLE_ENCRYPT=true
VITE_GLOB_ENABLE_ENCRYPT=true
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ
DvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xA
wIDAQAB
VITE_GLOB_RSA_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ
C44LZ19Io2qIUxZvOfDxpUXy2Kmvcb72cPL0NE3takaNO53To30evTpXvXdY3KdZsubQoBnuxVnCha+jnDN+FZ35gJ50oyvoRG03r24zaZMTe0HlSHsDL/p0e9zn4emRc+2X9wY6XG0ZLNcE/lpy2FiWDCQbXubVi9Bv6c+axUy
wIDAQAB
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PRIVATE_KEY=MIICd
gIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A=
=
VITE_GLOB_RSA_PRIVATE_KEY=MIICd
wIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALjgtnX0ijaohTFm858PGlRfLYqa9xvvZw8vQ0Te1qRo07ndOjfR69Ole9d1jcp1my5tCgGe7FWcKFr6OcM34VnfmAnnSjK+hEbTevbjNpkxN7QeVIewMv+nR73Ofh6ZFz7Zf3BjpcbRks1wT+WnLYWJYMJBte5tWL0G/pz5rFTLAgMBAAECgYAw4s6skxynBlmt7gS2hwZV0BMZRw/Xla7hP4SgpfII6GUwDRUTZJFg1i3Qk9eJ9R4zIQcX1Z+1pYSwReFXQyP1rkYx/pU8fBfsXNmYI4rC4J4fTPeVYaVcjiiw9s0wJHoRl2Hc00+7okWQxA7s9UDFLj96oh/qETDpQ2jaB+aDXQJBAN/PO0XAN2BzvML4VboiVeLnzg83nEkr6Tulg+FaU7LPraPV9j8lVWwibjdGZKJDsBG47BKdRU6319LKOdCHhr0CQQDTd/9kExlnBoYyi/dGVIZDmDvAwufS+WAvxZrGYcV7pCNY5wnW6IfhQlNWJLyhhy8g+cJhQXHU/swhWIlZ7eYnAkBGGLOpvvAZJ2ahcgH3kDVyIcPqBDLQwA4Gfmp1tEKuqFjsDWFYmarNeWMnsMZoA+crRc26bsN8ShwwiuHcq/XJAkEAw8fOD9XK5+JXbga9iUSKBZy0fXM1c9LjJXdaIx6z2hONkipcprYjzv47734Vkap5jJsPRkFz5RF85cZ1a2fgcwJBANvtElF3T6NkHETqVpdCcDbCiQDLaHX3eifUe1z8efVGKOhlTYN/msfCLEgMknxNCOuF+V8Y9ukw9ygN0OUv3L8
=
# 客户端id
# 客户端id
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
...
...
frontend/src/api/ai/consume/index.ts
0 → 100644
View file @
b54c4d35
import
type
{
ID
,
IDS
,
PageQuery
,
PageResult
}
from
'@/api/common'
;
import
type
{
Consume
}
from
'./model'
;
import
{
commonExport
}
from
'@/api/helper'
;
import
{
alovaInstance
}
from
'@/utils/http'
;
enum
Api
{
consumeExport
=
'/ai/consume/export'
,
consumeList
=
'/ai/consume/list'
,
root
=
'/ai/consume'
,
}
/**
* 查询消费记录列表
* @param params 请求参数
* @returns 列表
*/
export
function
consumeList
(
params
?:
PageQuery
)
{
return
alovaInstance
.
get
<
PageResult
<
Consume
>>
(
Api
.
consumeList
,
{
params
});
}
/**
* 导出消费记录excel
* @param data 请求参数
*/
export
function
consumeExport
(
data
:
Partial
<
Consume
>
)
{
return
commonExport
(
Api
.
consumeExport
,
data
);
}
/**
* 消费记录详情
* @param id id
* @returns 详情
*/
export
function
consumeInfo
(
id
:
ID
)
{
return
alovaInstance
.
get
<
Consume
>
(
`
${
Api
.
root
}
/
${
id
}
`
);
}
/**
* 消费记录新增
* @param data 参数
*/
export
function
consumeAdd
(
data
:
Partial
<
Consume
>
)
{
return
alovaInstance
.
postWithMsg
<
void
>
(
Api
.
root
,
data
);
}
/**
* 消费记录修改
* @param data 参数
*/
export
function
consumeUpdate
(
data
:
Partial
<
Consume
>
)
{
return
alovaInstance
.
putWithMsg
<
void
>
(
Api
.
root
,
data
);
}
/**
* 消费记录删除
* @param ids id集合
*/
export
function
consumeRemove
(
ids
:
IDS
)
{
return
alovaInstance
.
deleteWithMsg
<
void
>
(
`
${
Api
.
root
}
/
${
ids
}
`
);
}
frontend/src/api/ai/consume/model.d.ts
0 → 100644
View file @
b54c4d35
/**
* @description: Consume interface
*/
export
interface
Consume
{
id
:
number
;
/**
* 项目名称(字典ai_project_name的dict_value)
*/
projectName
:
string
;
/**
* 使用人姓名
*/
userName
:
string
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
toolName
:
string
;
/**
* 国内/国外(中文原文,固定二值)
*/
region
:
string
;
/**
* 消耗积分
*/
consumePoints
:
number
;
/**
* 金额(人民币)
*/
amountCny
:
number
;
/**
* 消费发生时间
*/
consumeTime
:
string
;
/**
* 备注
*/
remark
:
string
;
createTime
:
string
;
}
frontend/src/api/ai/recharge/index.ts
0 → 100644
View file @
b54c4d35
import
type
{
ID
,
IDS
,
PageQuery
,
PageResult
}
from
'@/api/common'
;
import
type
{
Recharge
}
from
'./model'
;
import
{
commonExport
}
from
'@/api/helper'
;
import
{
alovaInstance
}
from
'@/utils/http'
;
enum
Api
{
rechargeExport
=
'/ai/recharge/export'
,
rechargeList
=
'/ai/recharge/list'
,
root
=
'/ai/recharge'
,
}
/**
* 查询充值记录列表
* @param params 请求参数
* @returns 列表
*/
export
function
rechargeList
(
params
?:
PageQuery
)
{
return
alovaInstance
.
get
<
PageResult
<
Recharge
>>
(
Api
.
rechargeList
,
{
params
});
}
/**
* 导出充值记录excel
* @param data 请求参数
*/
export
function
rechargeExport
(
data
:
Partial
<
Recharge
>
)
{
return
commonExport
(
Api
.
rechargeExport
,
data
);
}
/**
* 充值记录详情
* @param id id
* @returns 详情
*/
export
function
rechargeInfo
(
id
:
ID
)
{
return
alovaInstance
.
get
<
Recharge
>
(
`
${
Api
.
root
}
/
${
id
}
`
);
}
/**
* 充值记录新增
* @param data 参数
*/
export
function
rechargeAdd
(
data
:
Partial
<
Recharge
>
)
{
return
alovaInstance
.
postWithMsg
<
void
>
(
Api
.
root
,
data
);
}
/**
* 充值记录修改
* @param data 参数
*/
export
function
rechargeUpdate
(
data
:
Partial
<
Recharge
>
)
{
return
alovaInstance
.
putWithMsg
<
void
>
(
Api
.
root
,
data
);
}
/**
* 充值记录删除
* @param ids id集合
*/
export
function
rechargeRemove
(
ids
:
IDS
)
{
return
alovaInstance
.
deleteWithMsg
<
void
>
(
`
${
Api
.
root
}
/
${
ids
}
`
);
}
frontend/src/api/ai/recharge/model.d.ts
0 → 100644
View file @
b54c4d35
/**
* @description: Recharge interface
*/
export
interface
Recharge
{
id
:
number
;
/**
* AI工具/平台名称(字典ai_tool_name的dict_value)
*/
toolName
:
string
;
/**
* 国内/国外(中文原文,固定二值)
*/
region
:
string
;
/**
* 充值分类(字典ai_recharge_category的dict_value编码)
*/
rechargeCategory
:
string
;
/**
* 充值完成时间
*/
rechargeTime
:
string
;
/**
* 到期时间
*/
expireTime
:
string
;
/**
* 充值积分
*/
rechargePoints
:
number
;
/**
* 充值金额(美元)
*/
amountUsd
:
number
;
/**
* 充值金额(人民币)
*/
amountCny
:
number
;
/**
* 开通账号数量
*/
accountCount
:
number
;
/**
* 剩余积分
*/
remainingPoints
:
number
;
/**
* 备注
*/
remark
:
string
;
createTime
:
string
;
}
frontend/src/api/helper.ts
View file @
b54c4d35
...
@@ -23,8 +23,9 @@ export const ContentTypeEnum = {
...
@@ -23,8 +23,9 @@ export const ContentTypeEnum = {
export
function
commonExport
(
url
:
string
,
data
:
Record
<
string
,
any
>
)
{
export
function
commonExport
(
url
:
string
,
data
:
Record
<
string
,
any
>
)
{
return
alovaInstance
.
post
<
Blob
>
(
url
,
data
,
{
return
alovaInstance
.
post
<
Blob
>
(
url
,
data
,
{
headers
:
{
'Content-Type'
:
ContentTypeEnum
.
FORM_URLENCODED
},
headers
:
{
'Content-Type'
:
ContentTypeEnum
.
FORM_URLENCODED
},
// t
ransformResponse: false,
isT
ransformResponse
:
false
,
responseType
:
'blob'
,
responseType
:
'blob'
,
timeout
:
600
_000
,
});
});
}
}
...
...
frontend/src/constants/dict-enum.ts
View file @
b54c4d35
export
const
DictEnum
=
{
export
const
DictEnum
=
{
AI_PROJECT_NAME
:
'ai_project_name'
,
// 项目名称
AI_RECHARGE_CATEGORY
:
'ai_recharge_category'
,
// 充值分类
AI_TOOL_NAME
:
'ai_tool_name'
,
// AI工具/平台名称
SYS_COMMON_STATUS
:
'sys_common_status'
,
SYS_COMMON_STATUS
:
'sys_common_status'
,
SYS_DEVICE_TYPE
:
'sys_device_type'
,
// 设备类型
SYS_DEVICE_TYPE
:
'sys_device_type'
,
// 设备类型
SYS_GRANT_TYPE
:
'sys_grant_type'
,
// 授权类型
SYS_GRANT_TYPE
:
'sys_grant_type'
,
// 授权类型
...
...
frontend/src/locales/langs/zh-CN/authentication.json
View file @
b54c4d35
{
{
"welcomeBack"
:
"欢迎回来"
,
"welcomeBack"
:
"欢迎回来"
,
"pageTitle"
:
"
开箱即用的大型中后台
管理系统"
,
"pageTitle"
:
"
AI
管理系统"
,
"pageDesc"
:
"
工程化、高性能、跨组件库的前端模版
"
,
"pageDesc"
:
"
让每笔钱都花的明明白白
"
,
"loginSuccess"
:
"登录成功"
,
"loginSuccess"
:
"登录成功"
,
"loginSuccessDesc"
:
"欢迎回来"
,
"loginSuccessDesc"
:
"欢迎回来"
,
"loginSubtitle"
:
"请输入您的账户信息以开始管理您的项目"
,
"loginSubtitle"
:
"请输入您的账户信息以开始管理您的项目"
,
...
...
frontend/src/router/routes/modules/ai.ts
0 → 100644
View file @
b54c4d35
import
type
{
RouteRecordRaw
}
from
'vue-router'
;
const
routes
:
RouteRecordRaw
[]
=
[
// AI工具平台
{
path
:
'/ai'
,
name
:
'Ai'
,
meta
:
{
title
:
'AI工具平台'
,
keepAlive
:
true
,
icon
:
'lucide:bot'
,
permission
:
[
'dashboard'
],
},
children
:
[
{
path
:
'recharge'
,
name
:
'AiRechargeList'
,
component
:
()
=>
import
(
'@/views/ai/recharge/index.vue'
),
meta
:
{
title
:
'充值记录'
,
icon
:
'lucide:wallet'
,
keepAlive
:
true
,
permission
:
[
'dashboard'
],
componentPath
:
'@/views/ai/recharge/index.vue'
,
},
},
{
path
:
'consume'
,
name
:
'AiConsumeList'
,
component
:
()
=>
import
(
'@/views/ai/consume/index.vue'
),
meta
:
{
title
:
'消费记录'
,
icon
:
'lucide:receipt'
,
keepAlive
:
true
,
permission
:
[
'dashboard'
],
componentPath
:
'@/views/ai/consume/index.vue'
,
},
},
],
},
];
export
default
routes
;
frontend/src/views/ai/consume/consume-drawer.vue
0 → 100644
View file @
b54c4d35
<
script
setup
lang=
"ts"
>
import
type
{
Consume
}
from
'@/api/ai/consume/model'
;
import
type
{
AntdFormRules
}
from
'@/types/form'
;
import
type
{
FormInstance
}
from
'antdv-next'
;
import
{
computed
,
ref
}
from
'vue'
;
import
{
consumeAdd
,
consumeInfo
,
consumeUpdate
}
from
'@/api/ai/consume'
;
import
{
useVbenDrawer
}
from
'@/components'
;
import
{
FormInput
as
Input
,
FormInputNumber
as
InputNumber
,
FormSelect
as
Select
,
FormTextArea
as
TextArea
,
}
from
'@/components/global/form'
;
import
{
DictEnum
}
from
'@/constants'
;
import
{
$t
}
from
'@/locales'
;
import
{
cloneDeep
,
getPopupContainer
}
from
'@/utils'
;
import
{
getDictOptions
}
from
'@/utils/dict'
;
import
{
useBeforeCloseDiff
}
from
'@/utils/popup'
;
import
{
DatePicker
,
Form
,
FormItem
}
from
'antdv-next'
;
const
emit
=
defineEmits
<
{
reload
:
[]
}
>
();
const
isUpdate
=
ref
(
false
);
const
title
=
computed
(()
=>
{
return
isUpdate
.
value
?
$t
(
'pages.common.edit'
)
:
$t
(
'pages.common.add'
);
});
type
FormData
=
Partial
<
Consume
>
;
// 国内/国外为固定二值(非字典,Clarify C2')
const
regionOptions
=
[
{
label
:
'国内'
,
value
:
'国内'
},
{
label
:
'国外'
,
value
:
'国外'
},
];
function
getDefaultValues
():
FormData
{
return
{
id
:
undefined
,
projectName
:
undefined
,
userName
:
undefined
,
toolName
:
undefined
,
region
:
undefined
,
consumePoints
:
undefined
,
amountCny
:
undefined
,
consumeTime
:
undefined
,
remark
:
''
,
};
}
const
formData
=
ref
<
FormData
>
(
getDefaultValues
());
const
formInstance
=
ref
<
FormInstance
>
();
const
formRules
=
ref
<
AntdFormRules
<
FormData
>>
({
projectName
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.selectRequired'
)
}],
userName
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.required'
)
}],
toolName
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.selectRequired'
)
}],
region
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.selectRequired'
)
}],
consumeTime
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.required'
)
}],
});
function
customFormValueGetter
()
{
return
JSON
.
stringify
(
formData
.
value
);
}
const
{
onBeforeClose
,
markInitialized
,
resetInitialized
}
=
useBeforeCloseDiff
(
{
initializedGetter
:
customFormValueGetter
,
currentGetter
:
customFormValueGetter
,
},
);
const
[
BasicDrawer
,
drawerApi
]
=
useVbenDrawer
({
onBeforeClose
,
onClosed
:
handleClosed
,
onConfirm
:
handleConfirm
,
async
onOpenChange
(
isOpen
)
{
if
(
!
isOpen
)
{
return
null
;
}
drawerApi
.
drawerLoading
(
true
);
const
{
id
}
=
drawerApi
.
getData
()
as
{
id
?:
number
|
string
};
isUpdate
.
value
=
!!
id
;
// 更新 && 赋值
if
(
isUpdate
.
value
&&
id
)
{
const
record
=
await
consumeInfo
(
id
);
formData
.
value
=
{
...
getDefaultValues
(),
...
record
,
remark
:
record
.
remark
??
''
,
};
}
await
markInitialized
();
drawerApi
.
drawerLoading
(
false
);
},
});
async
function
handleConfirm
()
{
try
{
drawerApi
.
lock
(
true
);
await
formInstance
.
value
?.
validate
();
const
data
=
cloneDeep
(
formData
.
value
);
await
(
isUpdate
.
value
?
consumeUpdate
(
data
)
:
consumeAdd
(
data
));
resetInitialized
();
emit
(
'reload'
);
drawerApi
.
close
();
}
catch
(
error
)
{
console
.
error
(
error
);
}
finally
{
drawerApi
.
lock
(
false
);
}
}
async
function
handleClosed
()
{
formData
.
value
=
getDefaultValues
();
formInstance
.
value
?.
resetFields
();
resetInitialized
();
}
</
script
>
<
template
>
<BasicDrawer
:title=
"title"
:size=
"700"
>
<Form
ref=
"formInstance"
:model=
"formData"
:label-col=
"
{ style: { width: '140px' } }"
>
<FormItem
label=
"项目名称"
name=
"projectName"
:rules=
"formRules.projectName"
>
<Select
class=
"w-full"
:allow-clear=
"false"
option-filter-prop=
"label"
show-search
:get-popup-container=
"getPopupContainer"
:options=
"getDictOptions(DictEnum.AI_PROJECT_NAME)"
v-model:value=
"formData.projectName"
/>
</FormItem>
<FormItem
label=
"使用人姓名"
name=
"userName"
:rules=
"formRules.userName"
>
<Input
class=
"w-full"
allow-clear
v-model:value=
"formData.userName"
/>
</FormItem>
<FormItem
label=
"AI工具/平台名称"
name=
"toolName"
:rules=
"formRules.toolName"
>
<Select
class=
"w-full"
:allow-clear=
"false"
option-filter-prop=
"label"
show-search
:get-popup-container=
"getPopupContainer"
:options=
"getDictOptions(DictEnum.AI_TOOL_NAME)"
v-model:value=
"formData.toolName"
/>
</FormItem>
<FormItem
label=
"国内/国外"
name=
"region"
:rules=
"formRules.region"
>
<Select
class=
"w-full"
:allow-clear=
"false"
:get-popup-container=
"getPopupContainer"
:options=
"regionOptions"
v-model:value=
"formData.region"
/>
</FormItem>
<FormItem
label=
"消费时间"
name=
"consumeTime"
:rules=
"formRules.consumeTime"
>
<DatePicker
class=
"w-full"
show-time
format=
"YYYY-MM-DD HH:mm:ss"
value-format=
"YYYY-MM-DD HH:mm:ss"
v-model:value=
"formData.consumeTime"
/>
</FormItem>
<FormItem
label=
"消耗积分"
name=
"consumePoints"
>
<InputNumber
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
:precision="2"
v-model:value="formData.consumePoints"
/>
</FormItem>
<FormItem
label=
"金额(人民币)"
name=
"amountCny"
>
<InputNumber
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
:precision="2"
v-model:value="formData.amountCny"
/>
</FormItem>
<FormItem
label=
"备注"
name=
"remark"
>
<TextArea
allow-clear
class=
"w-full"
:rows=
"4"
:maxlength=
"200"
show-count
v-model:value=
"formData.remark"
/>
</FormItem>
</Form>
</BasicDrawer>
</
template
>
frontend/src/views/ai/consume/consume-search.vue
0 → 100644
View file @
b54c4d35
<
script
setup
lang=
"ts"
>
import
type
{
FormInstance
}
from
'antdv-next'
;
import
type
{
Dayjs
}
from
'dayjs'
;
import
{
ref
}
from
'vue'
;
import
{
FormInput
,
FormSelect
}
from
'@/components/global/form'
;
import
{
SearchButtonGroup
}
from
'@/components/table'
;
import
{
tableSeachClass
}
from
'@/components/vxe-table'
;
import
{
DictEnum
}
from
'@/constants'
;
import
{
formatDateTime
}
from
'@/utils'
;
import
{
getDictOptions
}
from
'@/utils/dict'
;
import
{
Card
,
DateRangePicker
,
Form
,
FormItem
}
from
'antdv-next'
;
const
emit
=
defineEmits
<
{
reset
:
[];
submit
:
[
record
:
Record
<
string
,
any
>
];
}
>
();
interface
ConsumeSearchFormParams
{
projectName
?:
string
;
userName
?:
string
;
toolName
?:
string
;
consumeTime
?:
[
Dayjs
,
Dayjs
];
}
const
model
=
ref
<
ConsumeSearchFormParams
>
({
projectName
:
undefined
,
userName
:
undefined
,
toolName
:
undefined
,
consumeTime
:
undefined
,
});
const
formInstance
=
ref
<
FormInstance
>
();
const
searchCollapsed
=
ref
(
false
);
function
buildSearchParams
(
values
:
ConsumeSearchFormParams
)
{
const
params
:
Record
<
string
,
any
>
=
{
...
values
};
if
(
params
.
consumeTime
)
{
params
.
params
=
{
beginTime
:
formatDateTime
(
params
.
consumeTime
[
0
]),
endTime
:
formatDateTime
(
params
.
consumeTime
[
1
]),
};
Reflect
.
deleteProperty
(
params
,
'consumeTime'
);
}
return
params
;
}
async
function
getValues
()
{
return
buildSearchParams
(
model
.
value
);
}
async
function
handleSubmit
()
{
await
formInstance
.
value
?.
validate
();
emit
(
'submit'
,
await
getValues
());
}
async
function
handleReset
()
{
formInstance
.
value
?.
resetFields
();
emit
(
'reset'
);
}
defineExpose
({
getValues
,
resetFields
:
()
=>
formInstance
.
value
?.
resetFields
(),
});
</
script
>
<
template
>
<Card
size=
"small"
>
<Form
ref=
"formInstance"
:model=
"model"
:label-col=
"
{ style: { width: '120px' } }"
@keydown.enter.prevent="handleSubmit"
>
<div
:class=
"tableSeachClass"
>
<template
v-if=
"!searchCollapsed"
>
<FormItem
label=
"项目名称"
name=
"projectName"
>
<FormSelect
allow-clear
option-filter-prop=
"label"
show-search
v-model:value=
"model.projectName"
:options=
"getDictOptions(DictEnum.AI_PROJECT_NAME)"
/>
</FormItem>
<FormItem
label=
"姓名"
name=
"userName"
>
<FormInput
v-model:value=
"model.userName"
allow-clear
/>
</FormItem>
<FormItem
label=
"AI工具/平台"
name=
"toolName"
>
<FormSelect
allow-clear
option-filter-prop=
"label"
show-search
v-model:value=
"model.toolName"
:options=
"getDictOptions(DictEnum.AI_TOOL_NAME)"
/>
</FormItem>
<FormItem
label=
"消费时间"
name=
"consumeTime"
>
<DateRangePicker
v-model:value=
"model.consumeTime"
allow-clear
/>
</FormItem>
</
template
>
<!-- [grid-column-end:-1] 始终定位到最后一列,justify-self-end 靠右对齐 -->
<div
class=
"[grid-column-end:-1] flex items-baseline justify-end gap-4"
>
<SearchButtonGroup
collapsible
v-model:collapsed=
"searchCollapsed"
@
reset=
"handleReset"
@
submit=
"handleSubmit"
/>
</div>
</div>
</Form>
</Card>
</template>
frontend/src/views/ai/consume/data.ts
0 → 100644
View file @
b54c4d35
import
type
{
VxeGridProps
}
from
'vxe-table'
;
import
{
DictEnum
}
from
'@/constants'
;
import
{
renderDict
}
from
'@/utils/render'
;
export
const
columns
:
VxeGridProps
[
'columns'
]
=
[
{
type
:
'checkbox'
,
width
:
60
},
{
title
:
'项目名称'
,
field
:
'projectName'
,
slots
:
{
default
:
({
row
})
=>
{
return
renderDict
(
row
.
projectName
,
DictEnum
.
AI_PROJECT_NAME
);
},
},
},
{
title
:
'姓名'
,
field
:
'userName'
,
},
{
title
:
'AI工具/平台名称'
,
field
:
'toolName'
,
slots
:
{
default
:
({
row
})
=>
{
return
renderDict
(
row
.
toolName
,
DictEnum
.
AI_TOOL_NAME
);
},
},
},
{
title
:
'国内/国外'
,
field
:
'region'
,
},
{
title
:
'消耗积分'
,
field
:
'consumePoints'
,
},
{
title
:
'金额(人民币)'
,
field
:
'amountCny'
,
},
{
title
:
'消费时间'
,
field
:
'consumeTime'
,
},
{
title
:
'备注'
,
field
:
'remark'
,
},
{
field
:
'action'
,
fixed
:
'right'
,
slots
:
{
default
:
'action'
},
title
:
'操作'
,
resizable
:
false
,
width
:
'auto'
,
},
];
frontend/src/views/ai/consume/index.vue
0 → 100644
View file @
b54c4d35
<
script
setup
lang=
"ts"
>
import
type
{
Consume
}
from
'@/api/ai/consume/model'
;
import
type
{
VxeGridInstance
,
VxeGridListeners
}
from
'vxe-table'
;
import
{
ref
,
useTemplateRef
}
from
'vue'
;
import
{
consumeExport
,
consumeList
,
consumeRemove
}
from
'@/api/ai/consume'
;
import
{
Page
,
useVbenDrawer
}
from
'@/components'
;
import
{
resolveQueryFormValues
,
useTableQuery
,
withDefaultVxeGridOptions
,
}
from
'@/components/vxe-table'
;
import
{
useBlobExport
}
from
'@/utils/file/export'
;
import
{
Popconfirm
,
Space
,
Spin
}
from
'antdv-next'
;
import
{
VxeGrid
}
from
'vxe-table'
;
import
{
columns
}
from
'./data'
;
import
consumeDrawer
from
'./consume-drawer.vue'
;
import
ConsumeSearchForm
from
'./consume-search.vue'
;
const
searchFormRef
=
ref
<
InstanceType
<
typeof
ConsumeSearchForm
>>
();
const
tableLoading
=
ref
(
false
);
const
gridOptions
=
withDefaultVxeGridOptions
<
Consume
>
({
checkboxConfig
:
{
// 高亮
highlight
:
true
,
// 翻页时保留选中状态
reserve
:
true
,
trigger
:
'cell'
,
},
columns
,
height
:
'auto'
,
keepSource
:
true
,
pagerConfig
:
{},
proxyConfig
:
{
showLoading
:
false
,
ajax
:
{
query
:
async
({
page
},
formValues
)
=>
{
const
values
=
await
resolveQueryFormValues
(
searchFormRef
,
formValues
);
tableLoading
.
value
=
true
;
try
{
return
await
consumeList
({
pageNum
:
page
.
currentPage
,
pageSize
:
page
.
pageSize
,
...
values
,
});
}
finally
{
tableLoading
.
value
=
false
;
}
},
},
},
rowConfig
:
{
keyField
:
'id'
,
},
toolbarConfig
:
{
slots
:
{
buttons
:
'toolbar-left'
,
tools
:
'toolbar-right'
,
},
},
id
:
'ai-consume-index'
,
});
const
tableRef
=
useTemplateRef
<
VxeGridInstance
<
Consume
>>
(
'tableRef'
);
const
{
query
,
reload
}
=
useTableQuery
(
searchFormRef
,
tableRef
,
syncCheckedRows
,
);
const
checkedRows
=
ref
<
Consume
[]
>
([]);
const
gridEvents
:
VxeGridListeners
=
{
checkboxAll
:
syncCheckedRows
,
checkboxChange
:
syncCheckedRows
,
};
const
[
ConsumeDrawer
,
drawerApi
]
=
useVbenDrawer
({
connectedComponent
:
consumeDrawer
,
});
function
handleAdd
()
{
drawerApi
.
setData
({});
drawerApi
.
open
();
}
async
function
handleEdit
(
record
:
Consume
)
{
drawerApi
.
setData
({
id
:
record
.
id
});
drawerApi
.
open
();
}
async
function
handleDelete
(
row
:
Consume
)
{
await
consumeRemove
([
row
.
id
]);
// 取消该行选中状态,避免 reserve 记录残留
tableRef
.
value
?.
setCheckboxRow
(
row
,
false
);
await
query
();
}
function
handleMultiDelete
()
{
const
rows
=
getCheckedRows
();
const
ids
=
rows
.
map
((
row
:
Consume
)
=>
row
.
id
);
window
.
modal
.
confirm
({
title
:
'提示'
,
okType
:
'danger'
,
content
:
`确认删除选中的
${
ids
.
length
}
条记录吗?`
,
onOk
:
async
()
=>
{
await
consumeRemove
(
ids
);
// 清除所有选中状态,避免 reserve 记录残留
tableRef
.
value
?.
clearCheckboxRow
();
tableRef
.
value
?.
clearCheckboxReserve
();
await
query
();
},
});
}
const
{
exportBlob
,
exportLoading
,
buildExportFileName
}
=
useBlobExport
(
consumeExport
);
async
function
handleExport
()
{
// 构建表单请求参数
const
formValues
=
(
await
searchFormRef
.
value
?.
getValues
())
??
{};
// 文件名
const
fileName
=
buildExportFileName
(
'消费记录数据'
);
exportBlob
({
data
:
formValues
,
fileName
});
}
function
handleSearchSubmit
(
data
:
Record
<
string
,
any
>
)
{
reload
(
data
);
}
function
handleSearchReset
()
{
reload
();
}
function
getCheckedRows
()
{
const
table
=
tableRef
.
value
;
if
(
!
table
)
{
return
[];
}
return
[
...
table
.
getCheckboxRecords
(),
...
table
.
getCheckboxReserveRecords
(),
]
as
Consume
[];
}
function
syncCheckedRows
()
{
checkedRows
.
value
=
getCheckedRows
();
}
</
script
>
<
template
>
<Page
:auto-content-height=
"true"
>
<Spin
:styles=
"
{ root: { height: '100%' }, container: { height: '100%' } }"
:spinning="tableLoading"
size="large"
:delay="300"
>
<div
class=
"flex h-full flex-col gap-2"
>
<ConsumeSearchForm
ref=
"searchFormRef"
@
submit=
"handleSearchSubmit"
@
reset=
"handleSearchReset"
/>
<div
class=
"bg-card flex-1 overflow-hidden rounded-lg"
>
<VxeGrid
ref=
"tableRef"
class=
"p-2 pt-0"
v-bind=
"gridOptions"
v-on=
"gridEvents"
>
<template
#
toolbar-left
>
<div
class=
"text-[16px] font-medium"
>
消费记录
</div>
</
template
>
<
template
#
toolbar-right
>
<Space>
<a-button
v-access:code=
"['ai:consume:export']"
:loading=
"exportLoading"
:disabled=
"exportLoading"
@
click=
"handleExport"
>
{{
$t
(
'pages.common.export'
)
}}
</a-button>
<a-button
:disabled=
"checkedRows.length === 0"
danger
type=
"primary"
v-access:code=
"['ai:consume:remove']"
@
click=
"handleMultiDelete"
>
{{
$t
(
'pages.common.delete'
)
}}
</a-button>
<a-button
type=
"primary"
v-access:code=
"['ai:consume:add']"
@
click=
"handleAdd"
>
{{
$t
(
'pages.common.add'
)
}}
</a-button>
</Space>
</
template
>
<
template
#
action=
"{ row }"
>
<Space>
<action-button
v-access:code=
"['ai:consume:edit']"
@
click=
"handleEdit(row)"
>
{{
$t
(
'pages.common.edit'
)
}}
</action-button>
<Popconfirm
placement=
"left"
title=
"确认删除?"
@
confirm=
"handleDelete(row)"
>
<action-button
danger
v-access:code=
"['ai:consume:remove']"
@
click
.
stop=
""
>
{{
$t
(
'pages.common.delete'
)
}}
</action-button>
</Popconfirm>
</Space>
</
template
>
<
template
#
loading
>
<Spin
:spinning=
"true"
size=
"large"
/>
</
template
>
</VxeGrid>
</div>
</div>
</Spin>
<ConsumeDrawer
@
reload=
"() => query()"
/>
</Page>
</template>
frontend/src/views/ai/recharge/data.ts
0 → 100644
View file @
b54c4d35
import
type
{
VxeGridProps
}
from
'vxe-table'
;
import
{
DictEnum
}
from
'@/constants'
;
import
{
renderDict
}
from
'@/utils/render'
;
export
const
columns
:
VxeGridProps
[
'columns'
]
=
[
{
type
:
'checkbox'
,
width
:
60
},
{
title
:
'AI工具/平台名称'
,
field
:
'toolName'
,
slots
:
{
default
:
({
row
})
=>
{
return
renderDict
(
row
.
toolName
,
DictEnum
.
AI_TOOL_NAME
);
},
},
},
{
title
:
'国内/国外'
,
field
:
'region'
,
},
{
title
:
'充值分类'
,
field
:
'rechargeCategory'
,
slots
:
{
default
:
({
row
})
=>
{
return
renderDict
(
row
.
rechargeCategory
,
DictEnum
.
AI_RECHARGE_CATEGORY
);
},
},
},
{
title
:
'充值完成时间'
,
field
:
'rechargeTime'
,
},
{
title
:
'到期时间'
,
field
:
'expireTime'
,
},
{
title
:
'充值积分'
,
field
:
'rechargePoints'
,
},
{
title
:
'充值金额(美元)'
,
field
:
'amountUsd'
,
},
{
title
:
'充值金额(人民币)'
,
field
:
'amountCny'
,
},
{
title
:
'开通账号数量'
,
field
:
'accountCount'
,
},
{
title
:
'剩余积分'
,
field
:
'remainingPoints'
,
},
{
title
:
'备注'
,
field
:
'remark'
,
},
{
field
:
'action'
,
fixed
:
'right'
,
slots
:
{
default
:
'action'
},
title
:
'操作'
,
resizable
:
false
,
width
:
'auto'
,
},
];
frontend/src/views/ai/recharge/index.vue
0 → 100644
View file @
b54c4d35
<
script
setup
lang=
"ts"
>
import
type
{
Recharge
}
from
'@/api/ai/recharge/model'
;
import
type
{
VxeGridInstance
,
VxeGridListeners
}
from
'vxe-table'
;
import
{
ref
,
useTemplateRef
}
from
'vue'
;
import
{
rechargeExport
,
rechargeList
,
rechargeRemove
}
from
'@/api/ai/recharge'
;
import
{
Page
,
useVbenDrawer
}
from
'@/components'
;
import
{
resolveQueryFormValues
,
useTableQuery
,
withDefaultVxeGridOptions
,
}
from
'@/components/vxe-table'
;
import
{
useBlobExport
}
from
'@/utils/file/export'
;
import
{
Popconfirm
,
Space
,
Spin
}
from
'antdv-next'
;
import
{
VxeGrid
}
from
'vxe-table'
;
import
{
columns
}
from
'./data'
;
import
rechargeDrawer
from
'./recharge-drawer.vue'
;
import
RechargeSearchForm
from
'./recharge-search.vue'
;
const
searchFormRef
=
ref
<
InstanceType
<
typeof
RechargeSearchForm
>>
();
const
tableLoading
=
ref
(
false
);
const
gridOptions
=
withDefaultVxeGridOptions
<
Recharge
>
({
checkboxConfig
:
{
// 高亮
highlight
:
true
,
// 翻页时保留选中状态
reserve
:
true
,
trigger
:
'cell'
,
},
columns
,
height
:
'auto'
,
keepSource
:
true
,
pagerConfig
:
{},
proxyConfig
:
{
showLoading
:
false
,
ajax
:
{
query
:
async
({
page
},
formValues
)
=>
{
const
values
=
await
resolveQueryFormValues
(
searchFormRef
,
formValues
);
tableLoading
.
value
=
true
;
try
{
return
await
rechargeList
({
pageNum
:
page
.
currentPage
,
pageSize
:
page
.
pageSize
,
...
values
,
});
}
finally
{
tableLoading
.
value
=
false
;
}
},
},
},
rowConfig
:
{
keyField
:
'id'
,
},
toolbarConfig
:
{
slots
:
{
buttons
:
'toolbar-left'
,
tools
:
'toolbar-right'
,
},
},
id
:
'ai-recharge-index'
,
});
const
tableRef
=
useTemplateRef
<
VxeGridInstance
<
Recharge
>>
(
'tableRef'
);
const
{
query
,
reload
}
=
useTableQuery
(
searchFormRef
,
tableRef
,
syncCheckedRows
,
);
const
checkedRows
=
ref
<
Recharge
[]
>
([]);
const
gridEvents
:
VxeGridListeners
=
{
checkboxAll
:
syncCheckedRows
,
checkboxChange
:
syncCheckedRows
,
};
const
[
RechargeDrawer
,
drawerApi
]
=
useVbenDrawer
({
connectedComponent
:
rechargeDrawer
,
});
function
handleAdd
()
{
drawerApi
.
setData
({});
drawerApi
.
open
();
}
async
function
handleEdit
(
record
:
Recharge
)
{
drawerApi
.
setData
({
id
:
record
.
id
});
drawerApi
.
open
();
}
async
function
handleDelete
(
row
:
Recharge
)
{
await
rechargeRemove
([
row
.
id
]);
// 取消该行选中状态,避免 reserve 记录残留
tableRef
.
value
?.
setCheckboxRow
(
row
,
false
);
await
query
();
}
function
handleMultiDelete
()
{
const
rows
=
getCheckedRows
();
const
ids
=
rows
.
map
((
row
:
Recharge
)
=>
row
.
id
);
window
.
modal
.
confirm
({
title
:
'提示'
,
okType
:
'danger'
,
content
:
`确认删除选中的
${
ids
.
length
}
条记录吗?`
,
onOk
:
async
()
=>
{
await
rechargeRemove
(
ids
);
// 清除所有选中状态,避免 reserve 记录残留
tableRef
.
value
?.
clearCheckboxRow
();
tableRef
.
value
?.
clearCheckboxReserve
();
await
query
();
},
});
}
const
{
exportBlob
,
exportLoading
,
buildExportFileName
}
=
useBlobExport
(
rechargeExport
);
async
function
handleExport
()
{
// 构建表单请求参数
const
formValues
=
(
await
searchFormRef
.
value
?.
getValues
())
??
{};
// 文件名
const
fileName
=
buildExportFileName
(
'充值记录数据'
);
exportBlob
({
data
:
formValues
,
fileName
});
}
function
handleSearchSubmit
(
data
:
Record
<
string
,
any
>
)
{
reload
(
data
);
}
function
handleSearchReset
()
{
reload
();
}
function
getCheckedRows
()
{
const
table
=
tableRef
.
value
;
if
(
!
table
)
{
return
[];
}
return
[
...
table
.
getCheckboxRecords
(),
...
table
.
getCheckboxReserveRecords
(),
]
as
Recharge
[];
}
function
syncCheckedRows
()
{
checkedRows
.
value
=
getCheckedRows
();
}
</
script
>
<
template
>
<Page
:auto-content-height=
"true"
>
<Spin
:styles=
"
{ root: { height: '100%' }, container: { height: '100%' } }"
:spinning="tableLoading"
size="large"
:delay="300"
>
<div
class=
"flex h-full flex-col gap-2"
>
<RechargeSearchForm
ref=
"searchFormRef"
@
submit=
"handleSearchSubmit"
@
reset=
"handleSearchReset"
/>
<div
class=
"bg-card flex-1 overflow-hidden rounded-lg"
>
<VxeGrid
ref=
"tableRef"
class=
"p-2 pt-0"
v-bind=
"gridOptions"
v-on=
"gridEvents"
>
<template
#
toolbar-left
>
<div
class=
"text-[16px] font-medium"
>
充值记录
</div>
</
template
>
<
template
#
toolbar-right
>
<Space>
<a-button
v-access:code=
"['ai:recharge:export']"
:loading=
"exportLoading"
:disabled=
"exportLoading"
@
click=
"handleExport"
>
{{
$t
(
'pages.common.export'
)
}}
</a-button>
<a-button
:disabled=
"checkedRows.length === 0"
danger
type=
"primary"
v-access:code=
"['ai:recharge:remove']"
@
click=
"handleMultiDelete"
>
{{
$t
(
'pages.common.delete'
)
}}
</a-button>
<a-button
type=
"primary"
v-access:code=
"['ai:recharge:add']"
@
click=
"handleAdd"
>
{{
$t
(
'pages.common.add'
)
}}
</a-button>
</Space>
</
template
>
<
template
#
action=
"{ row }"
>
<Space>
<action-button
v-access:code=
"['ai:recharge:edit']"
@
click=
"handleEdit(row)"
>
{{
$t
(
'pages.common.edit'
)
}}
</action-button>
<Popconfirm
placement=
"left"
title=
"确认删除?"
@
confirm=
"handleDelete(row)"
>
<action-button
danger
v-access:code=
"['ai:recharge:remove']"
@
click
.
stop=
""
>
{{
$t
(
'pages.common.delete'
)
}}
</action-button>
</Popconfirm>
</Space>
</
template
>
<
template
#
loading
>
<Spin
:spinning=
"true"
size=
"large"
/>
</
template
>
</VxeGrid>
</div>
</div>
</Spin>
<RechargeDrawer
@
reload=
"() => query()"
/>
</Page>
</template>
frontend/src/views/ai/recharge/recharge-drawer.vue
0 → 100644
View file @
b54c4d35
<
script
setup
lang=
"ts"
>
import
type
{
Recharge
}
from
'@/api/ai/recharge/model'
;
import
type
{
AntdFormRules
}
from
'@/types/form'
;
import
type
{
FormInstance
}
from
'antdv-next'
;
import
{
computed
,
ref
}
from
'vue'
;
import
{
rechargeAdd
,
rechargeInfo
,
rechargeUpdate
}
from
'@/api/ai/recharge'
;
import
{
useVbenDrawer
}
from
'@/components'
;
import
{
FormInputNumber
as
InputNumber
,
FormSelect
as
Select
,
FormTextArea
as
TextArea
,
}
from
'@/components/global/form'
;
import
{
DictEnum
}
from
'@/constants'
;
import
{
$t
}
from
'@/locales'
;
import
{
cloneDeep
,
getPopupContainer
}
from
'@/utils'
;
import
{
getDictOptions
}
from
'@/utils/dict'
;
import
{
useBeforeCloseDiff
}
from
'@/utils/popup'
;
import
{
DatePicker
,
Form
,
FormItem
}
from
'antdv-next'
;
const
emit
=
defineEmits
<
{
reload
:
[]
}
>
();
const
isUpdate
=
ref
(
false
);
const
title
=
computed
(()
=>
{
return
isUpdate
.
value
?
$t
(
'pages.common.edit'
)
:
$t
(
'pages.common.add'
);
});
type
FormData
=
Partial
<
Recharge
>
;
// 国内/国外为固定二值(非字典,Clarify C2)
const
regionOptions
=
[
{
label
:
'国内'
,
value
:
'国内'
},
{
label
:
'国外'
,
value
:
'国外'
},
];
function
getDefaultValues
():
FormData
{
return
{
id
:
undefined
,
toolName
:
undefined
,
region
:
undefined
,
rechargeCategory
:
undefined
,
rechargeTime
:
undefined
,
expireTime
:
undefined
,
rechargePoints
:
undefined
,
amountUsd
:
undefined
,
amountCny
:
undefined
,
accountCount
:
undefined
,
remainingPoints
:
undefined
,
remark
:
''
,
};
}
const
formData
=
ref
<
FormData
>
(
getDefaultValues
());
const
formInstance
=
ref
<
FormInstance
>
();
const
formRules
=
ref
<
AntdFormRules
<
FormData
>>
({
toolName
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.selectRequired'
)
}],
region
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.selectRequired'
)
}],
rechargeCategory
:
[
{
required
:
true
,
message
:
$t
(
'ui.formRules.selectRequired'
)
},
],
rechargeTime
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.required'
)
}],
amountCny
:
[{
required
:
true
,
message
:
$t
(
'ui.formRules.required'
)
}],
});
function
customFormValueGetter
()
{
return
JSON
.
stringify
(
formData
.
value
);
}
const
{
onBeforeClose
,
markInitialized
,
resetInitialized
}
=
useBeforeCloseDiff
(
{
initializedGetter
:
customFormValueGetter
,
currentGetter
:
customFormValueGetter
,
},
);
const
[
BasicDrawer
,
drawerApi
]
=
useVbenDrawer
({
onBeforeClose
,
onClosed
:
handleClosed
,
onConfirm
:
handleConfirm
,
async
onOpenChange
(
isOpen
)
{
if
(
!
isOpen
)
{
return
null
;
}
drawerApi
.
drawerLoading
(
true
);
const
{
id
}
=
drawerApi
.
getData
()
as
{
id
?:
number
|
string
};
isUpdate
.
value
=
!!
id
;
// 更新 && 赋值
if
(
isUpdate
.
value
&&
id
)
{
const
record
=
await
rechargeInfo
(
id
);
formData
.
value
=
{
...
getDefaultValues
(),
...
record
,
remark
:
record
.
remark
??
''
,
};
}
await
markInitialized
();
drawerApi
.
drawerLoading
(
false
);
},
});
async
function
handleConfirm
()
{
try
{
drawerApi
.
lock
(
true
);
await
formInstance
.
value
?.
validate
();
const
data
=
cloneDeep
(
formData
.
value
);
await
(
isUpdate
.
value
?
rechargeUpdate
(
data
)
:
rechargeAdd
(
data
));
resetInitialized
();
emit
(
'reload'
);
drawerApi
.
close
();
}
catch
(
error
)
{
console
.
error
(
error
);
}
finally
{
drawerApi
.
lock
(
false
);
}
}
async
function
handleClosed
()
{
formData
.
value
=
getDefaultValues
();
formInstance
.
value
?.
resetFields
();
resetInitialized
();
}
</
script
>
<
template
>
<BasicDrawer
:title=
"title"
:size=
"700"
>
<Form
ref=
"formInstance"
:model=
"formData"
:label-col=
"
{ style: { width: '140px' } }"
>
<FormItem
label=
"AI工具/平台名称"
name=
"toolName"
:rules=
"formRules.toolName"
>
<Select
class=
"w-full"
:allow-clear=
"false"
option-filter-prop=
"label"
show-search
:get-popup-container=
"getPopupContainer"
:options=
"getDictOptions(DictEnum.AI_TOOL_NAME)"
v-model:value=
"formData.toolName"
/>
</FormItem>
<FormItem
label=
"国内/国外"
name=
"region"
:rules=
"formRules.region"
>
<Select
class=
"w-full"
:allow-clear=
"false"
:get-popup-container=
"getPopupContainer"
:options=
"regionOptions"
v-model:value=
"formData.region"
/>
</FormItem>
<FormItem
label=
"充值分类"
name=
"rechargeCategory"
:rules=
"formRules.rechargeCategory"
>
<Select
class=
"w-full"
:allow-clear=
"false"
:get-popup-container=
"getPopupContainer"
:options=
"getDictOptions(DictEnum.AI_RECHARGE_CATEGORY)"
v-model:value=
"formData.rechargeCategory"
/>
</FormItem>
<FormItem
label=
"充值完成时间"
name=
"rechargeTime"
:rules=
"formRules.rechargeTime"
>
<DatePicker
class=
"w-full"
show-time
format=
"YYYY-MM-DD HH:mm:ss"
value-format=
"YYYY-MM-DD HH:mm:ss"
v-model:value=
"formData.rechargeTime"
/>
</FormItem>
<FormItem
label=
"到期时间"
name=
"expireTime"
>
<DatePicker
class=
"w-full"
format=
"YYYY-MM-DD"
value-format=
"YYYY-MM-DD"
v-model:value=
"formData.expireTime"
/>
</FormItem>
<FormItem
label=
"充值积分"
name=
"rechargePoints"
>
<InputNumber
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
:precision="2"
v-model:value="formData.rechargePoints"
/>
</FormItem>
<FormItem
label=
"充值金额(美元)"
name=
"amountUsd"
>
<InputNumber
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
:precision="2"
v-model:value="formData.amountUsd"
/>
</FormItem>
<FormItem
label=
"充值金额(人民币)"
name=
"amountCny"
:rules=
"formRules.amountCny"
>
<InputNumber
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
:precision="2"
v-model:value="formData.amountCny"
/>
</FormItem>
<FormItem
label=
"开通账号数量"
name=
"accountCount"
>
<InputNumber
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
:precision="0"
v-model:value="formData.accountCount"
/>
</FormItem>
<FormItem
label=
"剩余积分"
name=
"remainingPoints"
>
<InputNumber
class=
"w-full"
:style=
"
{ width: '50%' }"
:min="0"
:precision="2"
v-model:value="formData.remainingPoints"
/>
</FormItem>
<FormItem
label=
"备注"
name=
"remark"
>
<TextArea
allow-clear
class=
"w-full"
:rows=
"4"
:maxlength=
"200"
show-count
v-model:value=
"formData.remark"
/>
</FormItem>
</Form>
</BasicDrawer>
</
template
>
frontend/src/views/ai/recharge/recharge-search.vue
0 → 100644
View file @
b54c4d35
<
script
setup
lang=
"ts"
>
import
type
{
FormInstance
}
from
'antdv-next'
;
import
type
{
Dayjs
}
from
'dayjs'
;
import
{
ref
}
from
'vue'
;
import
{
FormSelect
}
from
'@/components/global/form'
;
import
{
SearchButtonGroup
}
from
'@/components/table'
;
import
{
tableSeachClass
}
from
'@/components/vxe-table'
;
import
{
DictEnum
}
from
'@/constants'
;
import
{
formatDateTime
}
from
'@/utils'
;
import
{
getDictOptions
}
from
'@/utils/dict'
;
import
{
Card
,
DateRangePicker
,
Form
,
FormItem
}
from
'antdv-next'
;
const
emit
=
defineEmits
<
{
reset
:
[];
submit
:
[
record
:
Record
<
string
,
any
>
];
}
>
();
interface
RechargeSearchFormParams
{
rechargeTime
?:
[
Dayjs
,
Dayjs
];
toolName
?:
string
;
region
?:
string
;
rechargeCategory
?:
string
;
}
// 国内/国外为固定二值(非字典,Clarify C2)
const
regionOptions
=
[
{
label
:
'国内'
,
value
:
'国内'
},
{
label
:
'国外'
,
value
:
'国外'
},
];
const
model
=
ref
<
RechargeSearchFormParams
>
({
rechargeTime
:
undefined
,
toolName
:
undefined
,
region
:
undefined
,
rechargeCategory
:
undefined
,
});
const
formInstance
=
ref
<
FormInstance
>
();
const
searchCollapsed
=
ref
(
false
);
function
buildSearchParams
(
values
:
RechargeSearchFormParams
)
{
const
params
:
Record
<
string
,
any
>
=
{
...
values
};
if
(
params
.
rechargeTime
)
{
params
.
params
=
{
beginTime
:
formatDateTime
(
params
.
rechargeTime
[
0
]),
endTime
:
formatDateTime
(
params
.
rechargeTime
[
1
]),
};
Reflect
.
deleteProperty
(
params
,
'rechargeTime'
);
}
return
params
;
}
async
function
getValues
()
{
return
buildSearchParams
(
model
.
value
);
}
async
function
handleSubmit
()
{
await
formInstance
.
value
?.
validate
();
emit
(
'submit'
,
await
getValues
());
}
async
function
handleReset
()
{
formInstance
.
value
?.
resetFields
();
emit
(
'reset'
);
}
defineExpose
({
getValues
,
resetFields
:
()
=>
formInstance
.
value
?.
resetFields
(),
});
</
script
>
<
template
>
<Card
size=
"small"
>
<Form
ref=
"formInstance"
:model=
"model"
:label-col=
"
{ style: { width: '120px' } }"
@keydown.enter.prevent="handleSubmit"
>
<div
:class=
"tableSeachClass"
>
<template
v-if=
"!searchCollapsed"
>
<FormItem
label=
"充值时间"
name=
"rechargeTime"
>
<DateRangePicker
v-model:value=
"model.rechargeTime"
allow-clear
/>
</FormItem>
<FormItem
label=
"AI工具/平台"
name=
"toolName"
>
<FormSelect
allow-clear
option-filter-prop=
"label"
show-search
v-model:value=
"model.toolName"
:options=
"getDictOptions(DictEnum.AI_TOOL_NAME)"
/>
</FormItem>
<FormItem
label=
"国内/国外"
name=
"region"
>
<FormSelect
allow-clear
v-model:value=
"model.region"
:options=
"regionOptions"
/>
</FormItem>
<FormItem
label=
"充值分类"
name=
"rechargeCategory"
>
<FormSelect
allow-clear
v-model:value=
"model.rechargeCategory"
:options=
"getDictOptions(DictEnum.AI_RECHARGE_CATEGORY)"
/>
</FormItem>
</
template
>
<!-- [grid-column-end:-1] 始终定位到最后一列,justify-self-end 靠右对齐 -->
<div
class=
"[grid-column-end:-1] flex items-baseline justify-end gap-4"
>
<SearchButtonGroup
collapsible
v-model:collapsed=
"searchCollapsed"
@
reset=
"handleReset"
@
submit=
"handleSubmit"
/>
</div>
</div>
</Form>
</Card>
</template>
frontend/vite.config.ts
View file @
b54c4d35
...
@@ -37,7 +37,7 @@ export default defineConfig(async () => {
...
@@ -37,7 +37,7 @@ export default defineConfig(async () => {
changeOrigin
:
true
,
changeOrigin
:
true
,
// rewrite: (path) => path.replace(/^\/api/, ''),
// rewrite: (path) => path.replace(/^\/api/, ''),
// mock代理目标地址
// mock代理目标地址
target
:
'http://127.0.0.1:
900
0'
,
target
:
'http://127.0.0.1:
809
0'
,
ws
:
true
,
ws
:
true
,
},
},
},
},
...
...
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