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
a0e22287
Commit
a0e22287
authored
May 13, 2026
by
yuzhenWang
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'wyz' into 'test'
发布测试 See merge request
!141
parents
1d9c426b
8e482ad9
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
164 additions
and
279 deletions
+164
-279
src/components/SearchForm/SearchForm.vue
+134
-266
src/views/financialCenter/financialBilling.vue
+14
-6
src/views/financialCenter/payables.vue
+16
-7
No files found.
src/components/SearchForm/SearchForm.vue
View file @
a0e22287
...
...
@@ -146,44 +146,6 @@
@
change=
"val => handleModelChange(val, item)"
/>
<!-- Upload 回显值得时候数据格式至少是[
{url: '必须要传', name: 'name不是必须的根据需要传值'}]-->
<!--
<el-upload
v-else-if=
"item.type === 'upload'"
v-model:file-list=
"localModel[item.prop]"
:action=
"item.action"
:headers=
"item.headers"
:multiple=
"!!item.multiple"
:limit=
"item.limit || (item.multiple ? 999 : 1)"
:accept=
"item.accept"
:list-type=
"item.listType || 'text'"
:disabled=
"item.disabled"
:auto-upload=
"true"
:show-file-list=
"item.showFileList"
:on-exceed=
"handleExceed"
:before-upload=
"file => beforeUpload(file, item)"
:on-success=
"(res, file, fileList) => handleUploadSuccess(res, file, fileList, item)"
:on-error=
"(err, file, fileList) => handleUploadError(err, file, fileList, item)"
:on-remove=
"(file, fileList) => handleUploadRemove(file, fileList, item)"
>
<el-icon
class=
"iconStyle"
:size=
"20"
v-if=
"item.uploadType === 'image'"
>
<Upload
/>
</el-icon>
<el-button
v-else
size=
"small"
type=
"primary"
:link=
"item.link"
:disabled=
"item.disabled"
>
{{
'点击上传文件'
}}
</el-button>
<template
#
tip
v-if=
"item.maxSize || item.accept"
>
<div
class=
"el-upload__tip"
>
<span
v-if=
"item.maxSize"
>
大小不超过
{{
formatFileSize
(
item
.
maxSize
)
}}
</span>
<span
v-if=
"item.accept"
>
支持格式:
{{
item
.
accept
}}
</span>
</div>
</
template
>
</el-upload>
-->
<!-- Upload 回显值时数据格式至少是 [{url: '必须', name: '可选'}] -->
<template
v-else-if=
"item.type === 'upload'"
>
<!-- 🔽 默认模式:完整使用 el-upload(含自带文件列表) -->
<el-upload
...
...
@@ -325,14 +287,12 @@ import useDictStore from '@/store/modules/dict'
import
{
getDicts
}
from
'@/api/system/dict/data'
import
request
from
'@/utils/request'
import
dayjs
from
'dayjs'
// ==================== 上传文件 ====================
// ==================== 文件预览弹窗 ====================
const
previewDialogVisible
=
ref
(
false
)
const
previewUrl
=
ref
(
''
)
const
previewFileName
=
ref
(
''
)
const
previewFileType
=
ref
(
''
)
// 'image', 'pdf', 'unsupported'
//新增文件上传自定义方法开始
// 预览文件(支持图片和PDF)
// 预览文件(页面内弹窗,不打开新窗口)
function
previewFile
(
file
,
item
)
{
...
...
@@ -364,7 +324,6 @@ function removeFile(file, item) {
const
newList
=
fileList
.
filter
(
f
=>
f
.
uid
!==
file
.
uid
)
handleUploadRemove
(
file
,
newList
,
item
)
// 调用原有的删除处理函数
}
//新增文件上传自定义方法结束
// 文件大小格式化
function
formatFileSize
(
bytes
)
{
...
...
@@ -374,6 +333,7 @@ function formatFileSize(bytes) {
const
i
=
Math
.
floor
(
Math
.
log
(
bytes
)
/
Math
.
log
(
k
))
return
parseFloat
((
bytes
/
Math
.
pow
(
k
,
i
)).
toFixed
(
2
))
+
' '
+
sizes
[
i
]
}
function
beforeUpload
(
file
,
item
)
{
// 检查文件大小
if
(
item
.
maxSize
&&
file
.
size
>
item
.
maxSize
)
{
...
...
@@ -393,10 +353,8 @@ function beforeUpload(file, item) {
return
true
}
function
handleUploadSuccess
(
response
,
file
,
fileList
,
item
)
{
// 假设你的后端返回格式:{ code: 200, data: { url: '...', name: 'xxx.pdf' } }
// 你可以通过 item.responseMap 自定义映射,这里先用通用方式
function
handleUploadSuccess
(
response
,
file
,
fileList
,
item
)
{
const
data
=
response
.
data
||
response
const
url
=
data
.
url
||
data
.
fileUrl
||
data
.
path
const
name
=
data
.
name
||
data
.
fileName
||
file
.
name
...
...
@@ -416,9 +374,8 @@ function handleUploadSuccess(response, file, fileList, item) {
// 触发 model 更新
handleModelChange
([...
fileList
],
item
)
ElMessage
.
success
(
`文件
${
file
.
name
}
上传成功`
)
// console.log('上传成功', item)
}
function
handleExceed
(
files
,
fileList
)
{
ElMessage
.
warning
(
'超出文件数量限制'
)
}
...
...
@@ -432,6 +389,7 @@ function handleUploadRemove(file, fileList, item) {
// 用户删除文件时,同步更新 model
handleModelChange
([...
fileList
],
item
)
}
// ==================== 工具函数:深拷贝配置(保留函数) ====================
function
deepCloneConfig
(
obj
)
{
if
(
obj
===
null
||
typeof
obj
!==
'object'
)
return
obj
...
...
@@ -445,6 +403,7 @@ function deepCloneConfig(obj) {
}
return
cloned
}
function
parseToDate
(
str
)
{
if
(
!
str
)
return
null
if
(
str
===
'today'
)
{
...
...
@@ -459,6 +418,7 @@ function parseToDate(str) {
}
return
null
}
// ==================== 生成 disabledDate 函数 ====================
function
getDisabledDateFn
(
item
)
{
const
{
minDate
,
maxDate
}
=
item
...
...
@@ -503,6 +463,7 @@ function getDisabledDateFn(item) {
return
false
}
}
// ==================== Props & Emits ====================
const
props
=
defineProps
({
modelValue
:
{
type
:
Object
,
default
:
()
=>
({})
},
...
...
@@ -520,9 +481,7 @@ const emit = defineEmits([
// ==================== Refs ====================
const
formRef
=
ref
(
null
)
// 使用 shallowRef 避免深层响应式(表单通常扁平)
const
localModel
=
ref
({
...
props
.
modelValue
})
// 记录哪些字段的字典已加载
const
dictLoaded
=
ref
(
new
Set
())
const
internalConfig
=
ref
([])
const
remoteOptions
=
ref
({})
// { prop: [options] }
...
...
@@ -546,7 +505,31 @@ const formRules = computed(() => {
return
rules
})
// 1. 外部 modelValue 变化时,安全同步(仅当内容不同时)
// 同步 extra 字段:根据当前选中的 option 填充 extra 字段(不触发外部 emit)
function
syncExtraFieldsForProp
(
prop
,
value
)
{
const
item
=
internalConfig
.
value
.
find
(
i
=>
i
.
prop
===
prop
)
if
(
!
item
||
item
.
type
!==
'select'
||
!
item
.
onChangeExtraFields
)
return
false
const
options
=
getSelectOptions
(
item
)
const
selectedOption
=
options
.
find
(
opt
=>
String
(
opt
.
value
)
===
String
(
value
))
if
(
selectedOption
&&
selectedOption
.
raw
)
{
let
needUpdate
=
false
const
newModel
=
{
...
localModel
.
value
}
for
(
const
[
targetProp
,
sourceKey
]
of
Object
.
entries
(
item
.
onChangeExtraFields
))
{
const
extraValue
=
selectedOption
.
raw
[
sourceKey
]
if
(
newModel
[
targetProp
]
!==
extraValue
)
{
newModel
[
targetProp
]
=
extraValue
needUpdate
=
true
}
}
if
(
needUpdate
)
{
localModel
.
value
=
newModel
return
true
}
}
return
false
}
// 监听 config 变化(支持动态 config)
watch
(
()
=>
props
.
config
,
...
...
@@ -559,7 +542,6 @@ watch(
const
initialModel
=
{}
for
(
const
item
of
internalConfig
.
value
)
{
const
key
=
item
.
prop
// 优先用父传值,否则用默认值
if
(
props
.
modelValue
?.[
key
]
!==
undefined
)
{
initialModel
[
key
]
=
props
.
modelValue
[
key
]
}
else
if
(
...
...
@@ -572,27 +554,22 @@ watch(
}
}
// ✅ 在这里同步 modelValue(包括 extra 字段)
localModel
.
value
=
syncModelFromProps
(
props
.
modelValue
,
internalConfig
.
value
)
// console.log('子组件监测config变化', localModel.value)
},
{
immediate
:
true
}
)
// console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue)
// 监听 modelValue(用于后续外部更新)
watch
(
()
=>
props
.
modelValue
,
newVal
=>
{
if
(
!
newVal
||
!
internalConfig
.
value
)
return
// ✅ 同样使用 sync 函数
localModel
.
value
=
syncModelFromProps
(
newVal
,
internalConfig
.
value
)
// console.log('子组件监测 modelValue 变化:', localModel.value)
},
{
deep
:
true
}
)
//
提取同步逻辑
//
从 props 同步模型数据
function
syncModelFromProps
(
newModelValue
,
newConfig
)
{
if
(
!
newModelValue
||
!
newConfig
)
return
{}
...
...
@@ -614,22 +591,17 @@ function syncModelFromProps(newModelValue, newConfig) {
if
(
!
extraMap
||
typeof
extraMap
!==
'object'
)
continue
const
prop
=
item
.
prop
const
idValue
=
newModelValue
[
prop
]
// e.g. 2
const
idValue
=
newModelValue
[
prop
]
let
sourceObj
=
null
// 情况1: 如果 newModelValue[prop] 本身就是对象 → 直接用(兼容旧逻辑)
if
(
idValue
&&
typeof
idValue
===
'object'
)
{
sourceObj
=
idValue
}
// 情况2: 如果是 primitive(string/number),且有 options → 反查
else
if
(
Array
.
isArray
(
item
.
options
)
&&
idValue
!==
undefined
&&
idValue
!==
null
)
{
// 默认用 option.value 匹配,可配置 valueKey
}
else
if
(
Array
.
isArray
(
item
.
options
)
&&
idValue
!==
undefined
&&
idValue
!==
null
)
{
const
valueKey
=
item
.
valueKey
||
'value'
sourceObj
=
item
.
options
.
find
(
opt
=>
opt
[
valueKey
]
===
idValue
)
}
// 如果找到了 sourceObj,就提取 extra 字段
if
(
sourceObj
&&
typeof
sourceObj
===
'object'
)
{
for
(
const
[
targetKey
,
subPath
]
of
Object
.
entries
(
extraMap
))
{
const
val
=
getNestedValue
(
sourceObj
,
subPath
)
...
...
@@ -646,9 +618,7 @@ function syncModelFromProps(newModelValue, newConfig) {
const
extraMap
=
item
.
onChangeExtraFields
if
(
!
extraMap
||
typeof
extraMap
!==
'object'
)
continue
// 如果 newModelValue 中没有 sourceField,说明没有重新计算
if
(
newModelValue
[
sourceField
]
===
undefined
)
{
// 那么保留 localModel 中对应的 extra 字段
for
(
const
[
targetKey
,
subPath
]
of
Object
.
entries
(
extraMap
))
{
if
(
localModel
.
value
.
hasOwnProperty
(
targetKey
))
{
synced
[
targetKey
]
=
localModel
.
value
[
targetKey
]
...
...
@@ -659,79 +629,60 @@ function syncModelFromProps(newModelValue, newConfig) {
// 4. 保留 newModelValue 中已有的 extra 字段和其他额外字段
for
(
const
key
in
newModelValue
)
{
// 如果已经同步过了(比如主字段或第2步写入的extra),跳过
if
(
synced
.
hasOwnProperty
(
key
))
continue
// 判断是否是某个 extra 目标字段
const
isExtraTarget
=
newConfig
.
some
(
item
=>
item
.
onChangeExtraFields
&&
item
.
onChangeExtraFields
.
hasOwnProperty
(
key
)
)
// 如果是 extra 字段,且 newModelValue 里有值 → 保留它!
if
(
isExtraTarget
)
{
synced
[
key
]
=
newModelValue
[
key
]
}
// 如果不是 extra,也不是主字段 → 也保留(兼容 hidden 字段等)
else
if
(
!
newConfig
.
some
(
item
=>
item
.
prop
===
key
))
{
}
else
if
(
!
newConfig
.
some
(
item
=>
item
.
prop
===
key
))
{
synced
[
key
]
=
newModelValue
[
key
]
}
}
// console.log('🚀 子组件 进行modelvalue处理:', synced)
return
synced
}
function
getNestedValue
(
obj
,
path
)
{
return
path
.
split
(
'.'
).
reduce
((
current
,
key
)
=>
current
?.[
key
],
obj
)
}
// 当字典加载完成时,触发同步
function
markDictLoaded
(
prop
)
{
dictLoaded
.
value
.
add
(
prop
)
// 尝试同步该字段
if
(
props
.
modelValue
?.[
prop
]
!==
undefined
)
{
localModel
.
value
[
prop
]
=
props
.
modelValue
[
prop
]
}
}
// 2. 用户操作导致 localModel 变化时,emit(防抖可选)
// 用户操作导致 localModel 变化时,emit
function
handleModelChange
(
value
,
item
)
{
console
.
group
(
'用户操作导致 localModel 变化时,emit(防抖可选)'
)
const
newModel
=
{
...
localModel
.
value
,
[
item
.
prop
]:
value
}
if
(
item
?.
type
===
'select'
&&
item
.
onChangeExtraFields
)
{
const
options
=
getSelectOptions
(
item
)
// console.log('可用 options:', options)
const
opt
=
options
.
find
(
o
=>
o
.
value
===
value
)
// console.log('匹配的 opt:', opt)
const
opt
=
options
.
find
(
o
=>
String
(
o
.
value
)
===
String
(
value
))
if
(
opt
?.
raw
)
{
for
(
const
[
targetProp
,
sourceKey
]
of
Object
.
entries
(
item
.
onChangeExtraFields
))
{
const
extraValue
=
opt
.
raw
[
sourceKey
]
newModel
[
targetProp
]
=
extraValue
// console.log(`✅ 设置 ${targetProp} =`, extraValue)
newModel
[
targetProp
]
=
opt
.
raw
[
sourceKey
]
}
}
}
localModel
.
value
=
newModel
// console.log('子组件用户操作后,modelvalue值==', newModel)
nextTick
(()
=>
{
if
(
!
isEqualShallow
(
props
.
modelValue
,
newModel
))
{
// console.log('如果新旧值不一样,反馈给父组件', newModel)
emit
(
'update:modelValue'
,
newModel
)
}
else
{
console
.
log
(
'🚫 跳过 emit:认为相等'
)
}
})
if
(
item
.
type
===
'select'
)
{
// console.log('如果是select类型,反馈给父组件', item.prop, value, item)
emit
(
'selectChange'
,
item
.
prop
,
value
,
item
)
}
else
if
(
item
.
type
==
'upload'
)
{
// 传给父组件最新的上传值newModel
emit
(
'uploadSuccess'
,
item
.
prop
,
newModel
)
}
else
if
(
item
.
type
==
'input'
)
{
emit
(
'inputChange'
,
item
.
prop
,
value
,
item
)
}
console
.
groupEnd
()
}
// 辅助函数:浅比较两个对象
function
isEqualShallow
(
a
,
b
)
{
const
keysA
=
Object
.
keys
(
a
)
const
keysB
=
Object
.
keys
(
b
)
...
...
@@ -761,84 +712,34 @@ async function loadDictOptions(dictType) {
dictStore
.
setDict
(
dictType
,
options
)
return
options
}
catch
(
err
)
{
// console.error(`加载字典 ${dictType} 失败`, err)
ElMessage
.
error
(
`字典
${
dictType
}
加载失败`
)
return
[]
}
}
// ==================== 初始化 ====================
onMounted
(
async
()
=>
{
internalConfig
.
value
=
deepCloneConfig
(
props
.
config
)
const
initialData
=
{}
const
dictTypePromises
=
[]
const
apiPromises
=
[]
// ← 新增:收集 api 加载 promise
for
(
const
item
of
internalConfig
.
value
)
{
const
key
=
item
.
prop
if
(
localModel
.
value
[
key
]
==
null
)
{
if
(
item
.
multiple
)
{
initialData
[
key
]
=
item
.
defaultValue
??
[]
}
else
if
([
'checkbox-group'
,
'daterange'
,
'monthrange'
].
includes
(
item
.
type
))
{
initialData
[
key
]
=
item
.
defaultValue
??
[]
}
else
{
initialData
[
key
]
=
item
.
defaultValue
??
''
}
}
// 预加载 dictType
if
(
item
.
type
===
'select'
&&
item
.
dictType
)
{
dictTypePromises
.
push
(
loadDictOptions
(
item
.
dictType
).
then
(
opts
=>
{
remoteOptions
.
value
[
key
]
=
opts
markDictLoaded
(
key
)
// ← 立即标记已加载
})
)
}
// 预加载 api(远程接口)← 关键新增!
else
if
(
item
.
type
===
'select'
&&
item
.
api
)
{
apiPromises
.
push
(
loadRemoteOptionsForInit
(
item
)
// ← 专门用于初始化的加载函数
)
}
else
if
(
item
.
type
===
'select'
&&
item
.
options
)
{
remoteOptions
.
value
[
key
]
=
[...
item
.
options
]
markDictLoaded
(
key
)
}
// api 类型:延迟加载(focus 时)
}
if
(
Object
.
keys
(
initialData
).
length
>
0
)
{
localModel
.
value
=
{
...
localModel
.
value
,
...
initialData
}
}
// 等待所有字典加载完成
await
Promise
.
allSettled
(
dictTypePromises
)
})
// ==================== 获取 select 选项 ====================
function
getSelectOptions
(
item
)
{
const
key
=
item
.
prop
// 字典选项
if
(
item
.
dictType
||
item
.
api
)
{
// 字典选项
return
(
remoteOptions
.
value
[
key
]
||
[]).
map
(
opt
=>
({
value
:
opt
.
value
,
label
:
opt
.
label
,
raw
:
opt
.
raw
// ← 必须存 raw
raw
:
opt
.
raw
}))
}
else
if
(
item
.
options
)
{
// 静态选项s
return
item
.
options
.
map
(
opt
=>
({
value
:
opt
.
value
,
label
:
opt
.
label
,
raw
:
opt
.
raw
// 保留原始
raw
:
opt
.
raw
}))
}
return
[]
}
// 初始化加载远程 API 选项(无搜索词)
async
function
loadRemoteOptionsForInit
(
item
)
{
const
{
prop
,
api
,
requestParams
}
=
item
try
{
// 构造请求体:只传 requestParams,不传 keyword
const
payload
=
{
...(
typeof
requestParams
===
'function'
?
requestParams
()
:
requestParams
||
{})
}
...
...
@@ -854,38 +755,45 @@ async function loadRemoteOptionsForInit(item) {
?
item
.
transform
(
res
)
:
res
.
data
?.
records
||
res
.
data
||
[]
// 建议:统一转成字符串(或根据 item.valueType 判断)
const
newOptions
=
list
.
map
(
i
=>
({
value
:
String
(
i
[
item
.
valueKey
||
'value'
]),
// ← 强制转字符串
value
:
String
(
i
[
item
.
valueKey
||
'value'
]),
label
:
i
[
item
.
labelKey
||
'label'
],
raw
:
i
}))
remoteOptions
.
value
[
prop
]
=
newOptions
markDictLoaded
(
prop
)
// ← 关键:标记已加载
markDictLoaded
(
prop
)
// 同步 extra 字段
const
currentVal
=
localModel
.
value
[
prop
]
if
(
currentVal
!==
undefined
&&
currentVal
!==
null
&&
currentVal
!==
''
)
{
syncExtraFieldsForProp
(
prop
,
currentVal
)
}
}
catch
(
err
)
{
ElMessage
.
error
(
`预加载
${
item
.
label
}
失败`
)
remoteOptions
.
value
[
prop
]
=
[]
}
}
// ==================== 加载远程 API 选项(首次 focus 时加载,无搜索词) ====================
// 加载远程 API 选项(focus 时调用,无搜索词)
async
function
loadRemoteOptions
(
item
)
{
const
{
prop
,
api
,
requestParams
,
keywordField
,
debounceWait
,
...
rest
}
=
item
const
{
prop
,
api
,
requestParams
}
=
item
if
(
!
api
)
return
// 如果已经有选项且不是强制刷新,可跳过;但为了保证初次加载,remoteOptions[prop] 为空时才加载
if
(
remoteOptions
.
value
[
prop
]
&&
remoteOptions
.
value
[
prop
].
length
>
0
)
return
try
{
remoteLoading
.
value
[
prop
]
=
true
// 构造请求体:合并 requestParams + 分页(默认第一页)
const
payload
=
{
...(
typeof
requestParams
===
'function'
?
requestParams
()
:
requestParams
||
{})
// 注意:此时无 keyword,所以不加 keywordField
}
const
res
=
await
request
({
url
:
api
,
method
:
'post'
,
// ← 改为 POST
data
:
payload
// ← 参数放 body
method
:
'post'
,
data
:
payload
})
const
list
=
...
...
@@ -893,15 +801,19 @@ async function loadRemoteOptions(item) {
?
item
.
transform
(
res
)
:
res
.
data
?.
records
||
res
.
data
||
[]
// 建议:统一转成字符串(或根据 item.valueType 判断)
const
newOptions
=
list
.
map
(
i
=>
({
value
:
String
(
i
[
item
.
valueKey
||
'value'
]),
// ← 强制转字符串
value
:
String
(
i
[
item
.
valueKey
||
'value'
]),
label
:
i
[
item
.
labelKey
||
'label'
],
raw
:
i
}))
remoteOptions
.
value
[
prop
]
=
newOptions
// ✅ 关键:标记该字段字典已加载
markDictLoaded
(
prop
)
// 同步 extra 字段
const
currentVal
=
localModel
.
value
[
prop
]
if
(
currentVal
!==
undefined
&&
currentVal
!==
null
&&
currentVal
!==
''
)
{
syncExtraFieldsForProp
(
prop
,
currentVal
)
}
}
catch
(
err
)
{
ElMessage
.
error
(
`加载
${
item
.
label
}
失败`
)
remoteOptions
.
value
[
prop
]
=
[]
...
...
@@ -910,7 +822,7 @@ async function loadRemoteOptions(item) {
}
}
//
==================== 远程搜索(带关键词,防抖) ====================
//
远程搜索(带关键词,防抖)
let
searchTimeout
=
null
function
handleFilterChange
(
keyword
,
item
)
{
const
{
prop
,
api
,
requestParams
,
keywordField
=
'keyword'
,
debounceWait
=
300
}
=
item
...
...
@@ -921,16 +833,15 @@ function handleFilterChange(keyword, item) {
try
{
remoteLoading
.
value
[
prop
]
=
true
// 构造请求体:requestParams + 分页 + 动态关键词字段
const
payload
=
{
...(
typeof
requestParams
===
'function'
?
requestParams
()
:
requestParams
||
{}),
[
keywordField
]:
keyword
// ← 动态字段名,如 name / companyName
[
keywordField
]:
keyword
}
const
res
=
await
request
({
url
:
api
,
method
:
'post'
,
// ← POST 请求
data
:
payload
// ← body 传参
method
:
'post'
,
data
:
payload
})
const
list
=
...
...
@@ -939,9 +850,9 @@ function handleFilterChange(keyword, item) {
:
res
.
data
?.
records
||
res
.
data
||
[]
remoteOptions
.
value
[
prop
]
=
list
.
map
(
i
=>
({
value
:
i
[
item
.
valueKey
||
'value'
]
,
value
:
String
(
i
[
item
.
valueKey
||
'value'
])
,
label
:
i
[
item
.
labelKey
||
'label'
],
raw
:
i
// ← 保存完整对象
raw
:
i
}))
}
catch
(
err
)
{
ElMessage
.
error
(
`搜索
${
item
.
label
}
失败`
)
...
...
@@ -952,46 +863,6 @@ function handleFilterChange(keyword, item) {
}
// ==================== 数字输入处理 ====================
// function handleNumberInput(value, item) {
// const { inputType = 'text', decimalDigits = 2, prop } = item
// if (!prop) return
// let result = String(value ?? '').trim()
// if (inputType === 'integer') {
// // 只保留数字
// result = result.replace(/[^\d]/g, '')
// } else if (inputType === 'decimal') {
// // 1. 只保留数字和小数点
// result = result.replace(/[^\d.]/g, '')
// // 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
// if (result.startsWith('.')) {
// result = '0.' + result.slice(1)
// }
// // 3. 保证最多一个小数点
// const parts = result.split('.')
// if (parts.length > 2) {
// result = parts[0] + '.' + parts.slice(1).join('')
// }
// // 4. 限制小数位数(但保留结尾的小数点!)
// if (result.includes('.')) {
// const [intPart, decPart] = result.split('.')
// // 如果小数部分超过限制,截断
// if (decPart.length > decimalDigits) {
// result = intPart + '.' + decPart.slice(0, decimalDigits)
// }
// // ✅ 不再删除结尾的 '.'
// }
// }
// // 防止重复赋值(可选优化)
// if (localModel.value[prop] !== result) {
// localModel.value = { ...localModel.value, [prop]: result }
// }
// }
function
handleNumberInput
(
value
,
item
)
{
const
{
inputType
=
'text'
,
decimalDigits
=
2
,
prop
}
=
item
if
(
!
prop
)
return
...
...
@@ -999,83 +870,109 @@ function handleNumberInput(value, item) {
let
result
=
String
(
value
??
''
).
trim
()
if
(
inputType
===
'integer'
)
{
// 只保留数字和负号
result
=
result
.
replace
(
/
[^
-
\d]
/g
,
''
)
// 如果有多个负号或者负号不在开头,则移除多余的负号
if
((
result
.
match
(
/-/g
)
||
[]).
length
>
1
)
{
result
=
result
.
replace
(
/-/g
,
''
).
replace
(
/^/
,
'-'
)
// 仅保留一个负号在最前面
result
=
result
.
replace
(
/-/g
,
''
).
replace
(
/^/
,
'-'
)
}
}
else
if
(
inputType
===
'decimalNumber'
)
{
// 可以输入正数,负数,小数
// 1. 只保留数字、小数点和负号
result
=
result
.
replace
(
/
[^
-
\d
.
]
/g
,
''
)
// 2. 处理负号:确保最多只有一个负号且必须在开头
if
((
result
.
match
(
/-/g
)
||
[]).
length
>
1
)
{
result
=
result
.
replace
(
/-/g
,
''
)
// 移除所有负号
result
=
result
.
replace
(
/-/g
,
''
)
if
(
result
.
startsWith
(
'-'
))
{
result
=
'-'
+
result
.
slice
(
1
)
// 确保负号在最前面
result
=
'-'
+
result
.
slice
(
1
)
}
else
{
result
=
'-'
+
result
// 如果原本没有负号但需要保留数值,可以省略此步骤
result
=
'-'
+
result
}
}
// 3. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好)
if
(
result
.
startsWith
(
'.'
))
{
result
=
'0'
+
result
}
else
if
(
result
.
startsWith
(
'-.'
))
{
result
=
'-0'
+
result
.
slice
(
2
)
}
// 4. 保证最多一个小数点
const
parts
=
result
.
split
(
'.'
)
if
(
parts
.
length
>
2
)
{
result
=
parts
[
0
]
+
'.'
+
parts
.
slice
(
1
).
join
(
''
)
}
// 5. 限制小数位数(但保留结尾的小数点!)
if
(
result
.
includes
(
'.'
))
{
const
[
intPart
,
decPart
]
=
result
.
split
(
'.'
)
// 如果小数部分超过限制,截断
if
(
decPart
.
length
>
decimalDigits
)
{
result
=
intPart
+
'.'
+
decPart
.
slice
(
0
,
decimalDigits
)
}
// ✅ 不再删除结尾的 '.'
}
}
else
if
(
inputType
===
'decimal'
)
{
// 可以输入正整数和小数
// 1. 只保留数字和小数点
result
=
result
.
replace
(
/
[^\d
.
]
/g
,
''
)
// 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
if
(
result
.
startsWith
(
'.'
))
{
result
=
'0.'
+
result
.
slice
(
1
)
}
// 3. 保证最多一个小数点
const
parts
=
result
.
split
(
'.'
)
if
(
parts
.
length
>
2
)
{
result
=
parts
[
0
]
+
'.'
+
parts
.
slice
(
1
).
join
(
''
)
}
// 4. 限制小数位数(但保留结尾的小数点!)
if
(
result
.
includes
(
'.'
))
{
const
[
intPart
,
decPart
]
=
result
.
split
(
'.'
)
// 如果小数部分超过限制,截断
if
(
decPart
.
length
>
decimalDigits
)
{
result
=
intPart
+
'.'
+
decPart
.
slice
(
0
,
decimalDigits
)
}
// ✅ 不再删除结尾的 '.'
}
}
// 防止重复赋值(可选优化)
if
(
localModel
.
value
[
prop
]
!==
result
)
{
localModel
.
value
=
{
...
localModel
.
value
,
[
prop
]:
result
}
}
}
// ==================== 初始化 ====================
onMounted
(
async
()
=>
{
internalConfig
.
value
=
deepCloneConfig
(
props
.
config
)
const
initialData
=
{}
const
dictTypePromises
=
[]
const
apiPromises
=
[]
for
(
const
item
of
internalConfig
.
value
)
{
const
key
=
item
.
prop
if
(
localModel
.
value
[
key
]
==
null
)
{
if
(
item
.
multiple
)
{
initialData
[
key
]
=
item
.
defaultValue
??
[]
}
else
if
([
'checkbox-group'
,
'daterange'
,
'monthrange'
].
includes
(
item
.
type
))
{
initialData
[
key
]
=
item
.
defaultValue
??
[]
}
else
{
initialData
[
key
]
=
item
.
defaultValue
??
''
}
}
if
(
item
.
type
===
'select'
&&
item
.
dictType
)
{
dictTypePromises
.
push
(
loadDictOptions
(
item
.
dictType
).
then
(
opts
=>
{
remoteOptions
.
value
[
key
]
=
opts
markDictLoaded
(
key
)
})
)
}
else
if
(
item
.
type
===
'select'
&&
item
.
api
)
{
apiPromises
.
push
(
loadRemoteOptionsForInit
(
item
))
}
else
if
(
item
.
type
===
'select'
&&
item
.
options
)
{
remoteOptions
.
value
[
key
]
=
[...
item
.
options
]
markDictLoaded
(
key
)
}
}
if
(
Object
.
keys
(
initialData
).
length
>
0
)
{
localModel
.
value
=
{
...
localModel
.
value
,
...
initialData
}
}
// 等待所有字典和远程选项加载完成
await
Promise
.
allSettled
([...
dictTypePromises
,
...
apiPromises
])
// 所有 select 选项加载完成后,为有 onChangeExtraFields 且有值的字段填充 extra 字段
for
(
const
item
of
internalConfig
.
value
)
{
if
(
item
.
type
===
'select'
&&
item
.
onChangeExtraFields
)
{
const
currentVal
=
localModel
.
value
[
item
.
prop
]
if
(
currentVal
!==
undefined
&&
currentVal
!==
null
&&
currentVal
!==
''
)
{
syncExtraFieldsForProp
(
item
.
prop
,
currentVal
)
}
}
}
})
// ==================== 暴露方法 ====================
defineExpose
({
getFormData
()
{
...
...
@@ -1096,7 +993,7 @@ defineExpose({
if
([
'checkbox-group'
,
'daterange'
,
'monthrange'
].
includes
(
item
.
type
)
||
item
.
multiple
)
{
resetData
[
key
]
=
item
.
defaultValue
??
[]
}
else
if
(
item
.
type
===
'upload'
)
{
resetData
[
key
]
=
item
.
defaultValue
??
[]
// upload 也是数组
resetData
[
key
]
=
item
.
defaultValue
??
[]
}
else
{
resetData
[
key
]
=
item
.
defaultValue
??
''
}
...
...
@@ -1104,36 +1001,20 @@ defineExpose({
localModel
.
value
=
{
...
resetData
}
nextTick
(()
=>
formRef
.
value
?.
clearValidate
())
},
// ✅ 新增:强制刷新某个字段的远程选项
async
refreshRemoteOptions
(
targetProp
)
{
console
.
log
(
`[SearchForm] 收到刷新请求:
${
targetProp
}
`
)
// 1. 查找配置项
const
item
=
internalConfig
.
value
.
find
(
i
=>
i
.
prop
===
targetProp
)
if
(
!
item
)
{
console
.
warn
(
`[SearchForm] 未找到 prop 为
${
targetProp
}
的配置项`
)
return
}
if
(
item
.
type
!==
'select'
||
!
item
.
api
)
{
console
.
warn
(
`[SearchForm] 字段
${
targetProp
}
不是远程 Select 或没有 API`
)
return
}
console
.
log
(
`[SearchForm] 开始强制加载
${
targetProp
}
的数据,API:
${
item
.
api
}
`
)
// 2. 关键:在调用前,先清空旧数据,防止子组件内部的 "已加载则跳过" 逻辑生效
// 如果你的 loadRemoteOptions 里有 "if (remoteOptions.value[prop]?.length > 0) return"
// 这里必须先清空
remoteOptions
.
value
[
targetProp
]
=
[]
remoteLoading
.
value
[
targetProp
]
=
true
// 手动开启 loading
remoteLoading
.
value
[
targetProp
]
=
true
try
{
// 3. 调用内部加载函数
// 注意:直接调用 loadRemoteOptions,它会读取最新的 requestParams
await
loadRemoteOptions
(
item
)
console
.
log
(
`[SearchForm]
${
targetProp
}
加载完成`
)
}
catch
(
error
)
{
console
.
error
(
`[SearchForm]
${
targetProp
}
加载失败`
,
error
)
throw
error
...
...
@@ -1145,34 +1026,28 @@ defineExpose({
</
script
>
<
style
scoped
>
/* 预览弹窗样式 */
.preview-container
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
min-height
:
400px
;
}
.preview-image-wrapper
{
text-align
:
center
;
}
.preview-image
{
max-width
:
100%
;
max-height
:
70vh
;
object-fit
:
contain
;
}
.preview-pdf
{
width
:
100%
;
height
:
70vh
;
}
.preview-unsupported
{
text-align
:
center
;
padding
:
40px
;
}
.preview-unsupported
p
{
margin
:
16px
0
;
color
:
#909399
;
...
...
@@ -1180,14 +1055,12 @@ defineExpose({
.custom-upload-wrapper
{
width
:
100%
;
}
.custom-file-list
{
margin-top
:
12px
;
border
:
1px
solid
#dcdfe6
;
border-radius
:
4px
;
background-color
:
#fff
;
}
.file-item
{
display
:
flex
;
justify-content
:
space-between
;
...
...
@@ -1195,11 +1068,9 @@ defineExpose({
padding
:
8px
12px
;
border-bottom
:
1px
solid
#ebeef5
;
}
.file-item
:last-child
{
border-bottom
:
none
;
}
.file-name
{
flex
:
1
;
font-size
:
14px
;
...
...
@@ -1209,7 +1080,6 @@ defineExpose({
white-space
:
nowrap
;
margin-right
:
16px
;
}
.file-actions
{
display
:
flex
;
gap
:
12px
;
...
...
@@ -1217,11 +1087,9 @@ defineExpose({
.formBox
{
box-sizing
:
border-box
;
}
.search-form-item
{
margin-bottom
:
20px
;
}
.iconStyle
{
color
:
#409eff
;
}
...
...
src/views/financialCenter/financialBilling.vue
View file @
a0e22287
...
...
@@ -434,9 +434,9 @@ const searchConfig = ref([
}
},
{
type
:
'
date
range'
,
type
:
'
month
range'
,
prop
:
'payoutDate'
,
label
:
'出账
日
(估)'
,
label
:
'出账
月
(估)'
,
startPlaceholder
:
'开始时间'
,
endPlaceholder
:
'结束时间'
},
...
...
@@ -712,7 +712,7 @@ const confirmRateExchange = async () => {
rateExchangeFlag
.
value
=
false
loadTableData
()
}
catch
(
error
)
{
ElMessage
.
success
(
'结算汇率修改失败'
)
ElMessage
.
error
(
'结算汇率修改失败'
)
rateExchangeFlag
.
value
=
true
if
(
error
.
message
&&
error
.
message
.
includes
(
'Validation'
))
{
ElMessage
.
error
(
'必填项不能为空'
)
...
...
@@ -1099,12 +1099,20 @@ const addCheckRecordConfig = [
},
{
type
:
'
date
'
,
type
:
'
month
'
,
prop
:
'payoutDate'
,
label
:
'出账日期'
,
label
:
'出账月(估)'
,
placeholder
:
'请选择'
,
maxDate
:
'today'
,
rules
:
[{
required
:
true
,
message
:
'出账月(估)必填'
,
trigger
:
'blur'
}]
},
{
type
:
'month'
,
prop
:
'actualPayoutDate'
,
label
:
'出账月(实)'
,
placeholder
:
'请选择'
,
maxDate
:
'today'
,
rules
:
[{
required
:
true
,
message
:
'出账
日期
必填'
,
trigger
:
'blur'
}]
rules
:
[{
required
:
true
,
message
:
'出账
月(实)
必填'
,
trigger
:
'blur'
}]
},
// {
// type: 'input',
...
...
src/views/financialCenter/payables.vue
View file @
a0e22287
...
...
@@ -156,7 +156,7 @@
/>
<el-table-column
fixed=
"right"
label=
"操作"
min-width=
"120"
>
<
template
#
default=
"{ row }"
>
<el-popover
placement=
"right"
:width=
"200"
trigger=
"click"
>
<el-popover
placement=
"right"
:width=
"200"
trigger=
"click"
v-if=
"row.type == '1'"
>
<template
#
reference
>
<el-icon>
<MoreFilled
/>
...
...
@@ -797,16 +797,16 @@ const updatePayRecordFormConfig = [
prop
:
'fortunePeriod'
,
label
:
'佣金期数'
,
inputType
:
'decimal'
,
visible
:
formData
=>
formData
.
fortuneBizType
===
'R'
,
rules
:
[{
pattern
:
/^
\d
+$/
,
message
:
'只能输入正整数'
,
trigger
:
'blur'
}]
visible
:
formData
=>
formData
.
fortuneBizType
===
'R'
//
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type
:
'input'
,
prop
:
'fortuneTotalPeriod'
,
label
:
'总期数'
,
inputType
:
'decimal'
,
visible
:
formData
=>
formData
.
fortuneBizType
===
'R'
,
rules
:
[{
pattern
:
/^
\d
+$/
,
message
:
'只能输入正整数'
,
trigger
:
'blur'
}]
visible
:
formData
=>
formData
.
fortuneBizType
===
'R'
//
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type
:
'select'
,
...
...
@@ -1333,7 +1333,13 @@ const handleConfirmAddPayRecord = async () => {
const
handleConfirmUpdatePayRecord
=
async
()
=>
{
if
(
selectedRow
.
value
.
type
==
'1'
)
{
try
{
const
formData
=
updatePayRecordFormRef
.
value
.
getFormData
()
const
formData
=
await
updatePayRecordFormRef
.
value
.
validate
()
console
.
log
(
'===================================='
)
console
.
log
(
'formData'
,
formData
)
console
.
log
(
'===================================='
)
if
(
!
formData
)
{
return
}
const
params
=
{
...
formData
,
expectedFortuneBizId
:
selectedRow
.
value
.
expectedFortuneBizId
...
...
@@ -1346,7 +1352,10 @@ const handleConfirmUpdatePayRecord = async () => {
loadPayRecordTableData
(
selectedRow
.
value
.
expectedFortuneBizId
)
expectedFortuneListData
()
}
catch
(
error
)
{
ElMessage
.
error
(
error
.
message
)
if
(
error
.
message
&&
error
.
message
.
includes
(
'Validation'
))
{
ElMessage
.
error
(
'必填项不能为空'
)
}
ElMessage
.
error
(
'更新失败'
)
}
}
}
...
...
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