Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
聂康
cusc-realname-private-h5-master
Commits
8f7acb3f
Commit
8f7acb3f
authored
Jun 17, 2025
by
kang.nie@inzymeits.com
Browse files
初始化代码
parent
2d7d3f82
Pipeline
#3104
failed with stages
in 0 seconds
Changes
274
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
src/components/Card.vue
0 → 100644
View file @
8f7acb3f
<
template
>
<div
class=
"rnr-card"
>
<slot
/>
</div>
</
template
>
<
script
>
export
default
{
name
:
'
Card
'
}
</
script
>
<
style
lang=
"scss"
scoped
>
.rnr-card
{
background
:
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
16px
;
box-sizing
:
border-box
;
margin
:
0
24px
24px
;
padding
:
0px
32px
0
;
}
</
style
>
src/components/Empty.vue
0 → 100644
View file @
8f7acb3f
<
template
>
<router-view
/>
</
template
>
<
script
>
export
default
{
name
:
'
EmptyRouter
'
}
</
script
>
src/components/EnterpriseCert.vue
0 → 100644
View file @
8f7acb3f
<
template
>
<div
class=
"page-rnr-main-card"
>
<ErrorWrap
:error-message=
"inputError.companyName"
:error=
"!!inputError.companyName"
>
<van-field
id=
"companyName"
v-model=
"formData.companyName"
:clearable=
"true"
:required=
"true"
:border=
"true"
clear-trigger=
"always"
label=
"名称"
placeholder=
"请输入"
input-align=
"right"
@
blur=
"validateField(['companyName'])"
/>
</ErrorWrap>
<!-- 企业性质 -->
<van-field
id=
"companyType"
:value=
"optionLabel(formData.companyType, COMPANY_TYPE)"
:clearable=
"true"
is-link
readonly
label=
"性质"
placeholder=
"请选择"
input-align=
"right"
@
click=
"$set(showDialog, 'companyType', true)"
/>
<van-popup
v-model=
"showDialog.companyType"
round
position=
"bottom"
>
<van-picker
:columns=
"COMPANY_TYPE"
show-toolbar
value-key=
"name"
@
cancel=
"validateField(['companyType'], $set(showDialog, 'companyType', false))"
@
confirm=
"(
{ value }) => handleChange(value, 'companyType')"
/>
</van-popup>
<!-- 行业类型 -->
<van-field
id=
"industryType"
:value=
"optionLabel(formData.industryType, INDUSTRY_TYPE)"
is-link
readonly
label=
"行业类型"
placeholder=
"请选择"
input-align=
"right"
@
click=
"$set(showDialog, 'industryType', true)"
/>
<van-popup
v-model=
"showDialog.industryType"
round
position=
"bottom"
>
<van-picker
:columns=
"INDUSTRY_TYPE"
show-toolbar
value-key=
"name"
@
cancel=
"validateField(['industryType'], $set(showDialog, 'industryType', false))"
@
confirm=
"(
{ value }) => handleChange(value, 'industryType')"
/>
</van-popup>
<!-- 证件类型 -->
<van-field
id=
"companyCertType"
:value=
"optionLabel(formData.companyCertType, COMPANY_CERT_TYPE)"
:required=
"true"
is-link
readonly
label=
"证件类型"
placeholder=
"请选择"
input-align=
"right"
@
click=
"$set(showDialog, 'companyCertType', true)"
/>
<van-popup
v-model=
"showDialog.companyCertType"
round
position=
"bottom"
>
<van-picker
:columns=
"COMPANY_CERT_TYPE"
show-toolbar
value-key=
"name"
@
cancel=
"validateField(['companyCertType'], $set(showDialog, 'companyCertType', false))"
@
confirm=
"(
{ value }) => handleChange(value, 'companyCertType')"
/>
</van-popup>
<!-- 证件号码 -->
<ErrorWrap
:error-message=
"inputError.companyCertNumber"
:error=
"!!inputError.companyCertNumber"
>
<van-field
id=
"companyCertNumber"
v-model=
"formData.companyCertNumber"
:clearable=
"true"
:required=
"true"
clear-trigger=
"always"
label=
"证件号码"
placeholder=
"请输入"
input-align=
"right"
@
blur=
"validateField(['companyCertNumber'])"
/>
</ErrorWrap>
<!-- 证件地址 -->
<ErrorWrap
:error-message=
"inputError.companyCertAddress"
:error=
"!!inputError.companyCertAddress"
>
<van-field
id=
"companyCertAddress"
v-model=
"formData.companyCertAddress"
:clearable=
"true"
:required=
"true"
clear-trigger=
"always"
label=
"证件地址"
placeholder=
"请输入"
input-align=
"right"
@
blur=
"validateField(['companyCertAddress'])"
/>
</ErrorWrap>
<!-- 通讯地址 -->
<ErrorWrap
:error-message=
"inputError.companyContactAddress"
:error=
"!!inputError.companyContactAddress"
>
<van-field
id=
"companyContactAddress"
v-model=
"formData.companyContactAddress"
:clearable=
"true"
:border=
"false"
:required=
"true"
clear-trigger=
"always"
label=
"通讯地址"
placeholder=
"请输入"
input-align=
"right"
@
blur=
"validateField(['companyContactAddress'])"
/>
</ErrorWrap>
</div>
</
template
>
<
script
>
import
ErrorWrap
from
'
./ErrorWrap
'
import
{
validate
}
from
'
@/utils/validate
'
export
default
{
name
:
'
EnterpriseCert
'
,
components
:
{
ErrorWrap
},
data
()
{
const
{
COMPANY_TYPE
,
INDUSTRY_TYPE
,
COMPANY_CERT_TYPE
}
=
this
.
$store
.
getters
.
dict
return
{
COMPANY_TYPE
,
INDUSTRY_TYPE
,
COMPANY_CERT_TYPE
,
inputError
:
{},
formData
:
{
companyName
:
''
,
companyType
:
''
,
industryType
:
''
,
companyCertType
:
'
0
'
,
companyCertNumber
:
''
,
companyCertAddress
:
''
,
companyContactAddress
:
''
},
// 弹框的展示
showDialog
:
{}
}
},
methods
:
{
/**
* 获取option的label
* @param value 当前选中的值
* @param options 源数据
*/
optionLabel
(
value
,
options
)
{
try
{
return
options
.
find
(
option
=>
option
.
value
===
value
).
name
}
catch
(
error
)
{
return
''
}
},
/**
* 选项变化方法
* @param value 当前选中的值
* @param field 当前字段
*/
handleChange
(
value
,
field
)
{
// 开始赋值
this
.
formData
[
field
]
=
value
// 关闭弹框
this
.
showDialog
[
field
]
=
false
},
/**
* 校验字段
*/
validateField
(
rules
)
{
// 当前所有规则
rules
.
forEach
(
rule
=>
{
this
.
$set
(
this
.
inputError
,
rule
,
''
)
})
// 校验错误的字段和信息
const
[
failField
,
failMessage
]
=
validate
(
this
.
formData
,
rules
)
// 记录错误信息
this
.
$set
(
this
.
inputError
,
failField
,
failMessage
)
},
/**
* 校验所有字段
*/
validate
()
{
// 当前需要校验的规则
const
rules
=
[
'
companyName
'
,
'
companyCertNumber
'
,
'
companyCertAddress
'
,
'
companyContactAddress
'
]
// 当前所有规则
rules
.
forEach
(
rule
=>
{
this
.
$set
(
this
.
inputError
,
rule
,
''
)
})
// 校验必填项
const
[
failField
,
failMessage
]
=
validate
(
this
.
formData
,
rules
)
// 标记当前输入错误的
this
.
$set
(
this
.
inputError
,
failField
,
failMessage
)
// 如果当前有错误信息,则直接报错
if
(
failMessage
)
{
throw
new
Error
(
failMessage
)
}
return
this
.
formData
}
}
}
</
script
>
src/components/ErrorWrap.vue
0 → 100644
View file @
8f7acb3f
<
template
>
<div
class=
"page-rnr-field-wrap"
>
<slot
/>
<div
class=
"page-rnr-field-error"
>
<div
v-if=
"error"
class=
"page-rnr-field-error-message"
>
{{
errorMessage
}}
</div>
</div>
</div>
</
template
>
<
script
>
export
default
{
name
:
'
ErrorWrap
'
,
props
:
{
// 是否需要展示错误
error
:
{
type
:
Boolean
,
default
:
false
},
// 错误展示值
errorMessage
:
{
type
:
String
,
default
:
'
请输入内容
'
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.page-rnr-field-wrap
{
.page-rnr-field-error
{
.page-rnr-field-error-message
{
color
:
#FF4C4F
;
font-size
:
20px
;
font-weight
:
400
;
height
:
50px
;
padding-top
:
12px
;
position
:
relative
;
&
:after
{
position
:
absolute
;
box-sizing
:
border-box
;
content
:
' '
;
pointer-events
:
none
;
right
:
0
;
top
:
-1px
;
left
:
0
;
border-bottom
:
1
PX
solid
#ee0a24
;
transform
:
scaleY
(
0
.5
);
}
}
}
}
</
style
>
src/components/FormRender/assets/ico_camera.png
0 → 100644
View file @
8f7acb3f
2.55 KB
src/components/FormRender/assets/ico_idcard_back.png
0 → 100644
View file @
8f7acb3f
11.8 KB
src/components/FormRender/assets/ico_idcard_front.png
0 → 100644
View file @
8f7acb3f
27 KB
src/components/FormRender/assets/ico_pic.png
0 → 100644
View file @
8f7acb3f
17.3 KB
src/components/FormRender/assets/ico_pic_large.png
0 → 100644
View file @
8f7acb3f
39.4 KB
src/components/FormRender/bridge-upload.vue
0 → 100644
View file @
8f7acb3f
<
template
>
<div
class=
"component-bridge-upload"
>
<van-uploader
ref=
"vanUploadFileRef"
v-model=
"images"
:max-count=
"maxCount"
:max-size=
"isOverSize"
:after-read=
"handleAfterRead"
:readonly=
"isInApp"
@
click-upload=
"handleUploadByBridge"
@
oversize=
"handleOverSize"
@
delete=
"handleDeleteFile"
>
<slot
/>
</van-uploader>
<van-action-sheet
v-model=
"showUploadSheet"
:actions=
"actions"
cancel-text=
"取消"
@
select=
"handleSelect"
/>
</div>
</
template
>
<
script
>
import
CompressImg
from
'
@/utils/CompressImg
'
import
BlobUtil
from
'
@/utils/CompressImg/blobutil
'
import
bridge
,
{
isInApp
,
CAMERA
,
PHOTO
}
from
'
@/utils/bridge
'
import
{
Toast
}
from
'
vant
'
export
default
{
name
:
'
BridgeUploadSelect
'
,
props
:
{
maxCount
:
{
type
:
Number
,
default
:
5
},
maxSize
:
{
type
:
Number
,
default
:
1000
*
1024
},
value
:
{
type
:
Array
,
default
:
()
=>
([])
},
security
:
{
type
:
Boolean
,
default
:
false
},
compress
:
{
type
:
Object
,
default
:
null
},
api
:
{
type
:
Function
,
default
:
()
=>
{
console
.
log
(
'
请传入接口调用api函数
'
)
}
}
},
data
()
{
return
{
showUploadSheet
:
false
,
actions
:
[
{
name
:
'
拍照
'
,
value
:
CAMERA
},
{
name
:
'
从相册选择
'
,
value
:
PHOTO
}
],
images
:
[],
isInApp
}
},
watch
:
{
value
()
{
this
.
images
=
(
this
.
value
||
[]).
filter
(
item
=>
!!
item
)
}
},
methods
:
{
/**
* 更新文件
* @param file 需要更新的文件
* @param index 需要更新的索引
*/
updateFile
(
file
,
index
=
0
)
{
// 开始上传文件
this
.
handleAfterRead
(
file
)
},
/**
* 如果通过bridge上传
*/
handleUploadByBridge
()
{
if
(
isInApp
)
{
this
.
showUploadSheet
=
true
}
},
/**
* 删除文件
* @param index 当前文件索引
*/
handleDeleteFile
(
files
,
{
index
})
{
// 将文件id保存在照片中
const
newValue
=
[...
this
.
value
]
// 从数组中删除此项
newValue
.
splice
(
index
,
1
)
// 开始双向绑定
this
.
$emit
(
'
input
'
,
newValue
)
// 触发change方法
this
.
$emit
(
'
change
'
,
newValue
)
},
/**
* 读取文件后,开始上传文件
* @param file 当前选中的文件
*/
async
handleAfterRead
(
file
)
{
// 等待上传的图片
let
uploadFile
=
file
.
file
// 如果当前需要压缩
if
(
this
.
compress
)
{
try
{
uploadFile
=
await
this
.
compressImage
(
file
.
file
,
this
.
compress
)
}
catch
(
error
)
{
console
.
log
(
'
压缩图片报错:
'
,
file
.
file
)
return
}
}
// 展示上传中状态
file
.
status
=
'
uploading
'
file
.
message
=
'
上传中...
'
try
{
// 记录文件上传的uuid
file
.
uuid
=
await
this
.
api
(
uploadFile
)
// 清空加载的提示信息
file
.
status
=
''
file
.
message
=
''
// 文件集合
const
fileList
=
[...
this
.
value
,
file
]
// 开始双向绑定
this
.
$emit
(
'
input
'
,
fileList
)
// // 触发change方法
this
.
$emit
(
'
change
'
,
fileList
)
}
catch
(
error
)
{
// 如果上传失败
file
.
status
=
'
failed
'
file
.
message
=
'
上传失败
'
console
.
error
(
error
)
}
},
/**
* 压缩图片
* @param file 当前等待上传的文件
* @param options 压缩选项
*/
compressImage
(
file
,
options
)
{
console
.
log
(
'
正在压缩图片。。。
'
)
return
new
Promise
((
resolve
,
reject
)
=>
{
CompressImg
(
file
,
{
size
:
1000
,
quality
:
80
,
...
options
,
cb
:
(
dataURL
)
=>
{
const
abData
=
BlobUtil
.
base64ToArrayBuffer
(
dataURL
)
console
.
log
(
'
压缩后图片:
'
,
abData
.
length
,
'
, 限制大小:
'
+
this
.
maxSize
)
if
(
abData
.
length
>
this
.
maxSize
)
{
this
.
handleOverSize
()
reject
(
false
)
}
else
{
// 设置上传文件为压缩后的文件
const
compressedFile
=
BlobUtil
.
Blob
([
abData
],
{
type
:
'
image/jpeg
'
})
resolve
(
new
File
([
compressedFile
],
file
.
name
||
'
压缩后的文件.jpeg
'
))
}
}
})
})
},
/**
* 选中方法
* @param option 当前选中方法
*/
handleSelect
(
option
)
{
switch
(
option
.
value
)
{
case
CAMERA
:
bridge
.
getCamera
((
file
)
=>
{
// 上传文件
this
.
handleAfterRead
(
file
)
})
break
case
PHOTO
:
bridge
.
getPhoto
((
file
)
=>
{
// 上传文件
this
.
handleAfterRead
(
file
)
})
break
}
// 关闭弹框
this
.
showUploadSheet
=
false
},
/**
* 图片超过最大限制
*/
handleOverSize
()
{
if
(
this
.
compress
)
{
return
true
}
Toast
(
'
选择的图片超过最大限制,请重新选择
'
)
},
/**
* 是否超过尺寸限制
*/
isOverSize
(
file
)
{
// 如果当前需要压缩,则不判断
if
(
this
.
compress
)
{
return
false
}
return
file
.
size
>=
this
.
maxSize
}
}
}
</
script
>
src/components/FormRender/date-picker.vue
0 → 100644
View file @
8f7acb3f
<
script
>
import
ValidateMixin
from
'
./mixins/validate
'
import
dayjs
from
'
dayjs
'
export
default
{
name
:
'
RnrDatePicker
'
,
mixins
:
[
ValidateMixin
],
props
:
{
label
:
{
type
:
String
,
default
:
''
},
value
:
{
type
:
[
String
,
Number
],
default
:
''
},
attrs
:
{
type
:
Object
,
default
:
()
=>
({})
},
rules
:
{
type
:
Array
,
default
:
()
=>
([])
},
minDate
:
{
type
:
Object
,
default
:
new
Date
(
1900
,
1
,
1
)
},
maxDate
:
{
type
:
Object
,
default
:
new
Date
(
2999
,
12
,
30
)
},
validateCallback
:
{
type
:
Function
,
default
:
null
},
valueDisplay
:
{
type
:
Function
,
default
:
date
=>
{
// 如果当前没有传入日期
if
(
!
date
)
{
return
null
}
return
dayjs
(
date
).
isValid
()
?
dayjs
(
date
).
format
(
'
YYYY-MM-DD
'
)
:
date
}
},
valueFormatter
:
{
type
:
Function
,
default
:
date
=>
{
return
dayjs
(
date
).
valueOf
()
}
}
},
data
()
{
return
{
visible
:
false
}
},
methods
:
{
/**
* 数据改变方法
*/
handleConfirm
(
value
)
{
// 开始双向绑定数据
this
.
$emit
(
'
input
'
,
this
.
valueFormatter
(
value
))
this
.
$emit
(
'
change
'
,
this
.
valueFormatter
(
value
))
// 关闭弹出框
this
.
visible
=
false
// 等页面渲染完成后开始校验数据
this
.
$nextTick
(
this
.
validate
)
},
/**
* 点击关闭方法
*/
handleCancel
()
{
// 开始校验数据
this
.
validate
()
// 通知父组件关闭事件
this
.
$emit
(
'
close
'
)
// 关闭数据
this
.
visible
=
false
}
},
render
()
{
return
(
<
div
class
=
'
date-picker
'
>
<
van
-
field
value
=
{
this
.
valueDisplay
(
this
.
value
)}
clearable
=
{
true
}
required
=
{
this
.
required
}
clear
-
trigger
=
'
always
'
clickable
is
-
link
label
=
{
this
.
label
}
readonly
placeholder
=
'
请选择
'
input
-
align
=
'
right
'
onClick
=
{()
=>
(
this
.
visible
=
true
)}
/
>
<
van
-
popup
value
=
{
this
.
visible
}
onInput
=
{
visible
=>
(
this
.
visible
=
visible
)}
round
position
=
'
bottom
'
>
<
van
-
datetime
-
picker
minDate
=
{
this
.
minDate
}
maxDate
=
{
this
.
maxDate
}
type
=
'
date
'
title
=
'
选择年月日
'
onCancel
=
{
this
.
handleCancel
}
onConfirm
=
{
this
.
handleConfirm
}
/
>
<
/van-popup
>
<
/div
>
)
}
}
</
script
>
src/components/FormRender/file.vue
0 → 100644
View file @
8f7acb3f
<
script
>
import
BridgeUpload
from
'
./bridge-upload
'
import
ValidateMixin
from
'
./mixins/validate
'
export
default
{
name
:
'
RnrFile
'
,
components
:
{
BridgeUpload
},
mixins
:
[
ValidateMixin
],
props
:
{
label
:
{
type
:
String
,
default
:
''
},
value
:
{
type
:
Array
,
default
:
()
=>
([])
},
attrs
:
{
type
:
Object
,
default
:
()
=>
({})
},
rules
:
{
type
:
Array
,
default
:
()
=>
([])
},
size
:
{
type
:
String
,
default
:
'
small
'
},
maxCount
:
{
type
:
Number
,
default
:
1
},
compress
:
{
type
:
Object
,
default
:
null
},
api
:
{
type
:
Function
,
default
:
()
=>
{}
},
validateCallback
:
{
type
:
Function
,
default
:
null
}
},
methods
:
{
/**
* 当图片发生改变的方法
*/
handleImageChange
(
value
)
{
this
.
$emit
(
'
input
'
,
value
)
// 界面渲染完成后开始校验数据
this
.
$nextTick
(
this
.
validate
)
}
},
render
()
{
return
this
.
label
?
(
<
van
-
cell
required
=
{
this
.
required
}
title
=
{
this
.
label
}
class
=
{
`van-field rnr-file
${
this
.
size
===
'
small
'
?
'
wrapper-small
'
:
'
wrapper-large
'
}
`
}
scopedSlots
=
{{
label
:
()
=>
{
return
(
<
bridge
-
upload
compress
=
{
this
.
compress
}
api
=
{
this
.
api
}
maxCount
=
{
this
.
maxCount
}
value
=
{
this
.
value
}
onInput
=
{
this
.
handleImageChange
}
>
<
div
class
=
'
upload-wrapper
'
>
<
div
class
=
'
icon-upload-camera
'
/>
<
/div
>
<
/bridge-upload
>
)
}
}}
/
>
)
:
(
<
div
class
=
{
`rnr-file
${
this
.
size
===
'
small
'
?
'
wrapper-small
'
:
'
wrapper-large
'
}
`
}
>
<
bridge
-
upload
compress
=
{
this
.
compress
}
api
=
{
this
.
api
}
maxCount
=
{
this
.
maxCount
}
value
=
{
this
.
value
}
onInput
=
{
this
.
handleImageChange
}
>
<
div
class
=
'
upload-wrapper
'
>
<
div
class
=
'
icon-upload-camera
'
/>
<
/div
>
<
/bridge-upload
>
<
/div
>
)
}
}
</
script
>
<
style
lang=
"scss"
>
.rnr-file
{
.upload-wrapper
{
background-color
:
#F7F8FA
;
background-repeat
:
no-repeat
;
background-position
:
center
;
position
:
relative
;
}
.icon-upload-camera
{
background-image
:
url(./assets/ico_camera.png)
;
background-size
:
100%
100%
;
position
:
absolute
;
width
:
80px
;
height
:
80px
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
}
.van-uploader__preview-delete
{
border-radius
:
0
0
0
22px
;
width
:
32px
;
height
:
32px
;
.van-uploader__preview-delete-icon
{
font-size
:
32px
;
top
:
0
;
right
:
-2px
;
}
}
&
.wrapper-small
{
.upload-wrapper
{
background-image
:
url(./assets/ico_pic.png)
;
background-size
:
196px
196px
;
border-radius
:
8px
;
width
:
196px
;
height
:
196px
;
}
.van-uploader__preview
{
margin
:
0
24px
16px
0
;
.van-uploader__preview-image
{
width
:
196px
;
height
:
196px
;
}
.van-uploader__preview-delete
{
background-color
:
rgba
(
0
,
0
,
0
,
.4
);
border-radius
:
20px
;
display
:
flex
;
width
:
40px
;
height
:
40px
;
.van-uploader__preview-delete-icon
{
font-size
:
60px
;
top
:
-10px
;
right
:
-10px
;
}
}
&
:nth-child
(
3
)
{
margin-right
:
0px
;
}
}
}
&
.wrapper-large
{
.van-uploader__input-wrapper
{
margin
:
0
auto
;
.upload-wrapper
{
background-image
:
url(./assets/ico_pic_large.png)
;
background-size
:
100%
100%
;
width
:
638px
;
height
:
322px
;
}
}
.van-uploader__preview
{
width
:
638px
;
height
:
322px
;
margin
:
0
auto
;
overflow
:
hidden
;
margin-bottom
:
64px
;
.van-uploader__preview-image
{
width
:
100%
!
important
;
height
:
100%
!
important
;
}
.van-uploader__preview-delete
{
background-color
:
rgba
(
0
,
0
,
0
,
.4
);
border-radius
:
20px
;
display
:
flex
;
width
:
40px
;
height
:
40px
;
.van-uploader__preview-delete-icon
{
font-size
:
60px
;
top
:
-10px
;
right
:
-10px
;
}
}
}
}
}
</
style
>
src/components/FormRender/form-item.vue
0 → 100644
View file @
8f7acb3f
<
script
>
const
RESULT
=
{
SUCCESS
:
1
,
FAIL
:
2
}
export
default
{
name
:
'
RnrFormItem
'
,
data
()
{
return
{
result
:
RESULT
.
SUCCESS
,
message
:
''
}
},
methods
:
{
/**
* 表单校验结果
*/
formValidateResult
(
result
,
message
=
''
)
{
this
.
result
=
result
?
RESULT
.
SUCCESS
:
RESULT
.
FAIL
this
.
message
=
message
}
},
render
()
{
return
(
<
div
class
=
{[
'
validate-result-wrapper
'
,
this
.
result
===
RESULT
.
FAIL
?
'
status-fail
'
:
''
].
join
(
'
'
)}
>
{
this
.
$scopedSlots
.
default
(
this
.
formValidateResult
.
bind
(
this
))}
{
this
.
result
===
RESULT
.
SUCCESS
?
null
:
<
div
class
=
'
error-message
'
>
{
this
.
message
}
<
/div>
}
<
/div
>
)
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.validate-result-wrapper
{
.error-message
{
color
:
#FF4C4F
;
font-size
:
20px
;
font-weight
:
400
;
height
:
40px
;
padding-top
:
12px
;
position
:
relative
;
&
:after
{
position
:
absolute
;
box-sizing
:
border-box
;
content
:
' '
;
pointer-events
:
none
;
right
:
0
;
top
:
-1px
;
left
:
0
;
border-bottom
:
1
PX
solid
#ee0a24
;
transform
:
scaleY
(
0
.5
);
}
}
&
.status-fail
{
&
:after
{
display
:
none
;
}
}
}
</
style
>
src/components/FormRender/idcard.vue
0 → 100644
View file @
8f7acb3f
<
script
>
import
BridgeUpload
from
'
./bridge-upload
'
import
ValidateMixin
from
'
./mixins/validate
'
export
default
{
name
:
'
RnrIdcard
'
,
components
:
{
BridgeUpload
},
mixins
:
[
ValidateMixin
],
props
:
{
label
:
{
type
:
String
,
default
:
''
},
value
:
{
type
:
Array
,
default
:
()
=>
([])
},
attrs
:
{
type
:
Object
,
default
:
()
=>
({})
},
rules
:
{
type
:
Array
,
default
:
()
=>
([])
},
compress
:
{
type
:
Object
,
default
:
null
},
api
:
{
type
:
Function
,
default
:
()
=>
{}
},
validateCallback
:
{
type
:
Function
,
default
:
null
}
},
methods
:
{
/**
* 当图片发生改变的方法
*/
handleImageChange
(
index
,
val
)
{
// 重新赋值
const
value
=
[...
this
.
value
]
value
[
index
]
=
val
[
0
]
// 开始双向绑定
this
.
$emit
(
'
input
'
,
value
)
// 触发改变方法
this
.
$emit
(
'
change
'
,
value
)
// 等界面渲染完成后
this
.
$nextTick
(
this
.
validate
)
}
},
render
()
{
return
(
<
div
class
=
'
rnr-idcard
'
>
<
div
class
=
'
idcard-image-wrapper
'
>
<
bridge
-
upload
compress
=
{
this
.
compress
}
value
=
{
this
.
value
[
0
]
?
[
this
.
value
[
0
]]
:
[]}
api
=
{
this
.
api
}
maxCount
=
{
1
}
onInput
=
{
val
=>
this
.
handleImageChange
(
0
,
val
)}
>
<
div
class
=
'
upload-wrapper idcard-front
'
>
<
div
class
=
'
icon-upload-camera
'
/>
<
/div
>
<
/bridge-upload
>
<
div
class
=
'
idcard-label
'
>
上传身份证的人像面
<
/div
>
<
/div
>
<
div
class
=
'
idcard-image-wrapper
'
>
<
bridge
-
upload
compress
=
{
this
.
compress
}
value
=
{
this
.
value
[
1
]
?
[
this
.
value
[
1
]]
:
[]}
api
=
{
this
.
api
}
maxCount
=
{
1
}
onInput
=
{
val
=>
this
.
handleImageChange
(
1
,
val
)}
>
<
div
class
=
'
upload-wrapper idcard-back
'
>
<
div
class
=
'
icon-upload-camera
'
/>
<
/div
>
<
/bridge-upload
>
<
div
class
=
'
idcard-label
'
>
上传身份证的国徽面
<
/div
>
<
/div
>
<
/div
>
)
}
}
</
script
>
<
style
lang=
"scss"
>
.rnr-idcard
{
.idcard-image-wrapper
{
margin-top
:
64px
;
.component-bridge-upload
{
display
:
flex
;
justify-content
:
center
;
.upload-wrapper
{
background-color
:
#F7F8FA
;
background-repeat
:
no-repeat
;
background-position
:
center
;
background-size
:
100%
100%
;
width
:
516px
;
height
:
322px
;
position
:
relative
;
.icon-upload-camera
{
background-image
:
url(./assets/ico_camera.png)
;
background-size
:
100%
100%
;
position
:
absolute
;
width
:
80px
;
height
:
80px
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
}
&
.idcard-front
{
background-image
:
url(./assets/ico_idcard_front.png)
;
}
&
.idcard-back
{
background-image
:
url(./assets/ico_idcard_back.png)
;
}
}
.van-uploader__input-wrapper
{
margin
:
0
auto
;
}
.van-uploader__preview
{
width
:
516px
;
height
:
322px
;
margin
:
0
auto
!
important
;
overflow
:
hidden
;
.van-uploader__preview-image
{
width
:
100%
!
important
;
height
:
100%
!
important
;
}
.van-uploader__preview-delete
{
background-color
:
rgba
(
0
,
0
,
0
,
.4
);
border-radius
:
20px
;
display
:
flex
;
width
:
40px
;
height
:
40px
;
.van-uploader__preview-delete-icon
{
font-size
:
60px
;
top
:
-10px
;
right
:
-10px
;
}
}
&
:nth-child
(
3
)
{
margin-right
:
0px
;
}
}
}
.idcard-label
{
color
:
rgba
(
36
,
36
,
36
,
0
.4
);
font-size
:
24px
;
font-weight
:
400
;
line-height
:
33px
;
margin-top
:
16px
;
text-align
:
center
;
}
&
:first-child
{
margin-top
:
0
;
}
}
}
</
style
>
src/components/FormRender/index.vue
0 → 100644
View file @
8f7acb3f
<
template
>
<form
class=
"rnr-form"
>
<rnr-form-item
v-for=
"(val, key) in schema"
:key=
"key"
:class=
"val.border === 'none' ? '' : 'van-hairline--bottom'"
>
<template
#default
="
callback
"
>
<slot
v-if=
"val.slot"
:name=
"val.slot"
/>
<component
v-else
:value=
"formData[key]"
:is=
"val.type"
:ref=
"`$
{key}Ref`"
:label="val.label"
:attrs="val.attrs"
:options="val.options"
:rules="val.rules"
:max-count="val.maxCount"
:validate-callback="callback"
:min-date="val.minDate"
:max-date="val.maxDate"
:compress="val.compress"
:size="val.size"
:api="val.api"
v-on="schema.on"
@input="val => handleComponentInput(key, val)"
/>
</
template
>
</rnr-form-item>
</form>
</template>
<
script
>
import
RnrFormItem
from
'
./form-item
'
import
RnrPicker
from
'
./picker
'
import
RnrFile
from
'
./file
'
import
RnrSelect
from
'
./select
'
import
RnrInput
from
'
./input
'
import
RnrIdcard
from
'
./idcard
'
import
RnrDatePicker
from
'
./date-picker
'
export
default
{
name
:
'
FormRender
'
,
components
:
{
RnrFormItem
,
RnrDatePicker
,
RnrPicker
,
RnrSelect
,
RnrInput
,
RnrIdcard
,
RnrFile
},
props
:
{
schema
:
{
type
:
Object
,
default
:
()
=>
({})
},
value
:
{
type
:
Object
,
default
:
()
=>
({})
}
},
data
()
{
return
{
formData
:
Object
.
keys
(
this
.
schema
).
reduce
((
memo
,
key
)
=>
{
memo
[
key
]
=
this
.
schema
[
key
].
default
||
this
.
value
[
key
]
return
memo
},
{
...(
this
.
value
||
{})
})
}
},
watch
:
{
/**
* 监听输入值的变化
*/
value
:
{
handler
()
{
// 表格填写的数据
const
formData
=
this
.
formData
// 如果当前值被清空了,则需要清空所有值
if
(
!
this
.
value
)
{
Object
.
keys
(
formData
).
forEach
(
key
=>
{
formData
[
key
]
=
undefined
})
}
else
{
Object
.
keys
(
this
.
value
).
forEach
(
key
=>
{
this
.
$set
(
formData
,
key
,
this
.
value
[
key
])
})
}
},
deep
:
true
}
},
methods
:
{
/**
* 校验所有参数
*/
validate
()
{
// 校验结果
let
result
=
true
// 校验所有输入项
Object
.
keys
(
this
.
schema
).
forEach
(
key
=>
{
// 当前引用
const
ref
=
this
.
$refs
[
`
${
key
}
Ref`
]
// 开始校验
if
(
ref
instanceof
Array
)
{
result
=
result
&&
ref
[
0
].
validate
()
}
else
if
(
ref
instanceof
Object
)
{
result
=
result
&&
ref
.
validate
()
}
})
// 如果校验结果是失败的
if
(
!
result
)
{
throw
new
Error
(
'
数据校验失败
'
)
}
// 返回校验结果
return
result
},
/**
* 组件输入方法
* @param value 当前输入值
*/
handleComponentInput
(
key
,
value
)
{
// 将值塞到表单中
this
.
$set
(
this
.
formData
,
key
,
value
)
// 浅拷贝一下对象
const
formDataCopy
=
{
...
this
.
formData
}
// 开始双向绑定
this
.
$emit
(
'
input
'
,
formDataCopy
)
// 当前改变的key也带出去
this
.
$emit
(
'
change
'
,
formDataCopy
,
key
)
}
}
}
</
script
>
<
style
lang=
"scss"
>
.rnr-form
{
.van-field
{
background-color
:
transparent
;
padding
:
44px
0
;
position
:
relative
;
&
.van-cell
{
background-color
:
transparent
;
padding
:
0
;
&
.van-field
{
background-color
:
transparent
;
padding
:
44px
0
;
position
:
relative
;
.van-field__label
{
color
:
#242424
;
font-size
:
32px
;
font-weight
:
400
;
height
:
45px
;
}
.van-cell__value
{
height
:
45px
;
.van-field__control
{
color
:
#242424
;
font-size
:
32px
;
}
&
.van-field__value
{
position
:
relative
;
.van-field__error-message
{
position
:
absolute
;
left
:
-220px
;
bottom
:
-90px
;
}
}
}
}
&
.van-cell--required
{
.van-cell__title
{
>
span
{
padding-left
:
20px
;
}
}
&
:before
{
left
:
0px
;
font-size
:
16px
;
}
}
.van-cell__title
{
>
span
{
color
:
#242424
;
font-size
:
32px
;
font-weight
:
400
;
}
}
.van-cell__label
{
margin-top
:
36px
;
}
}
&
::after
{
position
:
absolute
;
box-sizing
:
border-box
;
content
:
' '
;
pointer-events
:
none
;
right
:
-50%
!
important
;
bottom
:
-50%
!
important
;
left
:
-50%
!
important
;
top
:
-50%
!
important
;
border-bottom
:
1px
solid
#ebedf0
;
-webkit-transform
:
scaleY
(
.5
);
transform
:
scale
(
.5
,
.5
)
!
important
;
}
}
}
</
style
>
src/components/FormRender/input.vue
0 → 100644
View file @
8f7acb3f
<
script
>
import
ValidateMixin
from
'
./mixins/validate
'
export
default
{
name
:
'
RnrInput
'
,
mixins
:
[
ValidateMixin
],
props
:
{
label
:
{
type
:
String
,
default
:
''
},
value
:
{
type
:
String
,
default
:
''
},
attrs
:
{
type
:
Object
,
default
:
()
=>
({})
},
rules
:
{
type
:
Array
,
default
:
()
=>
([])
},
validateCallback
:
{
type
:
Function
,
default
:
null
}
},
methods
:
{
/**
* 触发blur的方法
*/
handleBlur
()
{
// 开始校验
this
.
validate
()
}
},
render
()
{
return
(
<
van
-
field
value
=
{
this
.
value
}
clearable
=
{
true
}
required
=
{
this
.
required
}
clear
-
trigger
=
'
always
'
label
=
{
this
.
label
}
placeholder
=
{
`请输入
${
this
.
label
}
`
}
input
-
align
=
'
right
'
onInput
=
{
val
=>
this
.
$emit
(
'
input
'
,
val
)}
onBlur
=
{
this
.
handleBlur
}
{...(
this
.
attrs
||
{})}
/
>
)
}
}
</
script
>
src/components/FormRender/mixins/validate.js
0 → 100644
View file @
8f7acb3f
export
default
{
name
:
'
Validate
'
,
computed
:
{
/**
* 是否必填
*/
required
()
{
return
this
.
rules
?
this
.
rules
.
some
(
rule
=>
rule
.
required
)
:
false
}
},
methods
:
{
/**
* 校验方法
*/
validate
()
{
// 校验结果
let
validateResult
=
true
// 遍历所有规则
this
.
rules
&&
this
.
rules
.
forEach
(
rule
=>
{
// 如果校验成功
if
(
validateResult
)
{
// 如果当前是必填校验
if
(
rule
.
required
)
{
// 如果当前没有输入
if
(
!
this
.
value
||
this
.
value
.
length
===
0
)
{
this
.
validateCallback
(
validateResult
=
false
,
`请
${
this
.
$options
.
name
===
'
RnrInput
'
?
'
输入
'
:
'
选择
'
}${
this
.
label
}
`
)
}
}
// 如果当前是正则类型
if
(
rule
.
test
instanceof
RegExp
)
{
// 如果当前正则没匹配上
if
(
!
rule
.
test
.
test
(
this
.
value
))
{
// 如果方法没匹配上
const
message
=
rule
.
message
||
`
${
this
.
label
}${
this
.
$options
.
name
===
'
RnrInput
'
?
'
输入
'
:
'
选择
'
}
不合法,请重新
${
this
.
$options
.
name
===
'
RnrInput
'
?
'
输入
'
:
'
选择
'
}
`
this
.
validateCallback
(
validateResult
=
false
,
message
)
}
}
// 如果当前是校验方法
if
(
rule
.
validator
instanceof
Function
)
{
// 校验结果
const
result
=
rule
.
validator
(
this
.
value
)
// 如果方法没匹配上
if
(
(
typeof
result
===
'
boolean
'
&&
!
result
)
||
(
result
instanceof
Object
&&
!
result
.
result
)
)
{
const
message
=
result
.
message
||
`
${
this
.
label
}${
this
.
$options
.
name
===
'
RnrInput
'
?
'
输入
'
:
'
选择
'
}
不合法,请重新
${
this
.
$options
.
name
===
'
RnrInput
'
?
'
输入
'
:
'
选择
'
}
`
this
.
validateCallback
(
validateResult
=
false
,
message
)
}
}
}
})
// 如果校验成功,则重置校验位
if
(
validateResult
)
{
this
.
validateCallback
(
true
)
}
return
validateResult
},
/**
* 清空校验
*/
clearValidate
()
{
this
.
validateCallback
(
true
)
}
}
}
src/components/FormRender/picker.vue
0 → 100644
View file @
8f7acb3f
<
script
>
import
ValidateMixin
from
'
./mixins/validate
'
export
default
{
name
:
'
RnrPicker
'
,
mixins
:
[
ValidateMixin
],
props
:
{
label
:
{
type
:
String
,
default
:
''
},
value
:
{
type
:
[
String
,
Number
],
default
:
''
},
attrs
:
{
type
:
Object
,
default
:
()
=>
({})
},
options
:
{
type
:
Array
,
default
:
()
=>
([])
},
rules
:
{
type
:
Array
,
default
:
()
=>
([])
},
validateCallback
:
{
type
:
Function
,
default
:
null
}
},
data
()
{
return
{
visible
:
false
}
},
methods
:
{
/**
* 数据改变方法
*/
handleChange
({
value
})
{
// 开始双向绑定数据
this
.
$emit
(
'
input
'
,
value
)
this
.
$emit
(
'
change
'
,
value
)
// 关闭弹出框
this
.
visible
=
false
// 等页面渲染完成后开始校验数据
this
.
$nextTick
(
this
.
validate
)
},
/**
* 点击关闭方法
*/
handleCancel
()
{
// 开始校验数据
this
.
validate
()
// 通知父组件关闭事件
this
.
$emit
(
'
close
'
)
// 关闭数据
this
.
visible
=
false
}
},
render
()
{
// 取出当前选中的选项
const
option
=
this
.
options
.
find
(
option
=>
option
.
value
===
this
.
value
)
||
{}
return
(
<
div
class
=
'
rnr-picker
'
>
<
van
-
field
value
=
{
option
.
name
}
clearable
=
{
true
}
clear
-
trigger
=
'
always
'
clickable
is
-
link
label
=
{
this
.
label
}
required
=
{
this
.
required
}
readonly
placeholder
=
{
`请选择
${
this
.
label
}
`
}
input
-
align
=
'
right
'
{...(
this
.
attrs
||
{})}
onClick
=
{()
=>
(
this
.
visible
=
true
)}
/
>
<
van
-
action
-
sheet
value
=
{
this
.
visible
}
actions
=
{
this
.
options
}
cancel
-
text
=
'
取消
'
onInput
=
{
visible
=>
(
this
.
visible
=
visible
)}
onClose
=
{
this
.
handleCancel
}
onSelect
=
{
this
.
handleChange
}
/
>
<
/div
>
)
}
}
</
script
>
src/components/FormRender/select.vue
0 → 100644
View file @
8f7acb3f
<
script
>
import
ValidateMixin
from
'
./mixins/validate
'
export
default
{
name
:
'
RnrSelect
'
,
mixins
:
[
ValidateMixin
],
props
:
{
label
:
{
type
:
String
,
default
:
''
},
value
:
{
type
:
[
String
,
Number
],
default
:
''
},
attrs
:
{
type
:
Object
,
default
:
()
=>
({})
},
options
:
{
type
:
Array
,
default
:
()
=>
([])
},
rules
:
{
type
:
Array
,
default
:
()
=>
([])
},
validateCallback
:
{
type
:
Function
,
default
:
null
}
},
data
()
{
return
{
visible
:
false
}
},
methods
:
{
/**
* 数据改变方法
*/
handleChange
({
value
})
{
// 校验成功
this
.
$emit
(
'
change
'
,
value
)
this
.
$emit
(
'
input
'
,
value
)
// 关闭弹框
this
.
visible
=
false
// 等页面渲染完成后开始校验数据
this
.
$nextTick
(
this
.
validate
)
},
/**
* 关闭弹出框方法
*/
handleCancel
()
{
// 开始校验数据
this
.
validate
()
// 通知父组件关闭方法
this
.
$emit
(
'
close
'
)
// 隐藏picker组件
this
.
visible
=
false
}
},
render
()
{
// 取出当前选中的选项
const
option
=
this
.
options
.
find
(
option
=>
option
.
value
===
this
.
value
)
||
{}
return
(
<
div
class
=
'
rnr-select
'
>
<
van
-
field
value
=
{
option
.
name
}
clear
-
trigger
=
'
always
'
clickable
is
-
link
label
=
{
this
.
label
}
required
=
{
this
.
required
}
readonly
placeholder
=
{
`请选择
${
this
.
label
}
`
}
input
-
align
=
'
right
'
{...(
this
.
attrs
||
{})}
onClick
=
{()
=>
(
this
.
visible
=
true
)}
/
>
<
van
-
popup
value
=
{
this
.
visible
}
onInput
=
{
visible
=>
(
this
.
visible
=
visible
)}
round
position
=
'
bottom
'
>
<
van
-
picker
columns
=
{
this
.
options
}
value
-
key
=
'
name
'
title
=
{
this
.
label
}
show
-
toolbar
onConfirm
=
{
this
.
handleChange
}
onCancel
=
{
this
.
handleCancel
}
/
>
<
/van-popup
>
<
/div
>
)
}
}
</
script
>
src/components/InputActionSheet.vue
0 → 100644
View file @
8f7acb3f
<
template
>
<van-action-sheet
:value=
"visible"
:title=
"title"
@
cancel=
"$emit('update:visible', false)"
@
click-overlay=
"$emit('update:visible', false)"
>
<div
class=
"page-rnr-action-sheet"
>
<ErrorWrap
:error-message=
"errorMessage"
:error=
"!!errorMessage"
>
<van-field
v-model=
"inputValue"
:clearable=
"true"
:label=
"label"
clear-trigger=
"always"
:placeholder=
"`请输入$
{label}`"
input-align="right"
@blur="validateInput(inputValue)"
/>
</ErrorWrap>
<van-button
:disabled=
"btnDisable"
type=
"primary"
class=
"page-rnr-action-sheet-btn"
round
color=
"linear-gradient(120deg, rgba(79, 76, 251, 1) 0%, rgba(55, 111, 244, 1) 97%)"
block
@
click=
"handleConfirmInput"
>
确定
</van-button>
</div>
</van-action-sheet>
</
template
>
<
script
>
import
ErrorWrap
from
'
./ErrorWrap
'
import
{
validate
}
from
'
@/utils/validate
'
export
default
{
name
:
'
InputActionSheet
'
,
components
:
{
ErrorWrap
},
props
:
{
title
:
{
type
:
String
,
default
:
''
},
rules
:
{
type
:
Object
,
default
:
()
=>
({})
},
data
:
{
type
:
String
,
default
:
''
},
visible
:
{
type
:
Boolean
,
default
:
false
},
label
:
{
type
:
String
,
default
:
'
VIN码
'
}
},
data
()
{
return
{
inputValue
:
''
,
errorMessage
:
''
}
},
computed
:
{
btnDisable
()
{
// 校验失败的字段
const
[
failField
]
=
this
.
validateField
(
this
.
inputValue
)
// 错误信息
return
!!
failField
}
},
watch
:
{
/**
* 监听打开弹框事件
*/
visible
()
{
if
(
this
.
visible
)
{
this
.
inputValue
=
this
.
data
}
}
},
methods
:
{
/**
* 校验字段值是否合法
* @param value 字段输入的值
*/
validateField
(
value
)
{
// 当前需要校验的字段
const
fields
=
{}
const
fieldRules
=
[]
// 遍历规则
Object
.
keys
(
this
.
rules
).
forEach
(
ruleKey
=>
{
fields
[
ruleKey
]
=
value
fieldRules
.
push
(
ruleKey
)
})
// 返回校验结果
return
validate
(
fields
,
fieldRules
)
},
/**
* 校验用户输入
* @param value 字段输入的值
*/
validateInput
(
value
)
{
// 错误字段和错误值
const
[
failField
,
failMessage
]
=
this
.
validateField
(
value
)
// 错误信息
this
.
errorMessage
=
failField
?
failMessage
:
''
},
/**
* 确认用户输入的内容
*/
handleConfirmInput
()
{
// 触发submit方法
this
.
$emit
(
'
submit
'
,
this
.
inputValue
)
// 关闭弹框
this
.
$emit
(
'
update:visible
'
,
false
)
}
}
}
</
script
>
<
style
lang=
"scss"
>
.page-rnr-action-sheet
{
padding
:
30px
;
.page-rnr-action-sheet-title
{
color
:
#242424
;
font-size
:
32px
;
font-weight
:
500
;
line-height
:
45px
;
padding
:
0
0
0
24px
;
}
.van-cell
{
background-color
:
transparent
;
padding
:
0
;
&
.van-field
{
background-color
:
transparent
;
padding
:
44px
26px
;
position
:
relative
;
.van-field__label
{
color
:
#242424
;
font-size
:
32px
;
font-weight
:
400
;
width
:
120px
;
height
:
45px
;
}
.van-cell__value
{
height
:
45px
;
.van-field__control
{
color
:
#242424
;
font-size
:
32px
;
}
&
.van-field__value
{
position
:
relative
;
.van-field__error-message
{
position
:
absolute
;
left
:
-220px
;
bottom
:
-90px
;
}
}
.van-field__clear
{
margin-left
:
30px
;
}
}
}
&
.van-cell--required
{
.van-cell__title
{
span
{
padding-left
:
20px
;
}
}
&
:before
{
left
:
0px
;
font-size
:
16px
;
}
}
.van-cell__title
{
span
{
color
:
#242424
;
font-size
:
32px
;
font-weight
:
400
;
}
}
.van-cell__label
{
margin-top
:
36px
;
}
&
:after
{
position
:
absolute
;
box-sizing
:
border-box
;
content
:
' '
;
pointer-events
:
none
;
right
:
0px
;
bottom
:
0
;
left
:
0px
;
border-bottom
:
1
PX
solid
#ebedf0
!
important
;
transform
:
scaleY
(
.5
);
}
}
.page-rnr-action-sheet-btn
{
margin
:
112px
0px
88px
;
}
}
</
style
>
Prev
1
…
3
4
5
6
7
8
9
10
11
…
14
Next
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