Commit 8f7acb3f authored by kang.nie@inzymeits.com's avatar kang.nie@inzymeits.com
Browse files

初始化代码

parent 2d7d3f82
Pipeline #3104 failed with stages
in 0 seconds
<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>
<template>
<router-view />
</template>
<script>
export default {
name: 'EmptyRouter'
}
</script>
<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>
<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: 1PX solid #ee0a24;
transform: scaleY(0.5);
}
}
}
}
</style>
<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>
<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>
<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>
<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: 1PX solid #ee0a24;
transform: scaleY(0.5);
}
}
&.status-fail {
&:after {
display: none;
}
}
}
</style>
<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>
<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>
<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>
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)
}
}
}
<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>
<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>
<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: 1PX solid #ebedf0 !important;
transform: scaleY(.5);
}
}
.page-rnr-action-sheet-btn {
margin: 112px 0px 88px;
}
}
</style>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment