Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yd-csf-front
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
yuzhenWang
yd-csf-front
Commits
8fd3df19
Commit
8fd3df19
authored
Dec 26, 2025
by
Sweet Zhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
对接接口,封装搜索组件
parent
f97f6052
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
650 additions
and
588 deletions
+650
-588
src/api/financial/commission.js
+10
-0
src/components/SearchForm/SearchForm.vue
+230
-154
src/utils/dict.js
+2
-22
src/utils/request.js
+2
-1
src/utils/safeDownload.js
+57
-0
src/utils/useDict.js
+62
-0
src/views/financialCenter/financialSalary.vue
+64
-79
src/views/financialCenter/payables.vue
+36
-35
src/views/financialCenter/receivables.vue
+176
-297
vite.config.js
+11
-0
No files found.
src/api/financial/commission.js
View file @
8fd3df19
...
...
@@ -290,3 +290,13 @@ export function policyNoCommissionPayRecord(data) {
data
:
data
})
}
// 应收款导出
export
function
exportReceivedFortune
(
data
)
{
return
request
({
url
:
'/csf/api/CommissionExpected/export'
,
method
:
'post'
,
data
:
data
,
responseType
:
'blob'
})
}
src/components/SearchForm/SearchForm.vue
View file @
8fd3df19
<!-- components/SearchForm.vue -->
<
template
>
<el-form
:model=
"formModel"
label-width=
"100px"
size=
"default"
label-position=
"top"
>
<el-row
:gutter=
"20"
>
<template
v-for=
"item in config"
:key=
"item.prop"
>
<!-- 所有字段都用相同的响应式布局 -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-if=
"item.type === 'input'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-input
v-model=
"formModel[item.prop]"
:placeholder=
"item.placeholder || `请输入$
{item.label}`"
clearable
/>
</el-form-item>
</el-col>
<el-form
:model=
"formModel"
label-width=
"100px"
size=
"default"
label-position=
"top"
>
<el-row
:gutter=
"20"
>
<template
v-for=
"item in config"
:key=
"item.prop"
>
<!-- Input -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-if=
"item.type === 'input'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
:rules=
"item.rules"
>
<el-input
:model-value=
"formModel[item.prop]"
@
input=
"(val) => handleNumberInput(val, item)"
:placeholder=
"item.placeholder || `请输入$
{item.label}`" clearable style="width: 100%" />
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'select' && item.api"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-select
v-model=
"formModel[item.prop]"
:placeholder=
"item.placeholder || `请选择$
{item.label}`"
clearable
filterable
:filter-method="getFilterMethod(item)"
:loading="item.loading"
@focus="handleFocus(item)"
>
<el-option
v-for=
"opt in item.options"
:key=
"opt[item.valueKey || 'value']"
:label=
"opt[item.labelKey || 'label']"
:value=
"opt[item.valueKey || 'value']"
/>
</el-select>
</el-form-item>
</el-col>
<!-- Remote Select (带 api 的 select) -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'select' && item.api"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
:rules=
"item.rules"
>
<el-select
ref=
"selectRefs[item.prop]"
v-model=
"formModel[item.prop]"
:placeholder=
"item.placeholder || `请选择$
{item.label}`" clearable filterable
:multiple="item.multiple" :loading="item.loading"
:no-match-text="item.noMatchText || '无匹配数据'" @filter="handleSelectFilter(item)"
@visible-change="handleVisibleChange(item)" @change="(val) => handleChange(item, val)"
style="width: 100%">
<el-option
v-for=
"opt in item._allOptions"
:key=
"opt[item.valueKey || 'value']"
:label=
"opt[item.labelKey || 'label']"
:value=
"opt[item.valueKey || 'value']"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'select'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-select
v-model=
"formModel[item.prop]"
:placeholder=
"item.placeholder || `请选择$
{item.label}`"
clearable
:filterable="item.filterable !== false"
:multiple="item.multiple"
>
<el-option
v-for=
"opt in item.options"
:key=
"opt[item.valueKey || 'value']"
:label=
"opt[item.labelKey || 'label']"
:value=
"opt[item.valueKey || 'value']"
/>
</el-select>
</el-form-item>
</el-col>
<!-- Static Select / Dict Select -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'select'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
:rules=
"item.rules"
>
<el-select
v-model=
"formModel[item.prop]"
:placeholder=
"item.placeholder || `请选择$
{item.label}`"
clearable :filterable="item.filterable !== false" :multiple="item.multiple"
:no-match-text="item.noMatchText || '无匹配数据'">
<el-option
v-for=
"opt in item.options"
:key=
"opt[item.valueKey || 'value']"
:label=
"opt[item.labelKey || 'label']"
:value=
"opt[item.valueKey || 'value']"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'checkbox-group'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-checkbox-group
v-model=
"formModel[item.prop]"
>
<el-checkbox
v-for=
"opt in item.options"
:key=
"opt[item.valueKey || 'value']"
:label=
"opt[item.valueKey || 'value']"
>
{{
opt
[
item
.
labelKey
||
'label'
]
}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<!-- Checkbox Group -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'checkbox-group'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-checkbox-group
v-model=
"formModel[item.prop]"
>
<el-checkbox
v-for=
"opt in item.options"
:key=
"opt[item.valueKey || 'value']"
:label=
"opt[item.valueKey || 'value']"
>
{{
opt
[
item
.
labelKey
||
'label'
]
}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'date'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-date-picker
v-model=
"formModel[item.prop]"
type=
"date"
:placeholder=
"item.placeholder || `请选择$
{item.label}`"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
clearable
/>
</el-form-item>
</el-col>
<!-- Date -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'date'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-date-picker
v-model=
"formModel[item.prop]"
type=
"date"
:placeholder=
"item.placeholder || `请选择$
{item.label}`" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" style="width: 100%" clearable />
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'daterange'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-date-picker
v-model=
"formModel[item.prop]"
type=
"daterange"
:range-separator=
"item.rangeSeparator || '至'"
:start-placeholder=
"item.startPlaceholder || '开始日期'"
:end-placeholder=
"item.endPlaceholder || '结束日期'"
format=
"YYYY-MM-DD"
value-format=
"YYYY-MM-DD"
clearable
/>
</el-form-item>
</el-col>
</
template
>
</el-row>
</el-form>
<!-- Date Range -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"6"
:lg=
"6"
v-else-if=
"item.type === 'daterange'"
>
<el-form-item
:label=
"item.label"
:prop=
"item.prop"
>
<el-date-picker
v-model=
"formModel[item.prop]"
type=
"daterange"
:range-separator=
"item.rangeSeparator || '至'"
:start-placeholder=
"item.startPlaceholder || '开始日期'"
:end-placeholder=
"item.endPlaceholder || '结束日期'"
format=
"YYYY-MM-DD"
value-format=
"YYYY-MM-DD"
clearable
/>
</el-form-item>
</el-col>
</
template
>
</el-row>
</el-form>
</template>
<
script
setup
>
import
{
reactive
,
onMounted
}
from
'vue'
import
{
reactive
,
onMounted
,
onUnmounted
}
from
'vue'
import
request
from
'@/utils/request'
import
{
loadDicts
,
getDictOptions
}
from
'@/utils/useDict'
function
debounce
(
func
,
wait
)
{
let
timeout
return
function
(...
args
)
{
clearTimeout
(
timeout
)
timeout
=
setTimeout
(()
=>
func
.
apply
(
this
,
args
),
wait
)
}
}
const
selectRefs
=
reactive
({})
// 新增 handleChange
function
handleChange
(
item
,
value
)
{
// 如果是多选,value 是数组;单选是值
// 这里不需要处理值,因为 v-model 已绑定
// 清空 filter 输入框
nextTick
(()
=>
{
const
ref
=
selectRefs
[
item
.
prop
]
if
(
ref
&&
typeof
ref
.
inputValue
===
'string'
)
{
ref
.
inputValue
=
''
}
})
}
// ========================
// 工具函数
// ========================
const
formModel
=
reactive
({})
const
props
=
defineProps
({
config
:
{
type
:
Array
,
required
:
true
}
})
const
formModel
=
reactive
({})
// 防抖定时器 Map
const
debounceMap
=
new
Map
()
// 搜索缓存 Map: key = `${prop}:${keyword}`, value = option list
const
searchCache
=
new
Map
()
// ========================
// 数字输入处理
// ========================
function
handleNumberInput
(
value
,
item
)
{
const
{
inputType
=
'text'
,
decimalDigits
=
2
}
=
item
if
(
inputType
===
'text'
)
{
formModel
[
item
.
prop
]
=
value
return
}
let
result
=
String
(
value
)
if
(
inputType
===
'integer'
)
{
result
=
result
.
replace
(
/
[^\d]
/g
,
''
)
}
else
if
(
inputType
===
'decimal'
)
{
result
=
result
.
replace
(
/
[^\d
.
]
/g
,
''
).
replace
(
/^
\.
/
,
''
)
const
parts
=
result
.
split
(
'.'
)
if
(
parts
.
length
>
2
)
result
=
parts
[
0
]
+
'.'
+
parts
.
slice
(
1
).
join
(
''
)
}
else
if
(
inputType
===
'signed-decimal'
)
{
result
=
result
.
replace
(
/
[^\d
.-
]
/g
,
''
)
if
(
result
.
startsWith
(
'--'
))
result
=
'-'
+
result
.
slice
(
2
)
if
(
result
.
includes
(
'-'
)
&&
!
result
.
startsWith
(
'-'
))
result
=
result
.
replace
(
/-/g
,
''
)
result
=
result
.
replace
(
/^
\.
/
,
''
)
const
parts
=
result
.
split
(
'.'
)
if
(
parts
.
length
>
2
)
result
=
parts
[
0
]
+
'.'
+
parts
.
slice
(
1
).
join
(
''
)
}
if
((
inputType
===
'decimal'
||
inputType
===
'signed-decimal'
)
&&
decimalDigits
>=
0
)
{
const
dotIndex
=
result
.
indexOf
(
'.'
)
if
(
dotIndex
!==
-
1
)
{
const
integerPart
=
result
.
slice
(
0
,
dotIndex
)
let
decimalPart
=
result
.
slice
(
dotIndex
+
1
).
slice
(
0
,
decimalDigits
)
result
=
integerPart
+
(
decimalPart
?
'.'
+
decimalPart
:
''
)
}
}
formModel
[
item
.
prop
]
=
result
}
// ========================
//
公共请求方法
//
远程选项请求
// ========================
async
function
fetchOptions
(
item
,
keyword
=
''
)
{
const
payload
=
{
...
...
@@ -130,7 +154,6 @@ async function fetchOptions(item, keyword = '') {
pageNo
:
item
.
pageNo
||
1
,
pageSize
:
item
.
pageSize
||
20
}
if
(
keyword
)
{
const
keyField
=
item
.
keywordField
||
'keyword'
payload
[
keyField
]
=
keyword
.
trim
()
...
...
@@ -154,39 +177,80 @@ async function fetchOptions(item, keyword = '') {
}
else
if
(
res
?.
data
?.
data
&&
Array
.
isArray
(
res
.
data
.
data
))
{
list
=
res
.
data
.
data
}
return
list
}
// ========================
//
创建搜索函数(带防抖)
//
远程搜索逻辑
// ========================
function
createSearchFn
(
item
)
{
const
search
=
async
(
keyword
)
=>
{
item
.
loading
=
true
try
{
if
(
!
keyword
)
{
// 关键词清空 → 恢复默认选项(从缓存)
item
.
options
=
item
.
_defaultOptions
?
[...
item
.
_defaultOptions
]
:
[]
}
else
{
// 有关键词 → 远程搜索
const
list
=
await
fetchOptions
(
item
,
keyword
)
item
.
options
=
list
}
}
catch
(
e
)
{
console
.
error
(
`
${
item
.
label
}
搜索失败`
,
e
)
item
.
options
=
[]
}
finally
{
item
.
loading
=
false
function
handleSelectFilter
(
item
,
query
)
{
// 清除旧防抖
if
(
debounceMap
.
has
(
item
.
prop
))
{
clearTimeout
(
debounceMap
.
get
(
item
.
prop
))
}
const
timer
=
setTimeout
(()
=>
{
doRemoteSearch
(
item
,
query
)
},
item
.
debounceWait
??
300
)
debounceMap
.
set
(
item
.
prop
,
timer
)
}
async
function
doRemoteSearch
(
item
,
keyword
)
{
const
cacheKey
=
`
${
item
.
prop
}
:
${
keyword
}
`
if
(
searchCache
.
has
(
cacheKey
))
{
item
.
_allOptions
=
searchCache
.
get
(
cacheKey
)
return
}
item
.
loading
=
true
try
{
let
list
=
[]
if
(
!
keyword
.
trim
())
{
list
=
item
.
_fullOptions
||
[]
}
else
{
list
=
await
fetchOptions
(
item
,
keyword
)
}
item
.
_allOptions
=
list
searchCache
.
set
(
cacheKey
,
list
)
}
catch
(
e
)
{
console
.
error
(
`
${
item
.
label
}
搜索失败`
,
e
)
item
.
_allOptions
=
item
.
_fullOptions
||
[]
}
finally
{
item
.
loading
=
false
}
}
// ========================
// 下拉展开时懒加载全量
// ========================
function
handleVisibleChange
(
item
,
visible
)
{
if
(
visible
&&
!
item
.
_hasLoadedFull
)
{
item
.
loading
=
true
fetchOptions
(
item
,
''
)
.
then
(
list
=>
{
item
.
_allOptions
=
list
item
.
_fullOptions
=
list
item
.
_hasLoadedFull
=
true
searchCache
.
set
(
`
${
item
.
prop
}
:`
,
list
)
// 缓存空关键词
})
.
catch
(
e
=>
{
console
.
error
(
'加载默认选项失败'
,
e
)
item
.
_allOptions
=
[]
})
.
finally
(()
=>
{
item
.
loading
=
false
})
}
return
debounce
(
search
,
item
.
debounceWait
??
300
)
}
// ========================
// 初始化
// ========================
onMounted
(
async
()
=>
{
const
dictTypes
=
[]
const
dictItems
=
[]
for
(
const
item
of
props
.
config
)
{
// 初始化表单值
if
([
'checkbox-group'
,
'daterange'
].
includes
(
item
.
type
))
{
...
...
@@ -194,49 +258,61 @@ onMounted(async () => {
}
else
{
formModel
[
item
.
prop
]
=
item
.
defaultValue
??
''
}
// 处理带 api 的 select(hybrid 模式)
if
(
item
.
type
===
'select'
&&
item
.
api
)
{
item
.
options
=
[]
item
.
_allOptions
=
[]
item
.
_fullOptions
=
[]
item
.
loading
=
true
try
{
// 1. 加载默认数据(不传 keyword)
const
defaultList
=
await
fetchOptions
(
item
,
''
)
item
.
_defaultOptions
=
[...
defaultList
]
// 缓存
item
.
options
=
defaultList
const
list
=
await
fetchOptions
(
item
,
''
)
// 无关键词,加载默认列表
item
.
_allOptions
=
list
item
.
_fullOptions
=
list
item
.
_hasLoadedFull
=
true
searchCache
.
set
(
`
${
item
.
prop
}
:`
,
list
)
// 缓存空关键词结果
}
catch
(
e
)
{
console
.
error
(
`加载
${
item
.
label
}
默认选项失败`
,
e
)
item
.
o
ptions
=
[]
console
.
error
(
`
预
加载
${
item
.
label
}
默认选项失败`
,
e
)
item
.
_allO
ptions
=
[]
}
finally
{
item
.
loading
=
false
}
}
// 处理数字输入
if
(
item
.
type
===
'input'
&&
item
.
inputType
&&
item
.
inputType
!==
'text'
)
{
handleNumberInput
(
formModel
[
item
.
prop
],
item
)
}
// 收集字典项
if
(
item
.
type
===
'select'
&&
item
.
dictType
)
{
dictTypes
.
push
(
item
.
dictType
)
dictItems
.
push
(
item
)
}
}
// 加载字典
if
(
dictTypes
.
length
>
0
)
{
await
loadDicts
(
dictTypes
)
for
(
const
item
of
dictItems
)
{
item
.
options
=
getDictOptions
(
item
.
dictType
)
}
}
})
// ========================
//
提供给 el-select 的 filter-method
//
清理
// ========================
function
getFilterMethod
(
item
)
{
if
(
!
item
.
_searchFn
)
{
item
.
_searchFn
=
createSearchFn
(
item
)
}
return
(
keyword
)
=>
{
item
.
_searchFn
(
keyword
)
return
true
// 必须返回 true,否则下拉框会关闭
}
}
// 可选:聚焦时确保有数据(一般不需要,因为 onMounted 已加载)
function
handleFocus
(
item
)
{
// 如果意外为空,可重试
if
(
item
.
type
===
'select'
&&
item
.
api
&&
(
!
item
.
options
||
item
.
options
.
length
===
0
))
{
// 可加 loading 重试逻辑(略)
}
}
onUnmounted
(()
=>
{
debounceMap
.
forEach
(
timer
=>
clearTimeout
(
timer
))
debounceMap
.
clear
()
searchCache
.
clear
()
})
// ========================
// 暴露方法
// ========================
defineExpose
({
async
validate
()
{
// 如果你有 el-form ref,可加校验;否则直接返回
return
formModel
},
getSearchParams
()
{
const
params
=
{}
for
(
const
key
in
formModel
)
{
...
...
src/utils/dict.js
View file @
8fd3df19
...
...
@@ -51,25 +51,4 @@ export function useDictLists(typeLists) {
}
})
})()
}
// /**
// * 获取字典数据
// */
// export function useDict(...args) {
// const res = ref({})
// return (() => {
// args.forEach((dictType, index) => {
// res.value[dictType] = []
// const dicts = useDictStore().getDict(dictType)
// if (dicts) {
// res.value[dictType] = dicts
// } else {
// getDicts(dictType).then(resp => {
// res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
// useDictStore().setDict(dictType, res.value[dictType])
// })
// }
// })
// return toRefs(res.value)
// })()
// }
}
\ No newline at end of file
src/utils/request.js
View file @
8fd3df19
...
...
@@ -149,6 +149,7 @@ service.interceptors.response.use(
// 通用下载方法
export
function
download
(
url
,
params
,
filename
,
config
)
{
console
.
log
(
url
,
params
,
filename
,
config
)
downloadLoadingInstance
=
ElLoading
.
service
({
text
:
'正在下载数据,请稍候'
,
background
:
'rgba(0, 0, 0, 0.7)'
...
...
@@ -160,7 +161,7 @@ export function download(url, params, filename, config) {
return
tansParams
(
params
)
}
],
headers
:
{
'Content-Type'
:
'application/
x-www-form-urlencoded
'
},
headers
:
{
'Content-Type'
:
'application/
json
'
},
responseType
:
'blob'
,
...
config
})
...
...
src/utils/safeDownload.js
0 → 100644
View file @
8fd3df19
// utils/safeDownload.js
import
{
ElMessage
}
from
'element-plus'
/**
* 安全下载函数:适用于后端返回 Blob 文件流 或 JSON 错误 的场景
* @param {Blob} blobData - 接口返回的响应数据(必须是 responseType: 'blob')
* @param {string} defaultFilename - 默认文件名(如 'data.xlsx')
* @param {string} [mimeType] - MIME 类型(用于兜底创建 Blob)
*/
export
async
function
safeDownload
(
blobData
,
defaultFilename
,
mimeType
=
'application/octet-stream'
)
{
if
(
!
(
blobData
instanceof
Blob
))
{
ElMessage
.
error
(
'无效的下载数据'
)
return
}
try
{
// 👇 关键:先 peek 前 100 字节,判断是否是 JSON 错误
const
firstChunk
=
await
blobData
.
slice
(
0
,
100
).
text
()
const
trimmed
=
firstChunk
.
trim
()
// 如果看起来像 JSON(以 { 或 [ 开头),尝试解析
if
(
trimmed
.
startsWith
(
'{'
)
||
trimmed
.
startsWith
(
'['
))
{
const
fullText
=
await
blobData
.
text
()
let
parsed
try
{
parsed
=
JSON
.
parse
(
fullText
)
}
catch
(
e
)
{
// 解析失败,当作正常文件(比如内容就是纯文本)
parsed
=
null
}
// 如果解析成功,且包含错误字段(根据你后端约定)
if
(
parsed
&&
(
parsed
.
code
!==
undefined
||
parsed
.
msg
||
parsed
.
message
))
{
const
errorMsg
=
parsed
.
msg
||
parsed
.
message
||
'导出失败'
ElMessage
.
error
(
errorMsg
)
return
}
}
// ✅ 是合法文件流,执行下载(使用你已验证的逻辑)
const
url
=
window
.
URL
.
createObjectURL
(
blobData
)
const
link
=
document
.
createElement
(
'a'
)
link
.
href
=
url
link
.
download
=
defaultFilename
document
.
body
.
appendChild
(
link
)
link
.
click
()
document
.
body
.
removeChild
(
link
)
window
.
URL
.
revokeObjectURL
(
url
)
ElMessage
.
success
(
'下载成功'
)
}
catch
(
error
)
{
console
.
error
(
'safeDownload error:'
,
error
)
ElMessage
.
error
(
'下载过程中发生错误'
)
}
}
\ No newline at end of file
src/utils/useDict.js
0 → 100644
View file @
8fd3df19
// dictUtils.js
import
request
from
'@/utils/request'
// 全局缓存:key 为 dictType,value 为 { label: value } 映射对象
const
dictCache
=
new
Map
()
// 批量加载字典(支持多个 type)
export
async
function
loadDicts
(
typeList
)
{
// 过滤已缓存的类型,避免重复请求
const
needLoadTypes
=
typeList
.
filter
(
type
=>
!
dictCache
.
has
(
type
))
if
(
needLoadTypes
.
length
===
0
)
return
try
{
const
res
=
await
request
({
url
:
'/user/api/sysDict/type/list'
,
// 替换为你的实际接口地址
method
:
'POST'
,
data
:
{
typeList
:
needLoadTypes
}
})
// 替换 loadDicts 中的缓存部分
if
(
res
.
code
===
200
&&
Array
.
isArray
(
res
.
data
))
{
for
(
const
dict
of
res
.
data
)
{
const
{
dictType
,
dictItemList
=
[]
}
=
dict
if
(
!
dictType
)
continue
// 缓存完整列表(并可选排序 + 过滤)
const
validItems
=
dictItemList
.
filter
(
item
=>
item
.
status
===
1
)
// 只取启用的
.
sort
((
a
,
b
)
=>
a
.
orderNum
-
b
.
orderNum
)
// 按序号排序
dictCache
.
set
(
dictType
,
validItems
)
// 👈 缓存完整 item 列表
}
}
}
catch
(
error
)
{
console
.
error
(
'字典加载失败:'
,
error
)
throw
error
}
}
// 可选:提供清除缓存方法(用于权限切换等场景)
export
function
clearDictCache
()
{
dictCache
.
clear
()
}
export
function
getDictOptions
(
dictType
)
{
const
items
=
dictCache
.
get
(
dictType
)
||
[]
// console.log('getDictOptions',items)
return
items
.
map
(
item
=>
({
value
:
item
.
itemValue
,
label
:
item
.
itemLabel
// 如果需要,还可以加其他字段:disabled, key 等
}))
}
// 同时更新 getDictLabel
export
function
getDictLabel
(
dictType
,
value
)
{
const
items
=
dictCache
.
get
(
dictType
)
||
[]
const
item
=
items
.
find
(
i
=>
i
.
itemValue
===
value
)
return
item
?
item
.
itemLabel
:
value
}
\ No newline at end of file
src/views/financialCenter/financialSalary.vue
View file @
8fd3df19
...
...
@@ -5,33 +5,7 @@
:page-size=
'pageSize'
@
size-change=
'handleSizeChange'
@
current-change=
'handleCurrentChange'
>
<!-- 搜索区域 -->
<template
#
searchForm
>
<el-form
:model=
"queryParams"
label-width=
"80px"
>
<el-row
:gutter=
"20"
>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"转介人"
label-position=
"top"
>
<el-select
v-model=
"queryParams.broker"
placeholder=
"请选择转介人"
clearable
:remote-method=
"loadBrokers"
:loading=
"searchLoading"
filterable
remote
reserve-keyword
>
<el-option
v-for=
"item in brokerOptions"
:key=
"item.userSaleBizId"
:label=
"item.realName + '(' + item.phone + ')'"
:value=
"item.realName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"出账日期"
label-position=
"top"
>
<el-date-picker
v-model=
"queryParams.accountDate"
type=
"daterange"
range-separator=
"-"
start-placeholder=
"开始日期"
end-placeholder=
"结束日期"
value-format=
"YYYY-MM-DD"
/>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"出账状态"
label-position=
"top"
>
<el-select
v-model=
"queryParams.status"
placeholder=
"请选择状态"
clearable
>
<el-option
v-for=
"item in dictLists"
:key=
"item.itemValue"
:label=
"item.itemLabel"
:value=
"item.itemValue"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<SearchForm
ref=
"searchFormRef"
:config=
"searchConfig"
/>
</
template
>
<!-- 列表区域 -->
<
template
#
table
>
...
...
@@ -47,7 +21,7 @@
</el-row>
</div>
<el-table
:data=
"tableData"
@
selection-change=
"handleSelectionChange"
v-loading=
"loading"
ref=
"tableRef"
row-key=
"fortuneAccountBizId"
:reserve-selection=
"true"
border=
"true"
>
row-key=
"fortuneAccountBizId"
:reserve-selection=
"true"
:
border=
"true"
>
<el-table-column
type=
"selection"
width=
"55"
:reserve-selection=
"true"
/>
<el-table-column
prop=
"broker"
label=
"转介人"
min-width=
"120"
sortable
/>
<el-table-column
prop=
"team"
label=
"所属团队"
min-width=
"120"
sortable
/>
...
...
@@ -68,6 +42,7 @@
</el-tag>
</
template
>
</el-table-column>
<el-table-column
prop=
"fortuneAccountDate"
label=
"出账日"
min-width=
"150"
show-overflow-tooltip
/>
<el-table-column
prop=
"remark"
label=
"备注"
min-width=
"150"
show-overflow-tooltip
/>
</el-table>
</template>
...
...
@@ -107,10 +82,44 @@ import {
getReferrerFortuneList
,
salaryStatistics
,
}
from
'@/api/financial/commission'
import
{
searchIntermediaries
}
from
'@/api/search'
//
import { searchIntermediaries } from '@/api/search'
import
{
formatCurrency
}
from
'@/utils/number'
import
{
debounce
}
from
'@/utils/index'
;
import
{
ElMessageBox
}
from
'element-plus'
import
{
ElMessageBox
}
from
'element-plus'
import
SearchForm
from
'@/components/SearchForm/SearchForm.vue'
const
searchFormRef
=
ref
(
null
)
const
searchParams
=
ref
({})
const
searchConfig
=
ref
([
{
type
:
'select'
,
prop
:
'brokerBizIdList'
,
label
:
'转介人'
,
api
:
'/insurance/base/api/userSaleExpand/page'
,
keywordField
:
'realName'
,
requestParams
:
{
pageNo
:
1
,
pageSize
:
20
},
placeholder
:
'输入转介人名称搜索'
,
debounceWait
:
500
,
// 自定义防抖时间
valueKey
:
'userSaleBizId'
,
labelKey
:
'realName'
,
multiple
:
true
,
transform
:
(
res
)
=>
{
return
res
?.
data
.
records
||
[]
}
},
{
type
:
'daterange'
,
prop
:
'payoutDate'
,
label
:
'出账日(实)'
,
startPlaceholder
:
'开始时间'
,
endPlaceholder
:
'结束时间'
},
{
type
:
'select'
,
prop
:
'status'
,
label
:
'出账状态'
,
dictType
:
'csf_fortune_account_status'
}
])
// 添加表格引用
const
tableRef
=
ref
()
// 存储所有选中的行数据(用于跨页保持选择)
...
...
@@ -120,16 +129,7 @@ const statisticInfo = ref({
totalAmount
:
0
,
brokerCount
:
0
})
// 查询参数
const
queryParams
=
reactive
({
broker
:
''
,
accountDate
:
[],
accountDateStart
:
''
,
accountDateEnd
:
''
,
sortField
:
''
,
sortOrder
:
'desc'
,
status
:
'6'
})
// 表格数据
const
tableData
=
ref
([])
const
loading
=
ref
(
false
)
...
...
@@ -221,16 +221,14 @@ const clearAllSelection = () => {
// 获取数据列表
const
getList
=
async
()
=>
{
const
getList
=
async
(
searchParams
=
{}
)
=>
{
loading
.
value
=
true
try
{
if
(
queryParams
.
accountDate
.
length
>
0
)
{
queryParams
.
accountDateStart
=
queryParams
.
accountDate
[
0
]
queryParams
.
accountDateEnd
=
queryParams
.
accountDate
[
1
]
}
const
params
=
{
...
queryParams
,
accountDate
:
undefined
,
...
searchParams
,
accountDateStart
:
searchParams
.
payoutDate
?.[
0
]
||
undefined
,
accountDateEnd
:
searchParams
.
payoutDate
?.[
1
]
||
undefined
,
payoutDate
:
undefined
,
pageNo
:
currentPage
.
value
,
pageSize
:
pageSize
.
value
}
...
...
@@ -241,7 +239,7 @@ const getList = async () => {
pageTotal
.
value
=
response
.
data
.
page
.
total
loading
.
value
=
false
}
if
(
response
.
data
.
statisticsVO
&&
isSearch
.
value
)
{
if
(
response
.
data
.
statisticsVO
)
{
statisticInfo
.
value
=
response
.
data
.
statisticsVO
}
// 数据加载完成后,设置当前页的选中状态
...
...
@@ -266,32 +264,19 @@ const getStatusType = status => {
// 查询
const
handleQuery
=
()
=>
{
queryParams
.
pageNo
=
1
Object
.
values
(
queryParams
).
some
(
value
=>
{
if
(
Array
.
isArray
(
value
)
&&
value
.
length
>
0
)
{
isSearch
.
value
=
true
}
if
(
value
!==
''
&&
value
!=
null
)
{
isSearch
.
value
=
true
}
})
const
params
=
searchFormRef
.
value
.
getSearchParams
()
console
.
log
(
'父组件发起查询:'
,
params
)
clearAllSelection
()
getList
()
getList
(
params
)
}
// 重置查询
const
handleReset
=
()
=>
{
Object
.
assign
(
queryParams
,
{
broker
:
''
,
accountDateStart
:
''
,
accountDateEnd
:
''
,
sortField
:
''
,
sortOrder
:
'desc'
})
isSearch
.
value
=
false
// 清空选择
clearAllSelection
()
getList
()
// 重置搜索表单
searchFormRef
.
value
.
resetForm
()
searchParams
.
value
=
{}
console
.
log
(
'表单已重置'
)
getList
(
searchParams
.
value
)
}
// 选择行变化
...
...
@@ -391,7 +376,7 @@ const fetchCompletePolicyFortune = async row => {
const
searchLoading
=
ref
(
false
)
const
brokerOptions
=
ref
([])
// 获取转介人列表
const
loadBrokers
=
async
(
query
=
''
)
=>
{
const
loadBrokers
=
async
(
query
=
''
)
=>
{
searchLoading
.
value
=
true
const
params
=
{
realName
:
query
,
...
...
@@ -399,18 +384,18 @@ const loadBrokers = async (query='') => {
pageSize
:
1000
,
}
try
{
const
res
=
await
searchIntermediaries
(
params
)
if
(
res
.
code
===
200
)
{
brokerOptions
.
value
=
res
.
data
.
records
||
[]
searchLoading
.
value
=
false
}
else
{
brokerOptions
.
value
=
[]
searchLoading
.
value
=
false
}
//
const res = await searchIntermediaries(params)
//
if (res.code === 200) {
//
brokerOptions.value = res.data.records || []
//
searchLoading.value = false
//
} else {
//
brokerOptions.value = []
//
searchLoading.value = false
//
}
}
catch
(
error
)
{
}
}
debounce
(
loadBrokers
,
500
,
false
)
debounce
(
loadBrokers
,
500
,
false
)
const
visibleDefaultButtons
=
ref
([
'reset'
,
'query'
])
// 按钮配置
const
operationBtnList
=
ref
([
...
...
src/views/financialCenter/payables.vue
View file @
8fd3df19
...
...
@@ -64,20 +64,24 @@
</div>
<!-- 应付款管理列表 -->
<el-table
:data=
"tableData"
height=
"400"
border
highlight-current-row
style=
"width: 100%"
v-loading=
"loading"
>
<el-table-column
prop=
"commissionBizType"
label=
"应付款类型"
width=
"120"
fixed=
"left"
sortable
/>
<el-table-column
prop=
"payableNo"
label=
"应付款编号"
width=
"120"
/>
<el-table-column
prop=
"policyNo"
label=
"保单号"
width=
"120"
/>
<el-table-column
prop=
"fortuneBizType"
label=
"应付款类型"
width=
"120"
fixed=
"left"
sortable
>
<template
#
default=
"
{ row }">
{{
getFortuneBizTypeLabel
(
row
.
fortuneBizType
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"payableNo"
label=
"应付款编号"
width=
"120"
sortable
/>
<el-table-column
prop=
"policyNo"
label=
"保单号"
width=
"120"
sortable
/>
<el-table-column
prop=
"status"
label=
"出账状态"
width=
"120"
sortable
>
<
template
#
default=
"{ row }"
>
<el-tag
:type=
"row.status === '1' ? 'success' : 'warning'"
>
{{
row
.
status
===
'1'
?
'已入账'
:
'待入账'
}}
</el-tag>
{{
getDictLabel
(
'csf_expected_fortune_status'
,
row
.
status
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"currency"
label=
"出账币种"
width=
"120"
sortable
/>
<el-table-column
prop=
"fortunePeriod"
label=
"出账期数"
width=
"120"
sortable
/>
<el-table-column
prop=
"fortuneTotalPeriod"
label=
"出账总期数"
width=
"120"
sortable
/>
<el-table-column
prop=
"fortune
Typ
e"
label=
"出账项目"
width=
"120"
sortable
/>
<el-table-column
prop=
"fortune
Nam
e"
label=
"出账项目"
width=
"120"
sortable
/>
<el-table-column
prop=
"payoutDate"
label=
"出账日(估)"
width=
"120"
sortable
/>
<el-table-column
prop=
"actualPayoutDate"
label=
"出账日(实)"
width=
"120"
sortable
/>
<el-table-column
prop=
"commissionRatio"
label=
"出账比例(估)"
width=
"140"
sortable
>
<
template
#
default=
"{ row }"
>
{{
(
row
.
commissionRatio
||
0
)
+
'%'
}}
...
...
@@ -85,7 +89,7 @@
</el-table-column>
<el-table-column
prop=
"amount"
label=
"出账金额(估)"
width=
"140"
sortable
>
<
template
#
default=
"{ row }"
>
{{
formatCurrency
(
row
.
expectedA
mount
)
}}
{{
formatCurrency
(
row
.
a
mount
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"paidRatio"
label=
"已出账比例"
width=
"120"
sortable
>
...
...
@@ -153,6 +157,18 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import
{
formatCurrency
}
from
'@/utils/number'
import
{
expectedFortuneList
,
payRecordList
}
from
'@/api/financial/commission'
import
SearchForm
from
'@/components/SearchForm/SearchForm.vue'
import
{
getDictLabel
}
from
'@/utils/useDict'
;
// 应收单类型
const
fortuneBizTypeOptions
=
[
{
value
:
'R'
,
label
:
'关联保单应付单'
},
{
value
:
'U'
,
label
:
'非关联保单应付单'
}
]
// 应付单类型通过value转成label
const
getFortuneBizTypeLabel
=
(
value
)
=>
{
const
item
=
fortuneBizTypeOptions
.
find
(
item
=>
item
.
value
===
value
)
return
item
?.
label
||
''
}
const
searchFormRef
=
ref
(
null
)
const
searchParams
=
ref
({})
const
searchConfig
=
ref
([
...
...
@@ -173,39 +189,22 @@ const searchConfig = ref([
prop
:
'statusList'
,
label
:
'出账状态'
,
multiple
:
true
,
options
:
[
{
value
:
'1'
,
label
:
'启用'
},
{
value
:
'0'
,
label
:
'禁用'
}
]
dictType
:
'csf_expected_fortune_status'
},
{
type
:
'input'
,
prop
:
'fortunePeriod'
,
label
:
'出账期数'
label
:
'出账期数'
,
inputType
:
'decimal'
,
rules
:
[
{
pattern
:
/^
\d
+$/
,
message
:
'只能输入正整数'
,
trigger
:
'blur'
}
]
},
{
type
:
'select'
,
prop
:
'fortuneName'
,
label
:
'出账项目'
,
options
:
[
{
value
:
'1'
,
label
:
'启用'
},
{
value
:
'0'
,
label
:
'禁用'
}
]
},
{
type
:
'select'
,
prop
:
'insurerBizId'
,
label
:
'对账公司'
,
api
:
'/insurance/base/api/insuranceReconciliationCompany/page'
,
keywordField
:
'name'
,
requestParams
:
{
pageNo
:
1
,
pageSize
:
20
},
placeholder
:
'输入对账公司名称搜索'
,
debounceWait
:
500
,
// 自定义防抖时间
valueKey
:
'reconciliationCompanyBizId'
,
labelKey
:
'name'
,
transform
:
(
res
)
=>
{
return
res
?.
data
.
records
||
[]
}
dictType
:
'csf_fortune_type'
},
{
type
:
'select'
,
...
...
@@ -219,6 +218,7 @@ const searchConfig = ref([
valueKey
:
'insuranceCompanyBizId'
,
labelKey
:
'abbreviation'
,
transform
:
(
res
)
=>
{
console
.
log
(
res
)
return
res
?.
data
.
records
||
[]
}
},
{
...
...
@@ -227,7 +227,7 @@ const searchConfig = ref([
label
:
'产品计划'
,
api
:
'/product/api/relProjectProductLaunch/parameter/page'
,
keywordField
:
'productName'
,
requestParams
:
{
fieldBizId
:
'field_olk1qZe81qHHKXbw'
,
fieldValueBizId
:
'field_value_uOfJH5ucA2YwJpbn'
,
pageNo
:
1
,
pageSize
:
20
},
requestParams
:
{
fieldBizId
:
'field_olk1qZe81qHHKXbw'
,
fieldValueBizId
:
'field_value_uOfJH5ucA2YwJpbn'
,
pageNo
:
1
,
pageSize
:
20
},
placeholder
:
'输入产品计划名称搜索'
,
debounceWait
:
500
,
// 自定义防抖时间
valueKey
:
'productLaunchBizId'
,
...
...
@@ -353,14 +353,15 @@ const handleCurrentChange = (val) => {
}
// 加载表格数据
const
loadTableData
=
async
(
searchParams
=
{})
=>
{
const
loadTableData
=
async
(
searchParams
=
{})
=>
{
console
.
log
(
searchFormRef
.
value
)
loading
.
value
=
true
try
{
const
params
=
{
...
searchParams
,
payoutDateStart
:
searchParams
.
payoutDate
?.[
0
]
||
undefined
,
payoutDateEnd
:
searchParams
.
payoutDate
?.[
1
]
||
undefined
,
payoutDate
:
undefined
,
payoutDate
:
undefined
,
pageNo
:
currentPage
.
value
,
pageSize
:
pageSize
.
value
}
...
...
src/views/financialCenter/receivables.vue
View file @
8fd3df19
<
template
>
<div>
<CommonPage
:operationBtnList=
"operationBtnList"
:showSearchForm=
"true"
:show-pagination=
"true"
:total=
"pageTotal"
:current-page=
"currentPage"
:page-size=
"pageSize"
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
>
<CommonPage
:operationBtnList=
"operationBtnList"
:showSearchForm=
"true"
:show-pagination=
"true"
:total=
"pageTotal"
:current-page=
"currentPage"
:page-size=
"pageSize"
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
>
<!-- 搜索区域 -->
<template
#
searchForm
>
<el-form
:model=
"searchFormData"
label-width=
"120px"
>
<el-row
:gutter=
"20"
>
<!-- 原有搜索项 -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"保单号"
prop=
"policyNo"
label-position=
"top"
>
<el-input
v-model=
"searchFormData.policyNo"
placeholder=
"请输入保单号"
clearable
/>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"入账日(估)"
prop=
"incomeDateRange"
label-position=
"top"
>
<el-date-picker
v-model=
"searchFormData.incomeDateRange"
type=
"daterange"
value-format=
"yyyy-MM-dd"
range-separator=
"至"
start-placeholder=
"开始日期"
end-placeholder=
"结束日期"
/>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"入账状态"
prop=
"statusList"
label-position=
"top"
>
<el-select
v-model=
"searchFormData.statusList"
placeholder=
"请选择入账状态"
clearable
multiple
>
<el-option
label=
"待入账"
value=
"0"
/>
<el-option
label=
"完成入账"
value=
"1"
/>
<el-option
label=
"部分入账"
value=
"2"
/>
<el-option
label=
"已失效"
value=
"3"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"入账期数"
prop=
"commissionPeriod"
label-position=
"top"
>
<el-input
v-model=
"searchFormData.commissionPeriod"
placeholder=
"请输入入账期数"
clearable
/>
</el-form-item>
</el-col>
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"入账项目"
prop=
"commissionName"
label-position=
"top"
>
<el-input
v-model=
"searchFormData.commissionName"
placeholder=
"请输入入账项目"
clearable
/>
</el-form-item>
</el-col>
<!-- 新增:保险公司远程搜索下拉 -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"保险公司"
prop=
"insurerBizId"
label-position=
"top"
>
<el-select
v-model=
"searchFormData.insurerBizId"
placeholder=
"请选择保险公司"
clearable
:remote-method=
"(keyword) => remoteMethod('insurer', keyword)"
:loading=
"options.insurer.loading"
filterable
remote
reserve-keyword
>
<el-option
v-for=
"item in options.insurer.options"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</el-form-item>
</el-col>
<!-- 新增:产品计划远程搜索下拉 -->
<!--
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"产品计划"
prop=
"productLaunchBizId"
label-position=
"top"
>
<el-select
v-model=
"searchFormData.productLaunchBizId"
placeholder=
"请选择产品计划"
clearable
:remote-method=
"(keyword) => remoteMethod('product', keyword)"
:loading=
"options.product.loading"
filterable
remote
reserve-keyword
>
<el-option
v-for=
"item in options.product.options"
:key=
"item.bizId"
:label=
"item.productName"
:value=
"item.bizId"
/>
</el-select>
</el-form-item>
</el-col>
-->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"应收单类型"
prop=
"commissionBizType"
label-position=
"top"
>
<el-select
v-model=
"searchFormData.commissionBizType"
placeholder=
"请选择应收单类型"
clearable
>
<el-option
label=
"关联保单应收单"
value=
"R"
/>
<el-option
label=
"非关联保单应收单"
value=
"U"
/>
</el-select>
</el-form-item>
</el-col>
<!-- 新增:出单团队远程搜索下拉 -->
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"对账公司"
prop=
"reconciliationCompanyBizId"
label-position=
"top"
>
<el-select
v-model=
"searchFormData.reconciliationCompany"
placeholder=
"请选择对账公司"
clearable
:remote-method=
"(keyword) => remoteMethod('reconciliationCompany', keyword)"
:loading=
"options.reconciliationCompany.loading"
filterable
remote
reserve-keyword
>
<el-option
v-for=
"item in options.reconciliationCompany.options"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</el-form-item>
</el-col>
<!-- 新增:出单团队远程搜索下拉 -->
<!--
<el-col
:xs=
"24"
:sm=
"12"
:md=
"8"
:lg=
"6"
>
<el-form-item
label=
"出单团队"
prop=
"outTeamBizId"
label-position=
"top"
>
<el-select
v-model=
"searchFormData.outTeamBizId"
placeholder=
"请选择出单团队"
clearable
:remote-method=
"(keyword) => remoteMethod('team', keyword)"
:loading=
"options.team.loading"
filterable
remote
reserve-keyword
>
<el-option
v-for=
"item in options.team.options"
:key=
"item.teamBizId"
:label=
"item.teamName"
:value=
"item.teamBizId"
/>
</el-select>
</el-form-item>
</el-col>
-->
</el-row>
</el-form>
<SearchForm
ref=
"searchFormRef"
:config=
"searchConfig"
/>
</
template
>
<!-- 列表区域
(原有代码不变)
-->
<!-- 列表区域 -->
<
template
#
table
>
<div
class=
"statistics-container"
v-if=
"statisticsData.totalPolicyCount > 0"
>
<el-row
:gutter=
"20"
>
...
...
@@ -176,16 +29,19 @@
</el-col>
</el-row>
</div>
<el-table
:data=
"tableData"
height=
"400"
border
highlight-current-row
style=
"width: 100%"
v-loading=
"loading"
>
<el-table-column
prop=
"commissionBizType"
label=
"应收款类型"
width=
"120"
sortable
/>
<el-table
:data=
"tableData"
height=
"400"
border
highlight-current-row
style=
"width: 100%"
v-loading=
"loading"
>
<el-table-column
prop=
"commissionBizType"
label=
"应收单类型"
width=
"130"
sortable
>
<template
#
default=
"
{ row }">
{{
getCommissionBizTypeLabel
(
row
.
commissionBizType
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"receivableNo"
label=
"应收款编号"
width=
"120"
/>
<el-table-column
prop=
"policyNo"
label=
"保单号"
width=
"120"
fixed=
"left"
sortable
/>
<el-table-column
prop=
"reconciliationCompany"
label=
"对账公司"
width=
"120"
sortable
/>
<el-table-column
prop=
"status"
label=
"入账状态"
width=
"120"
sortable
>
<
template
#
default=
"{ row }"
>
<el-tag
:type=
"row.status === '1' ? 'success' : row.status === '0' ? 'warning' : row.status === '2' ? 'info' : 'danger'"
>
{{
row
.
status
===
'1'
?
'完成入账'
:
row
.
status
===
'0'
?
'待入账'
:
row
.
status
===
'2'
?
'部分入账'
:
'已失效'
}}
</el-tag>
{{
getDictLabel
(
'csf_expected_commission_status'
,
row
.
status
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"commissionPeriod"
label=
"入账期数"
width=
"120"
sortable
/>
...
...
@@ -236,7 +92,9 @@
<
template
#
default=
"{ row }"
>
<el-popover
placement=
"right"
:width=
"200"
trigger=
"click"
>
<template
#
reference
>
<el-icon><MoreFilled
/></el-icon>
<el-icon>
<MoreFilled
/>
</el-icon>
</
template
>
<el-menu
@
select=
"handleSelect($event, row)"
popper-class=
"custom-menu"
>
<el-menu-item
:index=
"item.value"
v-for=
"item in dropdownItems"
:key=
"item.value"
>
...
...
@@ -251,22 +109,11 @@
</CommonPage>
<!-- 原有弹窗(不变) -->
<CommonDialog
dialogTitle=
"入账记录"
dialogWidth=
"80%"
:openDialog=
"entryRecordDialogTableVisible"
:showAction=
"false"
:showClose=
"true"
@
close=
"entryRecordDialogTableVisible = false"
>
<CommonDialog
dialogTitle=
"入账记录"
dialogWidth=
"80%"
:openDialog=
"entryRecordDialogTableVisible"
:showAction=
"false"
:showClose=
"true"
@
close=
"entryRecordDialogTableVisible = false"
>
<el-table
:data=
"entryRecordDialogTableData"
border
style=
"width: 100%"
>
<el-table-column
v-for=
"item in entryRecordDialogTableColumns"
:key=
"item.property"
:prop=
"item.property"
:label=
"item.label"
:width=
"item.width"
/>
<el-table-column
v-for=
"item in entryRecordDialogTableColumns"
:key=
"item.property"
:prop=
"item.property"
:label=
"item.label"
:width=
"item.width"
/>
<el-table-column
fixed=
"right"
label=
"操作"
min-width=
"120"
>
<
template
#
default
>
<el-button
link
type=
"primary"
size=
"small"
@
click=
"handleClick"
>
...
...
@@ -277,32 +124,16 @@
</el-table>
</CommonDialog>
<CommonDialog
dialogTitle=
"操作记录"
dialogWidth=
"80%"
:openDialog=
"actionRecordsDialogVisible"
:showAction=
"false"
:showClose=
"true"
@
close=
"actionRecordsDialogVisible = false"
>
<CommonDialog
dialogTitle=
"操作记录"
dialogWidth=
"80%"
:openDialog=
"actionRecordsDialogVisible"
:showAction=
"false"
:showClose=
"true"
@
close=
"actionRecordsDialogVisible = false"
>
<el-table
:data=
"actionRecordsDialogTableData"
border
style=
"width: 100%"
>
<el-table-column
v-for=
"item in actionRecordsDialogTableColumns"
:key=
"item.property"
:prop=
"item.property"
:label=
"item.label"
:width=
"item.width"
/>
<el-table-column
v-for=
"item in actionRecordsDialogTableColumns"
:key=
"item.property"
:prop=
"item.property"
:label=
"item.label"
:width=
"item.width"
/>
</el-table>
</CommonDialog>
<CommonDialog
dialogTitle=
"设置入账状态"
dialogWidth=
"80%"
:openDialog=
"setStatusDialogTableVisible"
@
close=
"setStatusDialogTableVisible = false"
@
confirm=
"setStatusDialogTableVisible = false"
>
<CommonDialog
dialogTitle=
"设置入账状态"
dialogWidth=
"80%"
:openDialog=
"setStatusDialogTableVisible"
@
close=
"setStatusDialogTableVisible = false"
@
confirm=
"setStatusDialogTableVisible = false"
>
<el-form
:model=
"form"
>
<el-form-item
label=
"入账状态"
label-width=
"120"
>
<el-select
v-model=
"form.status"
placeholder=
"请选择入账状态"
>
...
...
@@ -332,11 +163,121 @@ import CommonDialog from '@/components/commonDialog'
import
{
ref
,
reactive
,
onMounted
}
from
'vue'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
MoreFilled
}
from
'@element-plus/icons-vue'
import
{
numberWithCommas
,
debounce
}
from
'@/utils/index'
import
{
receivedFortuneList
,
commissionEntryRecord
,
commissionEntryEditRecords
}
from
'@/api/financial/commission'
import
{
receivedFortuneList
,
commissionEntryRecord
,
commissionEntryEditRecords
,
exportReceivedFortune
}
from
'@/api/financial/commission'
import
{
numberWithCommas
}
from
'@/utils/index'
import
SearchForm
from
'@/components/SearchForm/SearchForm.vue'
import
{
getDictLabel
}
from
'@/utils/useDict'
;
import
{
safeDownload
}
from
'@/utils/safeDownload'
// 应收单类型
const
commissionBizTypeOptions
=
[
{
value
:
'R'
,
label
:
'关联保单应收单'
},
{
value
:
'U'
,
label
:
'非关联保单应收单'
}
]
// 查询下拉列表数据
import
{
searchInsurers
,
searchReconciliationCompanies
}
from
'@/api/search'
const
searchFormRef
=
ref
(
null
)
const
searchParams
=
ref
({})
const
searchConfig
=
ref
([
{
type
:
'input'
,
prop
:
'policyNo'
,
label
:
'保单号'
},
{
type
:
'daterange'
,
prop
:
'entryDate'
,
label
:
'入账日(估)'
,
startPlaceholder
:
'开始时间'
,
endPlaceholder
:
'结束时间'
},
{
type
:
'select'
,
prop
:
'statusList'
,
label
:
'入账状态'
,
multiple
:
true
,
dictType
:
'csf_expected_commission_status'
},
{
type
:
'input'
,
prop
:
'commissionPeriod'
,
label
:
'入账期数'
,
inputType
:
'decimal'
,
rules
:
[
{
pattern
:
/^
\d
+$/
,
message
:
'只能输入正整数'
,
trigger
:
'blur'
}
]
},
{
type
:
'select'
,
prop
:
'fortuneName'
,
label
:
'入账项目'
,
dictType
:
'csf_commission_type'
},
{
type
:
'select'
,
prop
:
'reconciliationCompanyBizIdList'
,
label
:
'对账公司'
,
api
:
'/insurance/base/api/insuranceReconciliationCompany/page'
,
keywordField
:
'name'
,
requestParams
:
{
pageNo
:
1
,
pageSize
:
20
},
placeholder
:
'输入对账公司名称搜索'
,
debounceWait
:
500
,
// 自定义防抖时间
multiple
:
true
,
valueKey
:
'reconciliationCompanyBizId'
,
labelKey
:
'name'
,
transform
:
(
res
)
=>
{
console
.
log
(
res
)
return
res
?.
data
.
records
||
[]
}
},
{
type
:
'select'
,
prop
:
'insurerCompanyBizIdList'
,
label
:
'保险公司'
,
api
:
'/insurance/base/api/insuranceCompany/page'
,
keywordField
:
'queryContent'
,
requestParams
:
{
pageNo
:
1
,
pageSize
:
20
},
placeholder
:
'输入保险公司名称搜索'
,
debounceWait
:
500
,
// 自定义防抖时间
multiple
:
true
,
valueKey
:
'insuranceCompanyBizId'
,
labelKey
:
'abbreviation'
,
transform
:
(
res
)
=>
{
return
res
?.
data
.
records
||
[]
}
},
{
type
:
'select'
,
prop
:
'productLaunchBizId'
,
label
:
'产品计划'
,
api
:
'/product/api/relProjectProductLaunch/parameter/page'
,
keywordField
:
'productName'
,
requestParams
:
{
fieldBizId
:
'field_olk1qZe81qHHKXbw'
,
fieldValueBizId
:
'field_value_uOfJH5ucA2YwJpbn'
,
pageNo
:
1
,
pageSize
:
20
},
placeholder
:
'输入产品计划名称搜索'
,
debounceWait
:
500
,
// 自定义防抖时间
valueKey
:
'productLaunchBizId'
,
labelKey
:
'productName'
,
transform
:
(
res
)
=>
{
return
res
?.
data
.
records
||
[]
}
},
{
type
:
'select'
,
prop
:
'commissionBizType'
,
label
:
'应收单类型'
,
options
:
commissionBizTypeOptions
,
},
{
type
:
'select'
,
prop
:
'teamBizId'
,
label
:
'出单团队'
,
api
:
'/csf/api/team/page'
,
keywordField
:
'teamName'
,
requestParams
:
{
pageNo
:
1
,
pageSize
:
20
},
placeholder
:
'输入出单团队名称搜索'
,
debounceWait
:
500
,
// 自定义防抖时间
valueKey
:
'teamBizId'
,
labelKey
:
'teamName'
,
transform
:
(
res
)
=>
{
return
res
?.
data
.
records
||
[]
}
},
])
// 分页相关
const
currentPage
=
ref
(
1
)
...
...
@@ -344,6 +285,13 @@ const pageSize = ref(10)
const
pageTotal
=
ref
(
0
)
const
loading
=
ref
(
false
)
// 应收单类型通过value转成label
const
getCommissionBizTypeLabel
=
(
value
)
=>
{
const
item
=
commissionBizTypeOptions
.
find
(
item
=>
item
.
value
===
value
)
return
item
?.
label
||
''
}
// 表格操作菜单
const
dropdownItems
=
[
{
label
:
'入账记录'
,
value
:
'entryRecord'
},
...
...
@@ -361,13 +309,6 @@ const entryRecordDialogTableData = ref([])
const
entryRecordDialogTableColumns
=
ref
([])
const
actionRecordsDialogTableColumns
=
ref
([])
// 新增:下拉框选项状态管理(loading + 选项列表)
const
options
=
reactive
({
insurer
:
{
loading
:
false
,
options
:
[]
},
// 保险公司
product
:
{
loading
:
false
,
options
:
[]
},
// 产品计划
reconciliationCompany
:
{
loading
:
false
,
options
:
[]
},
// 对账公司
})
// 设置入账状态表单
const
form
=
reactive
({
status
:
''
,
...
...
@@ -376,20 +317,6 @@ const form = reactive({
const
selectedRow
=
ref
(
null
)
// 搜索表单数据(新增下拉框绑定字段)
const
searchFormData
=
reactive
({
policyNo
:
''
,
incomeDateRange
:
[],
statusList
:
[],
commissionName
:
''
,
commissionPeriod
:
''
,
reconciliationCompany
:
''
,
insurerBizId
:
''
,
// 保险公司ID
productLaunchBizId
:
''
,
// 产品计划ID
commissionBizType
:
''
,
outTeamBizId
:
''
// 出单团队ID(替换原outTeam)
})
// 表格数据
const
tableData
=
ref
([])
...
...
@@ -402,80 +329,36 @@ const statisticsData = ref({
totalPolicyCount
:
0
})
// 新增:防抖处理远程搜索(延迟300ms,避免频繁请求)
const
debouncedRemoteMethod
=
debounce
(
async
(
type
,
keyword
)
=>
{
try
{
// 根据类型设置加载状态
options
[
type
].
loading
=
true
let
res
=
[]
// 不同类型调用不同接口
switch
(
type
)
{
case
'insurer'
:
res
=
await
searchInsurers
({
queryContent
:
keyword
,
pageSize
:
20
})
// 保险公司搜索
options
.
insurer
.
options
=
res
.
data
.
records
.
map
(
item
=>
{
return
{
label
:
item
.
abbreviation
,
value
:
item
.
insuranceCompanyBizId
}
})
||
[]
break
case
'reconciliationCompany'
:
res
=
await
searchReconciliationCompanies
({
name
:
keyword
,
pageSize
:
20
})
// 对账公司搜索
options
.
reconciliationCompany
.
options
=
res
.
data
.
records
.
map
(
item
=>
{
return
{
label
:
item
.
name
,
value
:
item
.
reconciliationCompanyBizId
}
})
||
[]
break
default
:
break
}
}
catch
(
error
)
{
console
.
error
(
`搜索
${
type
}
失败:`
,
error
)
ElMessage
.
error
(
`加载
${
type
===
'insurer'
?
'保险公司'
:
type
===
'product'
?
'产品计划'
:
'出单团队'
}
失败`
)
}
finally
{
// 关闭加载状态
options
[
type
].
loading
=
false
}
console
.
log
(
options
)
},
300
)
// 新增:远程搜索方法(对外暴露)
const
remoteMethod
=
(
type
,
keyword
)
=>
{
// if (!keyword) {
// // 关键词为空时清空选项(可选)
// options[type].options = []
// return
// }
debouncedRemoteMethod
(
type
,
keyword
)
}
// remoteMethod('reconciliationCompany', searchFormData.reconciliationCompany)
// remoteMethod('insurer', searchFormData.insurerBizId)
// 按钮事件处理
const
handleAdd
=
()
=>
ElMessage
.
info
(
'点击新增按钮'
)
const
handleImport
=
()
=>
ElMessage
.
info
(
'点击导入按钮'
)
const
handleExport
=
()
=>
ElMessage
.
info
(
'点击导出按钮'
)
const
handleExport
=
async
()
=>
{
// 获取搜索参数
const
params
=
searchFormRef
.
value
?.
getSearchParams
()
||
{}
const
response
=
await
exportReceivedFortune
(
params
)
// 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串
const
fileName
=
`应收款导出_
${
new
Date
().
toLocaleString
().
replace
(
/
\/
/g
,
''
).
replace
(
/:/g
,
''
).
replace
(
/
\s
/g
,
''
)}
.xlsx`
await
safeDownload
(
response
,
fileName
,
'application/vnd.ms-excel;charset=utf-8'
)
}
const
handleReset
=
()
=>
{
// 重置搜索表单
Object
.
keys
(
searchFormData
).
forEach
(
key
=>
{
if
(
Array
.
isArray
(
searchFormData
[
key
]))
{
searchFormData
[
key
]
=
[]
}
else
{
searchFormData
[
key
]
=
''
}
})
// 重置下拉框选项
Object
.
keys
(
options
).
forEach
(
key
=>
{
options
[
key
].
options
=
[]
})
ElMessage
.
success
(
'搜索条件已重置'
)
searchFormRef
.
value
.
resetForm
()
searchParams
.
value
=
{}
console
.
log
(
'表单已重置'
)
loadTableData
()
}
const
handleQuery
=
()
=>
loadTableData
()
const
handleQuery
=
()
=>
{
const
params
=
searchFormRef
.
value
.
getSearchParams
()
loadTableData
(
params
)
}
// 按钮配置
const
operationBtnList
=
ref
([
...
...
@@ -498,14 +381,14 @@ const handleCurrentChange = (val) => {
}
// 加载表格数据
const
loadTableData
=
async
()
=>
{
const
loadTableData
=
async
(
searchParams
=
{}
)
=>
{
loading
.
value
=
true
try
{
const
params
=
{
...
search
FormData
,
commissionDateStart
:
search
FormData
.
incomeDateRange
[
0
]
||
''
,
commissionDateEnd
:
search
FormData
.
incomeDateRange
[
1
]
||
''
,
incomeDateRang
e
:
undefined
,
...
search
Params
,
commissionDateStart
:
search
Params
?.
entryDate
?.[
0
]
||
undefined
,
commissionDateEnd
:
search
Params
?.
entryDate
?.[
1
]
||
undefined
,
entryDat
e
:
undefined
,
pageNo
:
currentPage
.
value
,
pageSize
:
pageSize
.
value
}
...
...
@@ -646,11 +529,7 @@ const handleConfirmSetStatus = () => {
}
// 初始化加载数据
onMounted
(()
=>
{
loadTableData
()
})
loadTableData
()
</
script
>
<
style
scoped
lang=
"scss"
>
</
style
>
\ No newline at end of file
<
style
scoped
lang=
"scss"
></
style
>
\ No newline at end of file
vite.config.js
View file @
8fd3df19
...
...
@@ -52,6 +52,17 @@ export default defineConfig(({ mode, command }) => {
changeOrigin
:
true
,
rewrite
:
(
p
)
=>
p
.
replace
(
/^
\/
dev-api/
,
''
)
},
'/csf'
:
{
target
:
'http://139.224.145.34:9002'
,
changeOrigin
:
true
,
secure
:
false
,
// 如果后端需要 host 头
// configure: (proxy, options) => {
// proxy.on('proxyReq', (proxyReq, req, res) => {
// proxyReq.setHeader('host', '139.224.145.34:9002')
// })
// }
},
// springdoc proxy
'^/v3/api-docs/(.*)'
:
{
target
:
baseUrl
,
...
...
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