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="contact-info">
<!-- <div class="contact-title">
<i/>联系信息
</div> -->
<!-- <van-cell-group> -->
<ErrorWrap :error="error.phone !== ''" :error-message="error.phone">
<van-field
v-model="phone"
placeholder="请输入"
label="车主手机号"
type="tel"
clearable
readonly
input-align="right"
required/>
</ErrorWrap>
<ErrorWrap :error="error.captcha !== ''" :error-message="error.captcha">
<van-field
v-model="captcha"
placeholder="请输入"
label="图形验证码"
clearable
input-align="right">
<img slot="button" :src="codeImg" class="code-img" alt="验证码" @click="getNewGraphic">
</van-field>
</ErrorWrap>
<ErrorWrap :error="error.sms !== ''" :error-message="error.sms">
<van-field
v-model="sms"
placeholder="请输入"
type="number"
label="验证码"
clearable
input-align="right">
<div slot="button" class="send-message">
<span v-if="!countDown" @click="getMessage">获取验证码</span>
<span v-else>{{ timeCount }}s</span>
</div>
</van-field>
</ErrorWrap>
<!-- </van-cell-group> -->
</div>
</template>
<script>
import { getGraphic } from '@/api/loginToC'
import { unboundSms } from '@/api/bind'
import ErrorWrap from '@/components/ErrorWrap'
import { Toast } from 'vant'
export default {
name: 'ContactInfo',
components: {
ErrorWrap
},
props: {
info: {
type: Object,
default: () => {}
}
},
data() {
return {
phone: '',
sms: '',
captcha: '',
error: {
phone: '',
captcha: '',
sms: ''
},
timeCount: 60, // 短信倒计时
countDown: false, // 是否倒计时
codeImg: '', // 图形验证码图片
requestId: '', // 图形验证码对应id
validateList: { // 输入框验证规则
'phone': [
{
validate: '',
error: '手机号不能为空'
},
{
validate: /^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/,
error: '手机号格式有误'
}
],
'captcha': [{
validate: '',
error: '验证码不能为空'
}],
'sms': [{
validate: '',
error: '验证码不能为空'
}]
}
}
},
watch: {
info(val) {
this.phone = val.phone
}
},
created() {
// 获取图形验证码
this.getNewGraphic()
},
methods: {
// 获取图形验证码
getNewGraphic() {
getGraphic({
platformSource: 'H5'
}).then(res => {
this.codeImg = res.captchaImg
this.requestId = res.requestId
})
},
// 获取短信验证码
getMessage() {
const flag = this.validateRule({
'phone': this.validateList.phone,
'captcha': this.validateList.captcha
})
if (flag) {
// 调接口
unboundSms({
rnrId: this.info.rnrId,
imgCaptcha: this.captcha,
requestId: this.requestId
}).then(res => {
// 倒计时
this.countsDown()
Toast('验证码发送成功!')
}).catch(() => {
this.getNewGraphic()
})
}
},
// 倒计时
countsDown() {
this.countDown = true
const timer = setInterval(() => {
this.timeCount--
if (this.timeCount < 0) {
clearInterval(timer)
this.timeCount = 60
this.countDown = false
}
}, 1000)
},
// 父组件获取信息
getContactInfo() {
const flag = this.validateRule(this.validateList)
if (flag) {
return {
smsCaptcha: this.sms
}
} else {
return false
}
},
// 通用输入框验证
validateRule(rule) {
var flag = true
Object.keys(rule).forEach(key => {
var errorList = []
rule[key].map(x => {
if (x.validate === '') {
if (this[key] === '') errorList.push(x.error)
} else {
if (!x.validate.test(this[key])) errorList.push(x.error)
}
})
console.log(errorList, ' errorList')
this.error[key] = errorList[0] ? errorList[0] : ''
if (errorList.length > 0) flag = false
})
return flag
}
}
}
</script>
<style lang="scss">
.contact-info {
opacity: 0.88;
background: #FFFFFF;
border-radius: 37px;
padding-left: 33px;
padding-right: 31px;
.contact-title {
display: flex;
align-items: center;
font-size: 32px;
height: 40px;
line-height: 40px;
color: #242424;
font-weight: 500;
text-align: left;
margin-left: 20px;
font-weight: 800;
i {
display: inline-block;
width: 40px;
height: 40px;
margin-right: 16px;
background: url(../../../assets/icon/icon_phone.png) 0 0 no-repeat;
background-size: 100% 100%;
}
}
.van-field {
border-radius: 134px;
height: 134px;
align-items: center;
padding: 0;
&.van-cell--required {
padding-left: 30px;
}
.van-field__label {
font-size: 32px;
color: #242424;
}
input {
font-size: 32px;
color: #242424;
}
}
.code-img {
width: 180px;
height: 80px;
}
.send-message {
font-size: 32px;
color: #0761F3;
font-weight: 400;
}
}
</style>
<template>
<div class="identify-result">
<van-overlay :show="dialogVisible">
<div class="wrapper">
<div class="content">
<div v-if="type === 'rebind'" class="result-top">
<div v-if="result === 'success'">
<img :src="rebindSuccessImg" alt="" class="result-img">
<div class="result-text">绑定成功</div>
</div>
<div v-if="result === 'failed'">
<img :src="rebindFail" alt="" class="result-img">
<div class="result-text">绑定失败</div>
</div>
</div>
<div v-if="type === 'unbound'" class="result-top">
<div v-if="result === 'success'">
<img :src="unboundSuccessImg" alt="" class="result-img">
<div class="result-text">解绑成功</div>
</div>
<div v-if="result === 'failed'">
<img :src="unboundFailImg" alt="" class="result-img">
<div class="result-text">解绑失败</div>
</div>
</div>
<van-button type="primary" class="close-btn" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click.stop="close">确定</van-button>
</div>
</div>
</van-overlay>
</div>
</template>
<script>
export default {
name: 'IdentifyResult',
props: {
type: {
type: String,
default: ''
},
result: {
type: String,
default: ''
},
dialogVisible: {
type: Boolean,
default: false
}
},
data() {
return {
rebindSuccessImg: require('@/assets/images/rebind_success.png'),
rebindFail: require('@/assets/images/rebind_failed.png'),
unboundSuccessImg: require('@/assets/images/unbound_success.png'),
unboundFailImg: require('@/assets/images/unbound_failed.png')
}
},
methods: {
close() {
// 关闭弹框
this.$emit('closeDialog')
// 如果是成功,则返回上一页
if (this.result === 'success') {
this.$router.go(-1)
}
}
}
}
</script>
<style lang="scss" scoped>
.identify-result {
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
.content {
position: relative;
width: 619px;
height: 478px;
background-image: linear-gradient(180deg, #CCDDFF 0%, #FFFFFF 36%);
border-radius: 16px;
.result-top{
position: relative;
top: -139px;
left: 50%;
margin-left: -171px;
width: 342px;
.result-img {
width: 342px;
height: 342px;
}
.result-text {
font-size: 36px;
color: #212026;
text-align: center;
font-weight: 500;
margin-top: 12px;
}
}
.close-btn {
position: absolute;
font-size: 32px;
bottom: 37px;
left: 36px;
width: 542px;
height: 96px;
}
}
}
}
</style>
<template>
<div class="rebind">
<div class="container">
<BindInfo type="rebind"/>
<ContactInfo ref="contactRef"/>
</div>
<div class="bind-btn">
<van-button type="primary" class="cancel" round @click="cancel">取消</van-button>
<van-button type="primary" class="bind" round @click="submit">重新绑定</van-button>
</div>
<IdentifyResult :dialog-visible="dialogVisible" type="rebind" @closeDialog="dialogVisible = false"/>
</div>
</template>
<script>
import BindInfo from './components/bind-info.vue'
import ContactInfo from './components/contact-info.vue'
import IdentifyResult from './components/identify-result'
export default {
name: 'Rebind',
components: {
BindInfo,
ContactInfo,
IdentifyResult
},
data() {
return {
dialogVisible: false
}
},
methods: {
submit() {
const contactInfo = this.$refs.contactRef.getContactInfo()
console.log(contactInfo)
if (!contactInfo) return
this.dialogVisible = true
},
cancel() {
this.$router.push({ name: 'UserHome' })
}
}
}
</script>
<style lang="scss" scoped>
.rebind {
background: #f9f9f9;
height: 100vh;
.container {
background: #f9f9f9;
padding: 0 24px 230px;
}
.bind-btn {
position: fixed;
bottom: 0;
left: 0;
background: #fff;
width: 100%;
height: 186px;
padding-top: 30px;
text-align: center;
.cancel {
width: 254px;
height: 96px;
background-image: linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%);
font-size: 30px;
color: #242424;
border: none;
margin-right: 40px;
}
.bind {
width: 392px;
height: 96px;
font-size: 30px;
background-image: linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%);
border: none;
}
}
}
</style>
<template>
<div class="rebind">
<div class="container">
<BindInfo :info="info" :params="params" type="unbound"/>
</div>
<div class="bind-btn">
<van-button type="" class="cancel" round @click="cancel">取消</van-button>
<van-button type="primary" class="bind" round @click="submit">确定解绑</van-button>
</div>
<IdentifyResult :dialog-visible="dialogVisible" :result="result" type="unbound" @closeDialog="dialogVisible = false"/>
</div>
</template>
<script>
import BindInfo from './components/bind-info.vue'
import IdentifyResult from './components/identify-result'
import { rnrInfo, unbound } from '@/api/bind'
export default {
name: 'Rebind',
components: {
BindInfo,
IdentifyResult
},
data() {
return {
dialogVisible: false,
params: {},
info: {},
result: '' // 解绑结果
}
},
activated() {
this.params = this.$route.params
// 获取信息
this.getRnrInfo()
},
methods: {
// 获取信息
getRnrInfo() {
rnrInfo(this.params).then(res => {
this.info = res
})
},
submit() {
const contactInfo = { smsCaptcha: '' }
if (!contactInfo) return
const { rnrId, iccid } = this.params
const params = { ...contactInfo, ...{ rnrId, iccid }}
unbound(params).then(res => {
this.result = 'success'
this.dialogVisible = true
}).catch(() => {
this.result = 'failed'
this.dialogVisible = true
})
},
cancel() {
this.$router.go(-1)
}
}
}
</script>
<style lang="scss" scoped>
.rebind {
background: #f9f9f9;
height: 100vh;
.container {
background: #f9f9f9;
padding: 0 24px 230px;
}
.bind-btn {
position: fixed;
bottom: 0;
left: 0;
background: #fff;
width: 100%;
height: 186px;
padding-top: 30px;
text-align: center;
.cancel {
width: 254px;
height: 96px;
background-image: linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%);
font-size: 30px;
color: #242424;
border: none;
margin-right: 40px;
}
.bind {
width: 392px;
height: 96px;
font-size: 30px;
background-image: linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%);
border: none;
}
}
}
</style>
<template>
<div class="page-home">
<div class="container">
<img :src="bgImg" alt="" class="bg">
<LogOut/>
<div class="content">
<div class="ml15">
<img v-if="logo" :src="logo" alt="logo" class="logo">
<img :src="titleImg" class="title" alt="">
<!-- <div class="line"/> -->
<!-- <div class="text">国际一流的汽车数字化运营服务提供商,共建设美好车生活</div> -->
</div>
<van-grid :gutter="0" :border="false" :column-num="2" class="card-wrapper">
<van-grid-item>
<div class="card new-car" @click="toRealName('RnrPersonCard', 0, 0)">
<div class="card-text1">新车车主</div>
<div class="card-text2">针对未实名或者已解绑的车辆</div>
</div>
</van-grid-item>
<van-grid-item>
<div class="card new-car-consigner" @click="toRealName('RnrPersonCard', 0, 1)">
<div class="card-text1">新车代办</div>
<div class="card-text2">车主无法到现场,委托他人代办</div>
</div>
</van-grid-item>
<van-grid-item>
<div class="card used-car" @click="toRealName('RnrPersonCard', 1, 0)">
<div class="card-text1">二手车新车主</div>
<div class="card-text2">针对未解绑的车辆</div>
</div>
</van-grid-item>
<van-grid-item>
<div class="card used-car-consigner" @click="toRealName('RnrPersonCard', 1, 1)">
<div class="card-text1">二手车代办</div>
<div class="card-text2">车主无法到现场,委托他人代办</div>
</div>
</van-grid-item>
<van-grid-item>
<div class="card enterprise" @click="toRouterPage('RnrEnterpriseCard')">
<div class="card-text1">企业实名认证</div>
<div class="card-text2">针对未实名或者已解绑的企业车辆</div>
</div>
</van-grid-item>
<van-grid-item>
<div class="card car-search" @click="toRouterPage('RnrCarSearch')">
<div class="card-text1">单车认证查询</div>
<div class="card-text2">查询单台车辆认证信息</div>
</div>
</van-grid-item>
</van-grid>
</div>
</div>
<van-overlay :show="loading">
<van-loading size="40px" vertical>加载中...</van-loading>
</van-overlay>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import LogOut from '@/components/LogOut'
import { Toast } from 'vant'
import { RESET_PERSON, RESET_ENTERPRISE, DISPATCH_INIT_PROTOCAL, SET_PERSON_CONSIGNER } from '@/store/modules/rnr'
export default {
name: 'Home',
components: {
LogOut
},
data() {
return {
loading: false,
bgImg: require('@/assets/images/bg.png'),
titleImg: require('@/assets/images/title.svg')
}
},
computed: {
...mapGetters(['logo'])
},
async created() {
// 如果当前在钉钉浏览器中打开
if (window.os.isDingTalk) {
Toast('请勿在钉钉内打开实名系统')
}
this.$store.dispatch(`rnr/${DISPATCH_INIT_PROTOCAL}`)
// 请求logo
await this.$store.dispatch('dict/getLogo', {
isCustomer: false,
force: true,
tenantNo: this.$store.getters.loginUser.tenantNo
})
},
methods: {
/**
* 是否有协议
*/
notHasProtocal() {
return !this.$store.getters.protocal || !this.$store.getters.protocal.realnameNoticeVertical
},
/**
* 校验协议是否存在
*/
async verifyProtocalExist() {
// 如果当前没有上传协议,则再查一遍协议
if (this.notHasProtocal()) {
// 展示loading框
this.loading = true
try {
await this.$store.dispatch(`rnr/${DISPATCH_INIT_PROTOCAL}`)
} catch (error) {
console.error(error)
}
// 关闭loading框
this.loading = false
// 查询一遍后依然没有协议,则不让继续
if (this.notHasProtocal()) {
Toast('请补齐协议文件')
return false
}
}
return true
},
/**
* 跳转到实名页面
* @param routerName 路由名称
* @param customerType 客户类型 0:新车车主 1:二手车车主
* @param isConsigner 是否委托人代办 1:是 0:否
*/
async toRealName(routerName, customerType, isConsigner) {
// 校验文件是否存在
const isProtocalExist = await this.verifyProtocalExist()
// 如果当前没有上传协议,则再查一遍协议
if (!isProtocalExist) {
return
}
// 清空store中保存的个人,企业实名数据
this.$store.commit(`rnr/${RESET_PERSON}`)
this.$store.commit(`rnr/${RESET_ENTERPRISE}`)
// 设置客户类型和是否委托人代办
this.$store.commit(`rnr/${SET_PERSON_CONSIGNER}`, {
customerType,
isConsigner: isConsigner === 1
})
// 如果当前是自然人实名认证
this.$router.push({
name: routerName,
query: {
customerType,
isConsigner
}
})
},
/**
* 跳转到路由页面
* @param routerName 路由名称
*/
async toRouterPage(routerName) {
// 校验文件是否存在
const isProtocalExist = await this.verifyProtocalExist()
// 如果当前没有上传协议,则再查一遍协议
if (!isProtocalExist) {
return
}
this.$router.push({ name: routerName })
}
}
}
</script>
<style lang="scss">
.page-home {
background: url(../../assets/images/bg_home.png) 0 0 no-repeat;
background-size: 100% 100%;
height: 100vh;
.van-overlay {
align-items: center;
display: flex;
justify-content: center;
}
.container {
position: relative;
padding: 0 24px;
.bg {
position: absolute;
top: 29px;
right: 0;
width: 468px;
height: 664px;
}
.content {
padding-top: 56px;
position: relative;
z-index: 1;
.logo {
width: auto;
height: 48px;
}
.title {
margin-top: 164px;
width: 347px;
height: 47px;
display: block;
}
.line {
width: 163px;
height: 3px;
margin: 38px 0 36px;
background-image: linear-gradient(90deg, #2C38D2 0%, #61A2F9 50%, #fff 100%);
}
.text {
width: 380px;
font-size: 26px;
line-height: 36px;
color: #596B97;
font-weight: 400;
margin-bottom: 126px;
}
.card-wrapper {
margin-top: 160px;
.van-grid-item {
overflow: hidden;
.van-grid-item__content {
background-color: transparent;
padding: 0px;
}
}
.card {
background-size: auto 100%;
background-repeat: no-repeat;
background-position: center center;
box-sizing: border-box;
width: 352px;
height: 256px;
padding-top: 50px;
padding-left: 46px;
padding: 50px 42px 0 44px;
.card-text1 {
font-family: PingFangSC-Semibold;
font-size: 32px;
line-height: 44px;
color: #FFFFFF;
text-shadow: 0 2px 7px rgba(48,89,197,0.70);
font-weight: 600;
margin-bottom: 8px;
}
.card-text2 {
width: 246px;
opacity: 0.83;
font-family: PingFangSC-Regular;
font-size: 20px;
line-height: 28px;
color: #FFFFFF;
font-weight: 400;
}
&.new-car {
background-image: url(../../assets/home/ico_new_car.png);
}
&.new-car-consigner {
background-image: url(../../assets/home/ico_new_car-consigner.png);
}
&.used-car {
background-image: url(../../assets/home/ico_used_car.png);
}
&.used-car-consigner {
background-image: url(../../assets/home/ico_used_car-consigner.png);
}
&.enterprise {
background-image: url(../../assets/home/ico_enterprise.png);
}
&.car-search {
background-image: url(../../assets/home/ico_car_search.png);
}
}
}
.ml15 {
margin-left: 33px;
}
}
}
}
</style>
<template>
<div class="page-home">
<div class="container">
<img :src="bgImg" alt="" class="bg">
<!-- <LogOut/> -->
<div class="content">
<div class="ml15">
<img v-if="logoImg" :src="logoImg" alt="logo" class="logo">
<img :src="titleImg" class="title" alt="">
<div class="line"/>
<!-- <div class="text">国际一流的汽车数字化运营服务提供商,共建设美好车生活</div> -->
</div>
<div class="service"><span @click="toHelp" class="help">帮助</span><span @click="handleFetchVisitTime">服务时间:{{ visitTime.length === 0 ? '暂未获取到服务时间,点击重试' : `${visitTime[0]} - ${visitTime[1]}(节假日无休)` }}</span></div>
<div class="card new-car" @click="toRouterPage('MarkupPersonCard')">
<div class="card-text1">新车车主</div>
<div class="card-text2">针对未实名或者已解绑的车辆</div>
<div class="card-desc" :class="{locked: !canVisit}">已过服务时间</div>
</div>
<div class="card enterprise" @click="toRouterPage('MarkupEnterpriseCard')">
<div class="card-text1">企业实名认证</div>
<div class="card-text2">针对未实名或者已解绑的企业车辆</div>
<div class="card-desc" :class="{locked: !canVisit}">已过服务时间</div>
</div>
<div class="card owner-search" @click="toRouterPage('UserSearch')">
<div class="card-text1">车主实名查询</div>
<div class="card-text2">查询个人实名信息</div>
</div>
</div>
</div>
<van-overlay :show="loading">
<van-loading size="40px" vertical>加载中...</van-loading>
</van-overlay>
</div>
</template>
<script>
import LogOut from '@/components/LogOut'
import { Toast } from 'vant'
import { checkEffectiveTime } from '@/api/dict'
import { RESET_PERSON, RESET_ENTERPRISE, DISPATCH_INIT_PROTOCAL } from '@/store/modules/markup'
export default {
name: 'UserHome',
components: {
LogOut
},
data() {
return {
loading: false,
bgImg: require('@/assets/images/bg.png'),
logoImg: this.$store.getters.logo,
titleImg: require('@/assets/images/title.svg'),
canVisit: true,
visitTime: []
}
},
created() {
// 查询协议文件
this.$store.dispatch(`markup/${DISPATCH_INIT_PROTOCAL}`)
// 查询可访问时间
this.handleFetchVisitTime()
},
activated() {
this.handleFetchVisitTime()
},
methods: {
/**
* 获取可访问时间
*/
async handleFetchVisitTime() {
try {
// 请求可访问的事件
const { visit, startTime, endTime } = await checkEffectiveTime()
// 记录是否可访问
this.canVisit = visit
// 可访问时间
this.visitTime = [startTime, endTime]
} catch (error) {
// 标记当前不能访问
this.canVisit = false
}
},
/**
* 是否有协议
*/
notHasProtocal() {
return !this.$store.getters.protocalMarkup || !this.$store.getters.protocalMarkup.realnameNoticeVertical
},
/**
* 校验协议是否存在
*/
async verifyProtocalExist() {
// 如果当前没有上传协议,则再查一遍协议
if (this.notHasProtocal()) {
// 展示loading框
this.loading = true
try {
await this.$store.dispatch(`markup/${DISPATCH_INIT_PROTOCAL}`)
} catch (error) {
console.error(error)
}
// 关闭loading框
this.loading = false
// 查询一遍后依然没有协议,则不让继续
if (this.notHasProtocal()) {
Toast('请补齐协议文件')
return false
}
}
return true
},
/**
* 跳转到路由页面
* @param routerName 路由名称
*/
async toRouterPage(routerName) {
// 非服务时间
if (!this.canVisit && (routerName === 'MarkupPersonCard' || routerName === 'MarkupEnterpriseCard')) {
return
}
// 校验文件是否存在
const isProtocalExist = await this.verifyProtocalExist()
// 如果当前没有上传协议,则再查一遍协议
if (!isProtocalExist) {
return
}
// 先清空数据
this.$store.commit(`markup/${RESET_PERSON}`)
this.$store.commit(`markup/${RESET_ENTERPRISE}`)
// 开始跳转页面
this.$router.push({ name: routerName })
},
/**
* 跳转帮助
* @time 2022-07-14 20:24:07
*/
toHelp() {
this.$router.push({ name: 'Help' })
}
}
}
</script>
<style lang="scss">
.page-home {
background: url(../../assets/images/bg_home.png) 0 0 no-repeat;
background-size: 100% 100%;
height: 100vh;
.van-overlay {
align-items: center;
display: flex;
justify-content: center;
}
.container {
position: relative;
padding: 0 14px;
.bg {
position: absolute;
top: 29px;
right: 0;
width: 468px;
height: 664px;
}
.content {
padding-top: 56px;
.logo {
width: auto;
height: 48px;
}
.title {
margin-top: 112px;
width: 347px;
height: 47px;
display: block;
}
.line {
width: 163px;
height: 3px;
margin: 38px 0 36px;
background-image: linear-gradient(90deg, #2C38D2 0%, #61A2F9 50%, #fff 100%);
}
.text {
width: 380px;
font-size: 26px;
line-height: 36px;
color: #596B97;
font-weight: 400;
}
.service {
margin: 25px 0 65px 33px;
font-size: 20px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #596B97;
position: relative;
z-index: 1;
.help {
margin-right: 12px;
padding-left: 24px;
color: #2A68FF;
background-position: 0 center;
background-repeat: no-repeat;
background-size: 20px 20px;
background-image: url(../../assets/images/icon_help.png);
}
}
.card {
background-size: auto 100%;
background-repeat: no-repeat;
background-position: center center;
box-sizing: border-box;
padding: 10px;
width: 706px;
height: 320px;
position: relative;
margin: 0 auto 4px;
z-index: 1;
padding: 89px 0 0 46px;
position: relative;
.card-desc {
padding: 89px 0 0 46px;
box-sizing: border-box;
border-radius: 24px;
position: absolute;
left: 10px;
top: 8px;
right: 10px;
bottom: 10px;
display: none;
&.locked {
display: block;
background: rgba(109,144,255,.2);
backdrop-filter: blur(7px);
box-sizing: border-box;
background-position: center 98px;
background-repeat: no-repeat;
background-size: 59px 76px;
background-image: url(../../assets/images/icon_lock.png);
font-size: 26px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #fff;
text-align: center;
padding: 192px 0 0 0;
}
}
.card-text1 {
color: #FFFFFF;
font-family: PingFangSC-Semibold;
font-size: 40px;
line-height: 56px;
height: 56px;
text-shadow: 0 2px 7px #2585DC;
font-weight: 600;
margin-bottom: 16px;
}
.card-text2 {
width: 266px;
opacity: 0.83;
font-family: PingFangSC-Regular;
font-size: 24px;
line-height: 33px;
color: #FFFFFF;
font-weight: 400;
}
&.new-car {
background-image: url(../../assets/images/new_car.png);
}
&.enterprise {
background-image: url(../../assets/images/enterprise.png);
}
&.owner-search {
background-image: url(../../assets/images/owner-search.png);
}
}
.ml15 {
margin-left: 33px;
}
}
}
}
</style>
<template>
<div class="agreement">
<div class="content">
联通智网科技股份有限公司(下称“联通智网”)和您所购买车辆的道路机动车辆生产企业(以下合称“我们”)非常重视用户的隐私和个人信息保护,我们在落实国家实名制政策法规,完成您所使用的车联网卡实名制认证工作中,致力于维持您对我们的信任。联通智网和您所购买车辆的道路机动车辆生产企业作为实名制认证相关数据的共同控制者,承诺采取相应的安全保护措施来保护您的个人信息。请在使用联通智网科技实名制认证平台前,仔细阅读并了解《联通智网科技实名制认证平台用户个人信息保护政策》(下称“本个人信息保护政策”)。<br>
我们希望通过本个人信息保护政策向您说明:在您使用我们的产品或服务时,我们会收集哪些数据、为什么收集这些数据、会利用这些数据做什么以及我们如何保护这些数据。<br>
本个人信息保护政策与您所使用的我们的产品或服务息息相关,对于您行使个人权利及保护您的个人信息至关重要,请您在使用我们的产品或服务前认真阅读并充分理解本政策所写明的内容。如您为未成年人,请务必通知您的监护人共同阅读本政策,并寻求您的监护人的同意和指导。<br>
阅读过程中,如您对我们的隐私政策及服务条款的内容有任何疑问,请联系我们。 您开始使用我们的产品或服务,即表示您已经理解并同意本个人信息保护政策全部内容。<br>
本个人信息保护政策包含以下内容:<br>
我们如何收集和使用您的个人信息;<br>
我们如何使用Cookie和同类技术;<br>
我们如何共享、转让、公开披露您的个人信息;<br>
我们如何保护您的个人信息;<br>
我们如何处理儿童的个人信息;<br>
您的个人信息如何储存及在全球范围转移;<br>
本政策如何更新;<br>
您的权利;<br>
如何联系我们。<br>
一、我们如何收集和使用个人信息<br>
个人信息是指以电子或者其他方式记录的与已识别或者可识别的自然人有关的各种信息,不包括匿名化处理后的信息。<br>
(一)为更好地为您提供服务,联通智网和您所购买车辆的道路机动车辆生产企业将遵循“合法、正当、必要”原则,按照相关法律法规及监管政策的规定收集和使用如下您的个人相关信息:<br>
1.通过基础功能收集信息<br>
在使用本平台时,用户在实名制过程中会填写姓名、手机号等信息,为更好的对车辆数据进行分析,更好的为客户进行服务,需结合中国联通及联通智网其他业务系统收集相关信息,包括但不限于个人信息、车辆信息、上网日志数据、车联网用量数据、位置轨迹数据、车联网订单数据等。留存的个人身份信息主要包括:个人身份及网络身份标识鉴权信息[包括但不限于个人用户姓名、有效通信联系方式、有效证件类型及号码、证件登记信息(如地址)等]。根据《电话用户真实身份信息登记规定》(工业和信息化部令第25号)、《工业和信息化部关于加强车联网卡实名登记管理的通知》(工信部网安函〔2021〕246号)如您拒绝提供个人有效证件及真实信息,将无法办理入网手续,无法使用我们的产品或服务。<br>
2.为共同向您提供产品服务或改进产品服务的质量或出于对产品服务安全性的考量等合理需要,我们可能按照相关法律法规及监管政策的要求或经过您的授权从关联公司、合作伙伴及其他受信任的第三方接收您的个人信息及其他信息。<br>
3.在采集过程中,我们将严格遵守相关法律法规及监管政策,不采集任何法律法规及监管政策禁止采集的信息。<br>
4.您了解并同意,以下情形中我们使用个人信息无需征得您的授权同意:<br>
(1) 为订立、履行个人作为一方当事人的合同所必需,或者按照依法制定的劳动规章制度和依法签订的集体合同实施人力资源管理所必需;<br>
(2) 为履行法定职责或者法定义务所必需;<br>
(3) 与公共安全、重大公共利益直接有关的;例如:为应对突发公共卫生事件,或者紧急情况下为保护自然人的生命健康和财产安全所必需;<br>
(4) 为公共利益实施新闻报道、舆论监督等行为,在合理的范围内处理个人信息;<br>
(5) 依照法律法规规定在合理的范围内处理个人自行公开或者其他已经合法公开的个人信息;<br>
(6) 出于维护您或其他个人的生命、财产等重大合法权益但又很难得到您的授权同意的;<br>
(7) 与国家安全、国防安全有关的;<br>
(8) 与犯罪侦查、起诉、审判和判决执行等有关的;<br>
(9) 用于维护所提供的产品或服务的安全稳定运行所必需,例如发现、处置产品或服务的故障;<br>
(10) 法律法规及监管政策规定的其他情形。<br>
(二)为向您提供服务,在遵守国家法律法规及监管政策的前提下,我们可能将收集到的您的个人信息用于以下目的:<br>
1.为您提供服务<br>
(1)在我们提供服务时,用于身份验证、客户服务、安全防范、诈骗监测、存档和备份用途,确保我们向您提供的产品和服务的安全性;<br>
(2)通过信息数据分析,帮助我们设计为您提供更好感知的新服务,改善我们现有服务;<br>
(3)根据国家相关要求,用于旨在推进政府管理、加强社会治理、改善社会民生、建立诚信体系等目的的服务;<br>
(4)使我们更加了解您如何使用我们的产品、服务,从而针对性地满足您的需求;<br>
(5)用于向您提供个性化或定制化产品、服务和广告,例如向您展现或推荐相关程度更高信息流或者推广信息结果;<br>
(6)评估我们服务效果,并加以改善;<br>
(7)软件认证或管理软件升级;<br>
(8)邀请您参与有关我们产品和服务的调查。<br>
2.您了解并同意,我们可以通过技术手段对您的个人或车辆信息数据进行匿名化处理,匿名化处理的信息将无法识别个人或车辆主体。在此情况下我们有权使用已经匿名化的信息,并在符合相关法律法规及监管政策的前提下,我们有权对包括您的个人或车辆信息在内的用户数据库进行整体化分析和利用。<br>
3.当我们要将信息用于本政策未载明的其它用途时,会事先征求您的同意。此外,第三方主体可能会通过实名制认证平台向您提供服务。当您进入第三方主体运营的服务页面时,请注意相关服务由第三方主体向您提供。涉及到第三方主体向您收集个人信息的,建议您仔细查看第三方主体的隐私政策或协议约定。<br>
二、我们如何使用Cookie和同类技术<br>
为确保网站正常运转,我们会在您的计算机或移动设备上存储名为 Cookie的小数据文件。Cookie通常包含标识符、站点名称以及一些号码和字符。借助于Cookie,网站能够存储您的偏好或购物车内的商品等数据。<br>
我们不会将Cookie用于本政策所述目的之外的任何用途。您可根据自己的偏好管理或删除Cookie。有关详情,请参见AboutCookies.org。您可以清除计算机上保存的所有Cookie,大部分网络浏览器都设有阻止Cookie的功能。但如果您这么做,则需要在每一次访问我们的网站时亲自更改用户设置。<br>
三、我们如何保护您的个人信息<br>
(一)我们在中华人民共和国境内收集和产生的个人信息将存储在中华人民共和国境内。我们仅在本政策所述目的所必需期间和法律法规及监管规定的时限内保存您的个人信息,超出保存期限后,我们将对所收集的信息进行匿名化处理。<br>
(二)为了保障您的信息安全,我们在收集您的信息后,将采取各种合理必要的措施保护您的信息。在技术开发环境当中,我们仅使用经过去标识化处理的信息进行统计分析;对外提供研究报告时,我们将对报告中所包含的信息进行去标识化处理。我们会将去标识化后的信息与可用于恢复识别个人的信息分开存储,确保在针对去标识化信息的后续处理中不重新识别个人。<br>
(三)我们已使用符合业界标准的安全防护措施保护您提供的个人信息,防止数据遭到未经授权访问、公开披露、使用、修改、损坏或丢失。我们将采取一切合理可行的措施(例如:身份鉴别、信息采集监测和审计、网络专线传输、防入侵、防病毒、信息加密存储、信息访问的权限管理、信息认证授权和监控审计、信息脱敏、信息溯源)以及配套的管理体系来保护您的个人信息。<br>
(四)为保障您的信息安全,我们会使用必要安全技术及配套的管理体系来防止您的信息被泄露、毁损或者丢失。同时我们已设立用户信息保护责任部门,建立相关内控制度,对可能接触到您信息的工作人员采取最小够用授权原则;不断对工作人员培训相关法律法规、隐私政策和安全意识,对工作人员处理您的信息的行为进行系统监控。我们每年会接受相关国家机构的信息安全检查,我们仅允许有必要知晓这些信息的联通智网和您所购买车辆的道路机动车辆生产企业关联方的员工、合作伙伴访问个人信息,同时要求可能接触到您个人信息的所有人员履行相应的保密义务。<br>
(五)您承诺妥善保存登录的账号、密码等信息,由于您的原因造成账号密码的泄露产生的后果由您承担。为防止信息泄露,我们将采取防泄漏、反爬虫等技术手段,限制频繁查询您的信息等异常操作行为,以防止账号密码泄露或他人冒用身份造成您产生不良行为记录或者您的信息泄露。<br>
(六)我们已制定个人信息安全事件应急预案,定期组织内部相关人员进行应急响应培训和应急演练,使其掌握岗位职责和应急处置策略和规程。但由于技术水平限制及可能存在的各种恶意手段,有可能因我们可控范围外的因素而出现安全问题。在发生个人信息安全事件后,我们将按照相关法律法规及监管政策的要求,及时向您告知:安全事件的基本情况和可能影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施等。我们将及时将事件相关情况以邮件、信函、电话、推送通知等方式告知您,难以逐一告知个人信息主体时,我们会采取合理、有效的方式发布公告。同时,我们还将按照监管部门要求,主动上报个人信息安全事件的处置情况。<br>
四、我们如何处理儿童的个人信息<br>
(一)我们非常重视对未成年个人信息的保护。我们将根据国家相关法律法规及监管政策的规定保护未成年人的个人信息。<br>
(二)对于经父母或监护人同意而收集未成年人个人信息的情况,我们只会在受到法律允许、父母或监护人明确同意或者保护未成年人所必要的情况下使用或公开披露此信息。<br>
(三)如果我们发现自己在未事先获得可证实的父母或监护人同意的情况下收集了未成年人的个人信息,会设法尽快删除相关数据。<br>
五、 您的个人信息如何储存及在全球范围转移<br>
我们在中华人民共和国境内收集的个人信息,将存储在中华人民共和国境内。我们仅在本政策所述目的所必需期间和法律法规及监管规定的时限内保留您的个人信息。如果需要将您的个人信息转移到境外,我们将:<br>
1.另行获得您的授权同意,并履行法定程序,在符合届时适用法律的情形下使您的个人信息得到足够的保护;<br>
2.该等转移将在符合届时适用法律要求的前提下进行,即便获得您的授权但是转移方式或者目的不符合相关法律法规规定的,我们也不会进行转移。 <br>
六、本政策如何更新<br>
我们的隐私政策可能变更。未经您明确同意,我们不会削减您按照本个人信息保护政策所应享有的权利。基于为给您提供更好的服务以及随着电信业务的发展或法律法规及监管政策变化,我们可能会适时对本政策进行更新。由于我们的用户较多,如本政策发生重大变更,我们将以推送通知、发送邮件、信函、电话或者在联通智网官方网站或者您的车辆生产/经销商网站发布公告的方式来通知您。若您在本政策修订后继续使用我们的产品或服务,这表示您已充分阅读、理解并愿意受修订后的本政策约束。<br>
七、您的权利<br>
(一)删除您的个人信息<br>
在以下情形中,您可以向我们提出删除个人信息的请求:<br>
(1) 如果我们处理个人信息的行为违反法律、行政法规或与您的约定;<br>
(2) 如果您不再使用我们的产品或服务,或您注销了账号;<br>
(3) 如果我们的处理目的已实现、无法实现或者为实现处理目的不再必要;<br>
(4) 如果我们不再为您提供产品或服务,或者保存期限已届满;<br>
(5) 如果您撤回同意。<br>
当您或我们协助您成功删除相关信息后,因为适用的法律和安全技术限制,我们可能无法立即从备份系统中删除相应的信息,但我们会确保备份系统中的相应信息不会再次应用于日常业务或其他用途,并确保在备份更新时及时删除这些信息。请您知晓并理解,根据法律法规的规定,如果法律、行政法规规定的保存期限未届满,或者删除个人信息从技术上难以实现的,我们会停止除存储和采取必要的安全保护措施之外的处理。<br>
(二)响应您的上述请求<br>
如出现以上情形,您可以通过发送邮件的方式来维护自己的权益,联通智网服务邮箱:hqs-rnr@chinaunicom.cn,为保障安全,您可能需要提供相关证件或以其他方式证明您的身份。我们可能会先要求您验证自己的身份,然后再处理您的要求,我们将在15个工作日内做出答复。<br>
对于您关于用户个人信息的合理请求,我们原则上不收取费用,但对多次重复、超出合理限度的请求,我们将视情收取一定成本费用。对于那些无端重复、需要过多技术手段、给他人合法权益带来风险或者非常不切实际的请求,我们可能会予以拒绝。在以下情形中,按照法律法规及监管政策要求,我们将无法响应您的请求:<br>
1.与国家安全、国防安全直接相关的;<br>
2.与公共安全、公共卫生、重大公共利益直接相关的;<br>
3.与犯罪侦查、起诉、审判和判决执行等直接相关的;<br>
4.有充分证据表明您存在主观恶意或滥用权利的;<br>
5.响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的;<br>
6.涉及商业秘密的。<br>
八、如何联系我们<br>
公司名称:联通智网科技股份有限公司京ICP备16005440号-5<br>
地址:北京市密云区兴盛南路8号院2号楼106室-266<br>
联系电话:400-665-2200<br>
</div>
<div class="btn-group">
<van-button type="info" round class="btn" @click="toLoginIn">同意</van-button>
</div>
</div>
</template>
<script>
export default {
name: 'Agreement',
methods: {
toLoginIn() {
this.$router.go(-1)
}
}
}
</script>
<style lang="scss" scoped>
.agreement {
text-align: left;
.content {
padding: 24px 24px 240px;
line-height: 1.4;
}
.btn-group {
position: fixed;
padding: 20px 0;
bottom: 0;
left: 50%;
margin-left: -375px;
width: 750px;
background: #f9f9f9;
}
.btn {
margin: 0 auto;
display: block;
width: 686px;
height: 96px;
background-image: linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%);
}
}
</style>
<template>
<div class="page-rnr-field-wrap">
<slot />
<div class="page-rnr-field-error">
<div v-show="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 {
padding-bottom: 30px;
padding-top: 12px;
.page-rnr-field-error-message {
color: #FF4C4F;
font-size: 20px;
font-weight: 400;
height: 28px;
padding-left: 75px;
position: relative;
font-weight: 400;
}
}
}
</style>
<template>
<div class="login">
<div class="container">
<div class="ml28">
<img v-if="logo" :src="logo" alt="logo" class="logo">
<img :src="require('@/assets/images/title.svg')" class="title" alt="">
</div>
<div class="content">
<van-cell-group>
<ErrorWrap :error="error.phone !== ''" :error-message="error.phone">
<van-field
v-model="phone"
:left-icon="require('@/assets/icon/icon_telephone.png')"
placeholder="请输入手机号"
type="tel"
clearable/>
</ErrorWrap>
</van-cell-group>
<van-cell-group>
<ErrorWrap :error="error.captcha !== ''" :error-message="error.captcha">
<div class="flex">
<van-field
v-model="captcha"
:left-icon="require('@/assets/icon/icon_code.png')"
placeholder="请输入验证码"
clearable
class="code-input"/>
<div><img :src="codeImg" class="code-img" alt="验证码" @click="getNewGraphic"></div>
</div>
</ErrorWrap>
</van-cell-group>
<van-cell-group>
<ErrorWrap :error="error.sms !== ''" :error-message="error.sms">
<van-field
v-model="sms"
:left-icon="require('@/assets/icon/icon_msg_code.png')"
maxlength="6"
placeholder="请输入短信验证码"
type="number"
clearable>
<div slot="button" class="send-message" >
<span v-if="!countDown" @click="getMessage">获取验证码</span>
<span v-else>{{ timeCount }}s</span>
</div>
</van-field>
</ErrorWrap>
</van-cell-group>
<van-checkbox v-model="readAgreement" class="agreement">
<span class="agreement-text" >我已阅读并同意<a @click="toAgreement($event, 'LoginAgreement')">实名制登记用户协议、隐私政策</a></span>
</van-checkbox>
<van-checkbox v-if="!typeToB" v-model="readUserInfoAgreement" class="agreement">
<span class="agreement-text" >我已阅读并同意<a @click="toAgreement($event, 'RnrPersonUserInfoAgreement')">个人信息保护政策共享条款</a></span>
</van-checkbox>
<van-button type="info" class="login-btn" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="logIn">登录</van-button>
</div>
</div>
</div>
</template>
<script>
import { SET_LOGIN_USER_INFO } from '@/store/modules/user'
import { getGraphicToB, getSmsToB, loginToB } from '@/api/loginToB'
import { getGraphic, getSms, login } from '@/api/loginToC'
import { Toast } from 'vant'
import { setToken } from '@/utils/auth'
import ErrorWrap from './components/ErrorWrap.vue'
export default {
name: 'Login',
components: {
ErrorWrap
},
data() {
return {
logo: this.$store.getters.logo,
phone: '', // 手机号
captcha: '', // 图片验证码
sms: '', // 短信验证码
readAgreement: false,
readUserInfoAgreement: false,
error: {
phone: '',
captcha: '',
sms: ''
},
timeCount: 60, // 短信倒计时
countDown: false, // 是否倒计时
codeImg: '', // 图形验证码图片
requestId: '', // 图形验证码对应id
validateList: { // 输入框验证规则
'phone': [
{
validate: '',
error: '手机号不能为空'
},
{
validate: /^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/,
error: '手机号格式有误'
}
],
'captcha': [{
validate: '',
error: '验证码不能为空'
}],
'sms': [{
validate: '',
error: '短信验证码不能为空'
}]
}
}
},
computed: {
urlName() {
const urlName = (this.$route.params.name || '').toLowerCase()
return urlName === 'home' ? 'business' : urlName
},
typeToB() {
return this.urlName === 'business'
},
platformSource() {
return 'H5'
}
},
created() {
// 获取图形验证码
this.getNewGraphic()
},
methods: {
// 获取图形验证码
getNewGraphic() {
(this.typeToB ? getGraphicToB : getGraphic)({
platformSource: this.platformSource
}).then(res => {
this.codeImg = res.captchaImg
this.requestId = res.requestId
})
},
// 获取短信验证码
getMessage() {
const flag = this.validateRule({
'phone': this.validateList.phone,
'captcha': this.validateList.captcha
})
if (flag) {
// 调接口
(this.typeToB ? getSmsToB : getSms)({
phone: this.phone,
captchaImage: this.captcha, // captcha: this.captcha,
requestId: this.requestId,
platformSource: this.platformSource
}).then(res => {
// 倒计时
this.countsDown()
Toast('验证码发送成功!')
}).catch(() => {
this.getNewGraphic()
})
}
},
// 倒计时
countsDown() {
this.countDown = true
const timer = setInterval(() => {
this.timeCount--
if (this.timeCount < 0) {
clearInterval(timer)
this.timeCount = 60
this.countDown = false
}
}, 1000)
},
// 登录
async logIn() {
// 登录验证
const flag = this.validateRule(this.validateList)
if (flag) {
if (!this.readAgreement) {
Toast('请勾选用户协议')
return
}
if (!this.readUserInfoAgreement && !this.typeToB) {
Toast('请勾选个人信息共享条款')
return
}
// 开始登录
const res = await (this.typeToB ? loginToB : login)({
phone: this.phone,
sms: this.sms,
platformSource: this.platformSource
})
// 设置token
const token = res.accessToken || res.access_token
setToken(token)
// 开始commit数据
this.$store.commit(`user/${SET_LOGIN_USER_INFO}`, {
token,
organId: res.organId,
organName: res.organName,
type: this.typeToB ? 'B' : 'C'
})
// 跳转页面
this.$router.push({ name: this.typeToB ? 'Home' : 'UserHome' })
// if (this.typeToB) {
// this.$router.push({ name: 'Home' })
// } else {
// this.$router.push({ name: res.agreement ? 'UserHome' : 'RnrPersonUserInfoAgreement', query: { tenantNo: this.tenantNo }})
// }
}
},
// 通用输入框验证
validateRule(rule) {
var flag = true
Object.keys(rule).forEach(key => {
var errorList = []
rule[key].map(x => {
if (x.validate === '') {
if (this[key] === '') errorList.push(x.error)
} else {
if (!x.validate.test(this[key])) errorList.push(x.error)
}
})
console.log(errorList, ' errorList')
this.error[key] = errorList[0] ? errorList[0] : ''
if (errorList.length > 0) flag = false
})
return flag
},
toAgreement(e, name) {
e.stopPropagation()
this.$router.push({ name })
}
}
}
</script>
<style lang="scss">
.login {
background: url(../../assets/images/bg_login.png) 0 0 no-repeat;
background-size: 100% 100%;
height: 100vh;
.container {
padding: 0 32px;
.ml28 {
margin-left: 28px;
padding: 170px 0 0;
}
.logo {
width: auto;
height: 48px;
}
.title {
margin-top: 87px;
width: 347px;
height: 47px;
display: block;
}
.content {
margin-top: 96px;
.flex {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.van-cell-group {
border-radius: 96px;
background-color: rgba(255, 255, 255, 0);
.van-field {
border-radius: 96px;
height: 96px;
align-items: center;
.van-icon__image {
width: 38px;
height: 38px;
}
input {
font-size: 30px;
color: #242424;
}
}
.code-input {
// width: 490px;
margin-right: 16px;
}
.code-img {
width: 180px;
height: 80px;
}
}
.van-hairline--top-bottom::after, .van-hairline-unset--top-bottom::after {
border: none;
}
// .van-checkbox__icon, .van-checkbox__icon--checked {
// height: 30px;
// .van-icon{
// width: 30px;
// height: 30px;
// }
// .van-icon-success::before {
// width: 25px;
// height: 0px;
// }
// }
.agreement {
margin-top: 10px;
}
.agreement-text {
font-size: 24px;
color: #242424;
line-height: 30px;
font-weight: 400;
a {
color: #0761F3;
}
}
.login-btn {
width: 686px;
height: 96px;
margin: 88px auto 0;
display: block;
}
.send-message {
font-size: 30px;
color: #0761F3;
font-weight: 400;
}
}
}
}
</style>
<template>
<Page class="page-rnr-person-agreement" title="入网协议">
<div class="page-rnr-main-card">
<van-loading v-if="!notificationUrl" class="global-loading" color="#0094ff" />
<Painter ref="painterRef" v-else>
<img crossOrigin :src="notificationUrl">
<img :src="signUrl" class="fixed-right">
<h1>* 附件</h1>
<div v-for="vin in enterpriseMarkup.iccidList" :key="vin.vin">
<p>VIN: {{ vin.vin }}</p>
<p>ICCID: {{ (vin.iccidList || []).join(', ') }}</p>
<p style="border-top: 1px solid;" />
</div>
</Painter>
</div>
<div class="page-rnr-sign-wrapper">
<div v-if="signUrl" class="page-rnr-sign-fill" @click="openSign">
<img :src="signUrl" class="page-rnr-sign-img">
<div class="page-rnr-sign-btn">重签</div>
</div>
<div v-else class="page-rnr-sign" @click="openSign">点击在此处签名</div>
</div>
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button :disabled="btnDisable" :loading="loading" type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
<Sign :visible.sync="showSign" @submit="handleSignChange" />
<!-- <PhoneVerify :visible.sync="showSms" :customer="true" :loading="loading" @submit="handleToNext" /> -->
</Page>
</template>
<script>
import Painter from '@/components/Painter'
import { mapGetters } from 'vuex'
import signMixin from '@/minxin/sign'
import Page from '@/components/Page'
import PhoneVerify from '@/components/PhoneVerify'
import { DISPATCH_INIT_PROTOCAL } from '@/store/modules/markup'
import { fileUpload, submitEnterpriseRnr } from '@/api/markup'
export default {
name: 'MarkupEnterpriseAgreement',
components: { Page, PhoneVerify, Painter },
mixins: [signMixin],
data() {
return {
loading: false,
showSms: false,
contractPic: [],
notificationUrl: ''
}
},
computed: {
...mapGetters(['enterpriseMarkup'])
},
async mounted() {
// 查询协议数据
await this.$store.dispatch(`markup/${DISPATCH_INIT_PROTOCAL}`)
// 当前请求到的协议数据
const { protocalMarkup } = this.$store.getters
// 协议链接
this.notificationUrl = protocalMarkup[window.os.isTablet ? 'companyAgreementVertical' : 'companyAgreementHorizontal'].fileUrl
},
methods: {
/**
* 调到到下一页:腾讯的实名认证平台
*/
async handleToNext() {
// 呈现loading状态
this.loading = true
try {
// 开始绘制canvas
const file = await this.$refs.painterRef.draw()
// 开始导出图片
const { uuid } = await fileUpload({ file })
// 取出数据
const enterpriseRnr = {
...(this.$store.getters.enterpriseMarkup || {}),
contractPic: [uuid],
corporationPhone: this.$store.getters.loginUser.phone,
verificationCode: ''
}
// 开始提交数据
const { h5LivenessUrl } = await submitEnterpriseRnr({
...enterpriseRnr
})
// 跳转到活体认证页面
window.location.href = h5LivenessUrl
} catch (error) {
console.error(error)
}
// 关闭loading状态
this.loading = false
},
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
}
}
}
</script>
<style lang="scss">
.page-rnr-person-agreement {
.page-rnr-main {
margin-top: 30px;
.page-rnr-main-card {
padding: 24px 24px 168px;
height: calc(100vh - 450px);
overflow: auto;
.page-rnr-main-canvas {
width: 100%;
}
}
.page-rnr-sign-wrapper {
background: #ffffff;
bottom: 196px;
padding: 24px 32px;
position: fixed;
left: 24px;
right: 24px;
.page-rnr-sign {
align-items: center;
background: #F7F8FA;
border: 1px dashed rgba(7, 97, 243, 1);
border-radius: 12px;
color: #0761F3;
display: flex;
font-size: 24px;
font-weight: 400;
height: 120px;
justify-content: center;
}
.page-rnr-sign-fill {
align-items: center;
background: #F7F8FA;
border-radius: 12px;
bottom: 148px;
display: flex;
height: 120px;
justify-content: space-between;
padding: 0px 48px 0px 132px;
.page-rnr-sign-img {
width: auto;
height: 100px;
}
.page-rnr-sign-btn {
color: #0761F3;
font-size: 24px;
font-weight: 400;
}
}
}
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
height: 96px;
}
}
}
}
</style>
<template>
<Page title="车卡信息" :show-operation="vinList.length > 0">
<div class="page-rnr-main-card">
<ScanCard :hide-scan="true" :success-num="successNum" :fail-num="failNum" :total="vinList.length" @add="handleOpenVin" @scan="handleScanVin">
<div v-if="vinList.length > 0" class="page-rnr-main-vin-wrapper" slot="content">
<ErrorWrap v-for="(vin, index) in vinList" :key="vin.vin" :error="!vin.result" :error-message="vin.errorMsg">
<van-field
:value="vin.vin"
:label="index + 1"
readonly
input-align="right"
@click="handleEditVin(index)"
>
<template #button>
<i class="imageicon imageicon-del" @click.stop="handleDeleteVin(index)" />
</template>
</van-field>
</ErrorWrap>
</div>
</ScanCard>
</div>
<template #slot-operation>
<van-button :loading="loading" type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" block @click="handleToNext">下一步</van-button>
</template>
<InputActionSheet :visible.sync="sheet.show" :title="sheet.title" :data="sheet.data" :rules="{ vin: 'vin' }" @submit="handleConfirmVin" />
</Page>
</template>
<script>
import { SET_ENTERPRISE_CARD } from '@/store/modules/markup'
import { checkVin } from '@/api/markup'
import Page from '@/components/Page'
import ScanCard from '@/components/ScanCard'
import InputActionSheet from '@/components/InputActionSheet'
import { Toast } from 'vant'
import ErrorWrap from '@/components/ErrorWrap'
export default {
name: 'MarkupEnterpriseCard',
components: { Page, ScanCard, InputActionSheet, ErrorWrap },
data() {
return {
sheet: {
show: false,
title: '',
data: ''
},
successNum: 0,
failNum: 0,
loading: false,
showOperation: false,
vinList: []
}
},
methods: {
/**
* 打开vin码输入的弹框
*/
handleOpenVin() {
// 弹出框
this.sheet.title = '添加VIN码'
this.sheet.show = true
this.sheet.data = ''
this.sheet.vinIndex = -1
},
/**
* 编辑当前vin码
* @param index 当前vin码所处的索引
*/
handleEditVin(index) {
// 弹出框
this.sheet.title = '修改VIN码'
this.sheet.show = true
this.sheet.vinIndex = index
this.sheet.data = this.vinList[index].vin
},
/**
* 删除指定所有的vin号
* @param index 删除行数据
*/
handleDeleteVin(index) {
this.vinList.splice(index, 1)
},
/**
* 跳转到下一页
*/
async handleToNext() {
// 展示loading框
this.loading = true
try {
// iccid的集合
const iccidList = []
// 当前输入的vin码
const vinList = this.vinList.map(vin => {
// 重置校验结果
vin.result = true
vin.errorMsg = ''
return vin.vin
})
// 开始校验数据
const { checkList } = await checkVin({ vinList })
// 校验失败的,将错误信息赋值给原来的组件
const failVinList = checkList.filter(vin => {
this.vinList.forEach(vinItem => {
if (vinItem.vin === vin.vin) {
vinItem.result = vin.checkResult
vinItem.errorMsg = vin.errorMsg
iccidList.push({
vin: vin.vin,
iccidList: vin.iccidList
})
}
})
return !vin.checkResult
})
// 校验失败的vin数量
this.failNum = failVinList.length
// 成功的vin数量
this.successNum = vinList.length - this.failNum
// 如果车卡关系校验有失败的
if (failVinList.length > 0) {
Toast('验证失败,请重新输入')
this.loading = false
return
}
// 将值保存到store中
this.$store.commit(`markup/${SET_ENTERPRISE_CARD}`, { vinList, iccidList })
// 开始跳转页面
this.$router.push({ name: 'MarkupEnterpriseCert' })
} catch (error) {
console.error(error)
}
this.loading = false
},
/**
* 扫码添加的vin码
* @param vin 待添加的vin
*/
handleScanVin(vin) {
// 设置当前索引是-1
this.sheet.vinIndex = -1
// 添加一个vin
this.handleConfirmVin(vin)
},
/**
* 同步用户输入的vin码
* @param vin 当前输入的vin码
*/
handleConfirmVin(vin) {
// 当前vin码集合
const { vinIndex } = this.sheet
// 如果当前是编辑
if (vinIndex >= 0) {
// 重新赋值
this.vinList[vinIndex].vin = vin
// 清空之前的校验
this.vinList[vinIndex].result = true
this.vinList[vinIndex].errorMsg = ''
} else {
this.vinList.push({
result: true,
vin,
errorMsg: ''
})
}
}
}
}
</script>
<style lang="scss">
.page-rnr {
.component-scan-code {
.van-cell__title {
padding-left: 32px;
}
.imageicon-del {
margin-bottom: -6px;
margin-left: 64px;
}
}
.page-rnr-main-vin-wrapper {
max-height: calc(100vh - 720px);
overflow: auto;
}
.page-rnr-main-fixed-btn-group {
.page-rnr-main-btn-next {
height: 96px;
}
}
}
</style>
<template>
<Page class="page-rnr-enterprise-file" title="企业证件信息">
<template #slot-before-main>
<div class="page-rnr-person-file-tip">文件类型为图片文件,允许支持的照片格式包括bmp、jpg/jpeg、png,小于500K。</div>
</template>
<div class="page-rnr-main-card">
<ErrorWrap :error-message="errorMessage" :error="!!errorMessage">
<van-cell id="contractPic" :required="true" title="证件信息" class="van-field">
<template #label>
<BridgeUpload
:max-count="3"
:compress="{}"
:security="true"
:customer="true"
v-model="licenseImages"
>
<div class="page-rnr-main-card-upload-normal">
<div class="icon-upload-camera" />
</div>
</BridgeUpload>
</template>
</van-cell>
</ErrorWrap>
</div>
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
</Page>
</template>
<script>
import { SET_ENTERPRISE_CERT_IMAGE } from '@/store/modules/markup'
import Page from '@/components/Page'
import ErrorWrap from '@/components/ErrorWrap'
import BridgeUpload from '@/components/BridgeUpload'
import { Toast } from 'vant'
import { validate } from '@/utils/validate'
export default {
name: 'MarkupEnterpriseCertImage',
components: { Page, BridgeUpload, ErrorWrap },
data() {
return {
errorMessage: '',
licenseImages: []
}
},
methods: {
/**
* 调到到下一页:腾讯的实名认证平台
*/
handleToNext() {
// 取出企业证件照片
const licenseImages = this.licenseImages
// 如果当前有字段没校验通过
if (licenseImages.length === 0) {
this.errorMessage = '请上传照片'
return
}
// 清空错误信息
this.errorMessage = ''
// 将当前数据保存到缓存中
this.$store.commit(`markup/${SET_ENTERPRISE_CERT_IMAGE}`, { licenseImages })
// 跳转到责任人页面
this.$router.push({ name: 'MarkupEnterpriseIdcard' })
},
/**
* 校验用户输入
*/
validate(ruleKeys = ['certPic']) {
// 错误的字段,错误的信息
const [failField, failMessage] = validate(this.formData, ruleKeys)
// 如果当前有错误信息
ruleKeys.forEach(key => {
failField === key && Toast.fail(failMessage)
})
return failField
},
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
}
}
}
</script>
<style lang="scss">
.page-rnr-enterprise-file {
.page-rnr-main {
margin-top: 0px;
}
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
margin-left: 40px;
height: 96px;
}
}
.icon-upload-camera {
background-image: url(../../../assets/rnr/ico_camera.png);
background-size: 100% 100%;
position: absolute;
width: 80px;
height: 80px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.page-rnr-main-card-upload-normal {
background-image: url(../../../assets/rnr/ico_pic.png);
background-color: #F7F8FA;
background-size: 196px 196px;
background-repeat: no-repeat;
background-position: center;
border-radius: 8px;
width: 196px;
height: 196px;
}
.page-rnr-main-btn {
font-size: 32px !important;
&.disabled {
color: #CACACA !important;
}
}
.van-cell__label {
.page-rnr-flex {
display: flex;
justify-content: space-between;
}
}
.page-rnr-person-file-tip {
font-size: 24px;
color: rgba(255, 255, 255, 0.6);
font-weight: 400;
padding: 14px 48px 18px;
}
.van-dialog {
box-sizing: border-box;
width: 685px;
padding: 0 40px 50px;
.van-dialog__header {
padding: 60px 0 12px;
}
.van-field__button {
padding-left: 30px;
.page-rnr-main-btn {
color: #0761F3;
font-size: 32px;
font-weight: 400;
}
}
.page-rnr-main-btn-confirm {
margin-top: 44px;
}
}
}
</style>
<template>
<Page title="企业信息">
<EnterpriseCert ref="certRef" />
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
</Page>
</template>
<script>
import { SET_ENTERPRISE_CERT } from '@/store/modules/markup'
import Page from '@/components/Page'
import EnterpriseCert from '@/components/EnterpriseCert'
export default {
name: 'EnterpriseCertInfo',
components: { Page, EnterpriseCert },
methods: {
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
},
handleToNext() {
// 开始校验
try {
const formData = this.$refs.certRef.validate()
// 将值保存到缓存中
this.$store.commit(`markup/${SET_ENTERPRISE_CERT}`, formData)
// 开始跳转页面
this.$router.push({ name: 'MarkupEnterpriseCertImage' })
} catch (error) {
console.error(error)
}
}
}
}
</script>
<style lang="scss">
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
margin-left: 40px;
height: 96px;
}
}
</style>
<template>
<Page class="page-rnr-enterprise-file" title="文件信息">
<template #slot-before-main>
<div class="page-rnr-person-file-tip">文件类型为图片文件,允许支持的照片格式包括bmp、jpg/jpeg、png,小于500K。</div>
</template>
<div class="page-rnr-main-card">
<ErrorWrap :error-message="inputError.authorizationLetterPic" :error="!!inputError.authorizationLetterPic">
<van-cell id="authorizationLetterPic" :required="true" title="企业实名认证授权书" class="van-field">
<template #label>
<BridgeUpload v-model="formData.authorizationLetterPic" :compress="{ quality: 80 }" :customer="true">
<div class="page-rnr-main-card-upload-normal">
<div class="icon-upload-camera" />
</div>
</BridgeUpload>
</template>
</van-cell>
</ErrorWrap>
</div>
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button :loading="loading" :disabled="btnDisable" type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
</Page>
</template>
<script>
import { SET_ENTERPRISE_FILE } from '@/store/modules/markup'
import PhoneVerify from '@/components/PhoneVerify'
import Page from '@/components/Page'
import ErrorWrap from '@/components/ErrorWrap'
import BridgeUpload from '@/components/BridgeUpload'
import { fileUpload } from '@/api/markup'
import { Toast } from 'vant'
import { validate } from '@/utils/validate'
import flowMixin from '@/minxin/flow'
export default {
name: 'MarkupEnterpriseFile',
components: { Page, BridgeUpload, PhoneVerify, ErrorWrap },
mixins: [flowMixin],
data() {
return {
inputError: {},
loading: false,
formData: {
authorizationLetterPic: []
}
}
},
computed: {
/**
* 按钮是否禁用
*/
btnDisable() {
const { authorizationLetterPic } = this.formData
return authorizationLetterPic.length === 0
}
},
methods: {
/**
* 删除文件
*/
handleDeleteFile(files, { index }, key) {
this.formData[key].splice(index, 1)
},
/**
* 上传文件
* @param origin 源文件
* @param key 字段key
*/
handleUploadFile(origin, key) {
// 获取当前文件
let files = origin
// 如果当前不是数组,则将其变成数组
if (!(files instanceof Array)) {
files = [files]
}
// 将文件的uuid都展示在界面上
files.forEach(async file => {
// 文件可访问路径和文件的uuid
const { uuid } = await fileUpload({ file: file.content })
// 将文件id保存在照片中
this.formData[key].push(uuid)
// 开始校验上传字段
this.validate([key])
})
},
/**
* 调到到下一页:签名页
*/
async handleToNext({ phone, verificationCode }) {
// 校验不通过的字段
const failField = this.validate(['authorizationLetterPic'])
// 如果当前校验失败
if (failField) {
return
}
// 将当前数据保存到store中
this.$store.commit(`markup/${SET_ENTERPRISE_FILE}`, {
...this.formData,
corporationPhone: phone,
verificationCode
})
this.$router.push({ name: 'MarkupEnterpriseNotification' })
},
/**
* 校验用户输入
*/
validate(ruleKeys) {
// 错误的字段,错误的信息
const [failField, failMessage] = validate(this.formData, ruleKeys)
// 如果当前有错误信息
ruleKeys.forEach(key => {
this.$set(this.inputError, key, failField === key ? failMessage : '')
})
return failField
},
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
},
/**
* 图片超过最大限制
*/
handleOverSize() {
Toast('选择的图片超过最大限制,请重新选择')
}
}
}
</script>
<style lang="scss">
.page-rnr-enterprise-file {
.page-rnr-main {
margin-top: 0px;
}
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
height: 96px;
margin-left: 40px;
}
}
.icon-upload-camera {
background-image: url(../../../assets/rnr/ico_camera.png);
background-size: 100% 100%;
position: absolute;
width: 80px;
height: 80px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.page-rnr-main-card-upload-normal {
background-image: url(../../../assets/rnr/ico_pic.png);
background-color: #F7F8FA;
background-size: 196px 196px;
background-repeat: no-repeat;
background-position: center;
border-radius: 8px;
width: 196px;
height: 196px;
}
.page-rnr-main-btn {
font-size: 32px !important;
&.disabled {
color: #CACACA !important;
}
}
.van-cell__label {
.page-rnr-flex {
display: flex;
justify-content: space-between;
}
}
.page-rnr-person-file-tip {
font-size: 24px;
color: rgba(255, 255, 255, 0.6);
font-weight: 400;
padding: 14px 48px 18px;
}
}
</style>
<template>
<Page title="责任人证件信息" class="page-markup-enterprise-idcard">
<template #slot-before-main>
<div class="page-rnr-person-file-tip">文件类型为图片文件,允许支持的照片格式包括bmp、jpg/jpeg、png,小于500K。</div>
</template>
<div class="page-rnr-main-card">
<!-- 身份证选择上传 -->
<FormRender
ref="formRenderRef"
v-if="formData.certType === 'IDCARD'"
v-model="formData"
:schema="SCHEMA1"
@change="handleFormDataChange"
/>
<!-- 其他证件照片的选择上传 -->
<FormRender
ref="formRenderRef"
v-else
v-model="formData"
:schema="SCHEMA2"
@change="handleFormDataChange"
/>
</div>
<MustKnow v-if="formData.certType === 'IDCARD'" />
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button :disabled="!formData.certPic[0] || !formData.certPic[1]" type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
</Page>
</template>
<script>
import cache from '@/utils/cache'
import { cuscImageUpload } from '@/api/markup'
import Page from '@/components/Page'
import MustKnow from '@/components/MustKnow'
import FormRender from '@/components/FormRender'
import flowMixin from '@/minxin/flow'
import { SET_ENTERPRISE_PAPER } from '@/store/modules/markup'
export default {
name: 'MarkupEnterpriseIdcard',
components: { Page, FormRender, MustKnow },
mixins: [flowMixin],
data() {
const { CARD_TYPE } = this.$store.getters.dict
return {
formData: {
certType: 'IDCARD',
certPic: []
},
SCHEMA1: {
certType: {
label: '证件类型',
type: 'rnr-select',
rules: [
{ required: true }
],
options: CARD_TYPE
},
certPic: {
type: 'rnr-idcard',
compress: {},
api: file => cuscImageUpload({ file }).then(resp => resp.uuid),
rules: [
{ required: true },
{
validator(value) {
// 如果人像面没有上传
if (!value[0] || value[0].length === 0) {
return { result: false, message: '请上传身份证人像面' }
}
if (!value[1] || value[1].length === 0) {
return { result: false, message: '请上传身份证国徽面' }
}
return true
}
}
]
}
},
SCHEMA2: {
certType: {
label: '证件类型',
type: 'rnr-select',
rules: [
{ required: true }
],
options: CARD_TYPE
},
certPic: {
type: 'rnr-file',
size: 'large',
compress: {},
maxCount: 2,
api: file => cuscImageUpload({ file }).then(resp => resp.uuid),
rules: [
{ required: true },
{
validator(value) {
// 如果人像面没有上传
if (!value[0] || value[0].length === 0) {
return { result: false, message: '请上传证件信息正面' }
}
if (!value[1] || value[1].length === 0) {
return { result: false, message: '请上传证件信息反面' }
}
return true
}
}
]
}
}
}
},
methods: {
/**
* 表单项改变方法
* @param value 当前双向绑定的值
* @param key 当前改变的字段key
*/
handleFormDataChange(value, key) {
if (key === 'certType') {
this.formData.certPic = []
}
},
/**
* 调到到下一页:腾讯的实名认证平台
*/
async handleToNext() {
// 开始校验数据
this.$refs.formRenderRef.validate()
// 取出双向绑定的身份证照片id
const { certType, certPic } = this.formData
// 将当前数据保存到store中
this.$store.commit(`markup/${SET_ENTERPRISE_PAPER}`, { corporationCertType: certType, corporationPhotoPic: certPic.map(pic => pic.uuid) })
// 当前证件照片的识别key
const CERT_PIC_STORAGE_KEY = `cert-pic-${Date.now()}`
// 将证件信息保存到缓存中
cache.set(CERT_PIC_STORAGE_KEY, certPic, { noStorage: true })
// 跳转到基本信息页
this.$router.push({ name: 'MarkupEnterpriseUser', query: { certPicKey: CERT_PIC_STORAGE_KEY }})
},
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
}
}
}
</script>
<style lang="scss">
.page-markup-enterprise-idcard {
padding-bottom: 72px;
.page-rnr-main {
margin-top: 0px !important;
.rnr-idcard, .rnr-file {
padding: 48px 0;
}
}
.page-rnr-person-file-tip {
font-size: 24px;
color: rgba(255, 255, 255, 0.6);
font-weight: 400;
padding: 14px 48px 18px;
}
}
</style>
<template>
<Page class="page-rnr-person-agreement" title="责任告知书">
<div class="page-rnr-main-card">
<van-loading v-if="!notificationUrl" class="global-loading" color="#0094ff" />
<Painter ref="painterRef" v-else>
<img crossOrigin :src="notificationUrl">
<img :src="signUrl" class="fixed-right">
</Painter>
</div>
<div class="page-rnr-sign-wrapper">
<div v-if="signUrl" class="page-rnr-sign-fill" @click="showSign = true">
<img :src="signUrl" class="page-rnr-sign-img">
<div class="page-rnr-sign-btn">重签</div>
</div>
<div v-else class="page-rnr-sign" @click="openSign">点击在此处签名</div>
</div>
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button :disabled="btnDisable" :loading="loading" type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
<Sign :visible.sync="showSign" @submit="handleSignChange" />
</Page>
</template>
<script>
import Painter from '@/components/Painter'
import { fileUpload } from '@/api/markup'
import { SET_ENTERPRISE_DUTY_PIC, DISPATCH_INIT_PROTOCAL } from '@/store/modules/markup'
import Page from '@/components/Page'
import signMixin from '@/minxin/sign'
import flowMixin from '@/minxin/flow'
export default {
name: 'MarkupEnterpriseNotification',
components: { Page, Painter },
mixins: [flowMixin, signMixin],
data() {
return {
loading: false,
showSms: false,
notificationUrl: ''
}
},
async mounted() {
// 查询协议数据
await this.$store.dispatch(`markup/${DISPATCH_INIT_PROTOCAL}`)
// 当前请求到的协议数据
const { protocalMarkup } = this.$store.getters
// 协议链接
this.notificationUrl = protocalMarkup[window.os.isTablet ? 'realnameNoticeVertical' : 'realnameNoticeHorizontal'].fileUrl
},
methods: {
/**
* 调到到下一页:腾讯的实名认证平台
*/
async handleToNext(formData) {
// 展示loading框
this.loading = true
try {
// 开始绘制canvas
const file = await this.$refs.painterRef.draw()
// 开始导出图片
const { uuid } = await fileUpload({ file })
// 开始保存数据
this.$store.commit(`markup/${SET_ENTERPRISE_DUTY_PIC}`, {
dutyPic: [uuid]
})
// 跳转到
this.$router.push({ name: 'MarkupEnterpriseAgreement' })
} catch (error) {
console.log(error)
}
// 关闭loading框
this.loading = false
},
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
}
}
}
</script>
<style lang="scss">
.page-rnr-person-agreement {
.page-rnr-main {
margin-top: 30px;
.page-rnr-main-card {
padding: 24px 24px 168px;
height: calc(100vh - 450px);
overflow: auto;
.page-rnr-main-canvas {
width: 100%;
}
}
.page-rnr-sign-wrapper {
background: #ffffff;
bottom: 196px;
padding: 24px 32px;
position: fixed;
left: 24px;
right: 24px;
.page-rnr-sign {
align-items: center;
background: #F7F8FA;
border: 1px dashed rgba(7, 97, 243, 1);
border-radius: 12px;
color: #0761F3;
display: flex;
font-size: 24px;
font-weight: 400;
height: 120px;
justify-content: center;
}
.page-rnr-sign-fill {
align-items: center;
background: #F7F8FA;
border-radius: 12px;
bottom: 148px;
display: flex;
height: 120px;
justify-content: space-between;
padding: 0px 48px 0px 132px;
.page-rnr-sign-img {
width: auto;
height: 100px;
}
.page-rnr-sign-btn {
color: #0761F3;
font-size: 24px;
font-weight: 400;
}
}
}
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
margin-left: 40px;
height: 96px;
}
}
}
}
</style>
<template>
<Page class="page-rnr-enterprise-file" title="责任人证件信息">
<template #slot-before-main>
<div class="page-rnr-person-file-tip">文件类型为图片文件,允许支持的照片格式包括bmp、jpg/jpeg、png,小于500K。</div>
</template>
<div class="page-rnr-main-card">
<ErrorWrap :error-message="inputError.certPic" :error="!!inputError.certPic">
<van-cell id="contractPic" :required="true" title="证件信息" class="van-field">
<template #label>
<BridgeUpload
:max-count="3"
:compress="{}"
:security="true"
:customer="true"
v-model="formData.certPic"
>
<div class="page-rnr-main-card-upload-normal">
<div class="icon-upload-camera" />
</div>
</BridgeUpload>
</template>
</van-cell>
</ErrorWrap>
</div>
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
</Page>
</template>
<script>
import { SET_ENTERPRISE_IDCARD } from '@/store/modules/markup'
import Page from '@/components/Page'
import ErrorWrap from '@/components/ErrorWrap'
import BridgeUpload from '@/components/BridgeUpload'
import { validate } from '@/utils/validate'
export default {
name: 'MarkupEnterprisePaper',
components: { Page, BridgeUpload, ErrorWrap },
data() {
const { customerType } = this.$store.getters.personRnr
return {
inputError: {},
countTime: 59,
isSend: false,
showSms: false,
customerType,
formData: {
certPic: []
}
}
},
methods: {
/**
* 调到到下一页:腾讯的实名认证平台
*/
handleToNext() {
// 校验不通过的字段
const failFiled = this.validate()
// 如果当前有字段没校验通过
if (failFiled) {
return
}
// 将当前数据保存到store中
this.$store.commit(`markup/${SET_ENTERPRISE_IDCARD}`, { corporationPhotoPic: this.formData.certPic })
// 开始跳转页面
this.$router.push({ name: 'MarkupEnterpriseFile' })
},
/**
* 校验用户输入
*/
validate(ruleKeys = ['certPic']) {
// 错误的字段,错误的信息
const [failField, failMessage] = validate(this.formData, ruleKeys)
// 如果当前有错误信息
ruleKeys.forEach(key => {
this.$set(this.inputError, key, failField === key ? failMessage : '')
})
return failField
},
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
}
}
}
</script>
<style lang="scss">
.page-rnr-enterprise-file {
.page-rnr-main {
margin-top: 0px !important;
}
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
margin-left: 40px;
height: 96px;
}
}
.icon-upload-camera {
background-image: url(../../../assets/rnr/ico_camera.png);
background-size: 100% 100%;
position: absolute;
width: 80px;
height: 80px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.page-rnr-main-card-upload-normal {
background-image: url(../../../assets/rnr/ico_pic.png);
background-color: #F7F8FA;
background-size: 196px 196px;
background-repeat: no-repeat;
background-position: center;
border-radius: 8px;
width: 196px;
height: 196px;
}
.page-rnr-main-btn {
font-size: 32px !important;
&.disabled {
color: #CACACA !important;
}
}
.van-cell__label {
.page-rnr-flex {
display: flex;
justify-content: space-between;
}
}
.page-rnr-person-file-tip {
font-size: 24px;
color: rgba(255, 255, 255, 0.6);
font-weight: 400;
padding: 14px 48px 18px;
}
.van-dialog {
box-sizing: border-box;
width: 685px;
padding: 0 40px 50px;
.van-dialog__header {
padding: 60px 0 12px;
}
.van-field__button {
padding-left: 30px;
.page-rnr-main-btn {
color: #0761F3;
font-size: 32px;
font-weight: 400;
}
}
.page-rnr-main-btn-confirm {
margin-top: 44px;
}
}
}
</style>
<template>
<van-loading v-if="status === STATUS.PENDING" vertical color="#0094ff" class="loading-container">加载中...</van-loading>
<section v-else class="page-rnr-enterprise">
<main class="page-rnr-result-content">
<div :class="[STATUS.FAIL, STATUS.TX_VALID_FAIL].includes(status) ? 'icon-result-fail' : 'icon-result-success'" class="page-rnr-result-icon" />
<div v-if="status === STATUS.FAIL" class="page-rnr-result-title">认证失败</div>
<div v-else-if="status === STATUS.MAN_WORK" class="page-rnr-result-title">已转入人工审核,请注意查询认证进度</div>
<div v-else-if="status === STATUS.TX_VALID_FAIL" class="page-rnr-result-title">腾讯活体认证失败</div>
<div v-else class="page-rnr-result-title">恭喜您认证成功</div>
</main>
<div class="page-rnr-main-fixed-btn-group">
<van-button v-if="status === STATUS.TX_VALID_FAIL" type="primary" class="page-rnr-main-btn-next" round block color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToTxVerify">重新认证</van-button>
<van-button v-else type="primary" class="page-rnr-main-btn-next" round block color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNextPage(status === STATUS.FAIL ? 'MarkupEnterpriseCard' : 'UserHome')">{{ status === STATUS.FAIL ? '重新认证' : '返回首页' }}</van-button>
</div>
</section>
</template>
<script>
import { enterpriseLivenessCallback } from '@/api/markup'
// 状态
const STATUS = {
// 加载中
PENDING: -1,
// 腾讯活体认证失败
TX_VALID_FAIL: 2,
// 成功
SUCCESS: 0,
// 人工审核中
MAN_WORK: 1,
// 失败
FAIL: 3
}
export default {
name: 'MarkupEnterpriseResult',
data() {
return {
STATUS,
status: STATUS.PENDING,
h5LivenessUrl: ''
}
},
activated() {
// 开始提交实名信息
this.fetchSubmitRnr(this.$route.query)
},
methods: {
/**
* 提交实名认证的接口
*/
async fetchSubmitRnr(query) {
try {
// 开始提交数据
const { status, h5LivenessUrl } = await enterpriseLivenessCallback(this.$route.query)
// 标记当前提交成功
this.status = status
// h5活体验证的链接
this.h5LivenessUrl = h5LivenessUrl
return
} catch (error) {
console.error(error)
}
// 标记当前提交失败
this.status = STATUS.FAIL
},
/**
* 跳转到活体认证
*/
handleToTxVerify() {
// 跳转到活体认证页面
window.location.href = this.h5LivenessUrl
},
/**
* 跳转到下一个页面
* @param pageName 页面名称
*/
handleToNextPage(pageName) {
this.$router.push({ name: pageName })
}
}
}
</script>
<style lang="scss">
.page-rnr-enterprise {
.page-rnr-result-content {
padding-top: 72px;
.page-rnr-result-icon {
background-size: 100% 100%;
background-repeat: no-repeat;
width: 648px;
height: 490px;
margin: 0 auto;
&.icon-result-success {
background-image: url(../../../assets/rnr/ico_success.png);
}
&.icon-result-fail {
background-image: url(../../../assets/rnr/ico_fail.png);
}
}
.page-rnr-result-title {
color: #212026;
font-size: 36px;
font-weight: 500;
text-align: center;
margin-top: 24px;
}
}
.page-rnr-main-fixed-btn-group {
background-color: #ffffff;
bottom: 0px;
height: 148px;
padding: 20px 32px;
position: fixed;
left: 0px;
right: 0px;
.page-rnr-main-btn-next {
font-size: 30px;
height: 96px;
}
}
}
.loading-container {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: fixed;
}
</style>
<template>
<Page title="责任人身份信息">
<UserInfo :value="formData" ref="userRef" :cert-type="enterpriseMarkup.corporationCertType" :customer="true" />
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
</Page>
</template>
<script>
import { mapGetters } from 'vuex'
import { SET_ENTERPRISE_OWNER, SET_OCR_PIC } from '@/store/modules/markup'
import Page from '@/components/Page'
import UserInfo from '@/components/UserInfo'
export default {
name: 'MarkupEnterpriseUser',
components: { Page, UserInfo },
data() {
return {
formData: {
certAddress: '',
certExpirationDate: '',
certNumber: '',
certType: 'IDCARD',
contactAddress: '',
fullName: '',
gender: ''
}
}
},
computed: {
...mapGetters(['enterpriseMarkup'])
},
methods: {
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
},
handleToNext() {
// 开始校验
try {
const formData = this.$refs.userRef.validate()
// 将值塞到缓存中
this.$store.commit(
`markup/${SET_ENTERPRISE_OWNER}`,
Object.keys(formData).reduce((memo, key) => {
if (key === 'fullName') {
memo['corporationName'] = formData[key]
} else {
const alias = `corporation${key.slice(0, 1).toUpperCase() + key.slice(1)}`
memo[alias] = formData[key]
}
return memo
}, {})
)
// 将当前选择的证件信息放到store中
this.$store.commit(`markup/${SET_OCR_PIC}`, formData.ocrPic)
// 开始跳转页面
this.$router.push({ name: 'MarkupEnterpriseFile' })
} catch (error) {
console.error(error)
}
}
}
}
</script>
<style lang="scss">
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
margin-left: 40px;
height: 96px;
}
}
</style>
<template>
<Page class="page-rnr-person-agreement">
<template #slot-title>
<span>{{ currentFlow }}/{{ totalFlow }}</span>
<span>入网协议</span>
</template>
<div class="page-rnr-main-card">
<van-loading v-if="!notificationUrl" class="global-loading" color="#0094ff" />
<Painter ref="painterRef" v-else>
<img crossOrigin :src="notificationUrl">
<img :src="signUrl" class="fixed-right">
<h1>* 附件</h1>
<p>VIN: {{ personMarkup.vin }}</p>
<p>ICCID: {{ (personMarkup.iccidList || []).join(', ') }}</p>
</Painter>
</div>
<div class="page-rnr-sign-wrapper">
<div v-if="signUrl" class="page-rnr-sign-fill" @click="showSign = true">
<img :src="signUrl" class="page-rnr-sign-img">
<div class="page-rnr-sign-btn">重签</div>
</div>
<div v-else class="page-rnr-sign" @click="showSign = true">点击在此处签名</div>
</div>
<template #slot-operation>
<van-button type="primary" class="page-rnr-main-btn-before" round color="linear-gradient(120deg, #EDF3FF 0%, #E9F0FF 97%)" @click="handleToBefore">上一步</van-button>
<van-button :disabled="btnDisable" :loading="loading" type="primary" class="page-rnr-main-btn-next" round color="linear-gradient(120deg, #4F4CFB 0%, #376FF4 97%)" @click="handleToNext">下一步</van-button>
</template>
<Sign :visible.sync="showSign" @submit="handleSignChange" />
<!-- <PhoneVerify :visible.sync="showSms" :customer="true" :loading="loading" @submit="handleToNext" /> -->
</Page>
</template>
<script>
import Painter from '@/components/Painter'
import { mapGetters } from 'vuex'
import { DISPATCH_INIT_PROTOCAL } from '@/store/modules/markup'
import signMixin from '@/minxin/sign'
import flowMixin from '@/minxin/flow'
import Page from '@/components/Page'
import PhoneVerify from '@/components/PhoneVerify'
import { fileUpload, submitRnr } from '@/api/markup'
export default {
name: 'MarkupPersonAgreement',
components: { Page, PhoneVerify, Painter },
mixins: [signMixin, flowMixin],
data() {
return {
loading: false,
showSms: false,
contractPic: [],
notificationUrl: ''
}
},
computed: {
...mapGetters(['personMarkup'])
},
async mounted() {
// 查询协议数据
await this.$store.dispatch(`markup/${DISPATCH_INIT_PROTOCAL}`)
// 当前请求到的协议数据
const { protocalMarkup } = this.$store.getters
// 协议链接
this.notificationUrl = protocalMarkup[window.os.isTablet ? 'peopleAgreementVertical' : 'peopleAgreementHorizontal'].fileUrl
},
methods: {
/**
* 调到到下一页:腾讯的实名认证平台
*/
async handleToNext(formData) {
// 呈现loading状态
this.loading = true
try {
// 开始绘制canvas
const file = await this.$refs.painterRef.draw()
// 开始导出图片
const { uuid } = await fileUpload({ file })
// 取出数据
const personRnr = {
...(this.$store.getters.personMarkup || {}),
contractPic: [uuid],
phone: this.$store.getters.loginUser.phone,
verificationCode: ''
}
// 开始提交数据
const { h5LivenessUrl } = await submitRnr({
...personRnr
})
// 跳转到活体认证页面
window.location.href = h5LivenessUrl
} catch (error) {
console.error(error)
}
// 关闭loading状态
this.loading = false
},
/**
* 跳转到上一页
*/
handleToBefore() {
// 回退到上个页面
this.$router.back()
}
}
}
</script>
<style lang="scss">
.page-rnr-person-agreement {
.page-rnr-main {
margin-top: 30px;
.page-rnr-main-card {
padding: 24px 24px 168px;
height: calc(100vh - 450px);
overflow: auto;
.page-rnr-main-canvas {
width: 100%;
}
}
.page-rnr-sign-wrapper {
background: #ffffff;
bottom: 196px;
padding: 24px 32px;
position: fixed;
left: 24px;
right: 24px;
.page-rnr-sign {
align-items: center;
background: #F7F8FA;
border: 1px dashed rgba(7, 97, 243, 1);
border-radius: 12px;
color: #0761F3;
display: flex;
font-size: 24px;
font-weight: 400;
height: 120px;
justify-content: center;
}
.page-rnr-sign-fill {
align-items: center;
background: #F7F8FA;
border-radius: 12px;
bottom: 148px;
display: flex;
height: 120px;
justify-content: space-between;
padding: 0px 48px 0px 132px;
.page-rnr-sign-img {
width: auto;
height: 100px;
}
.page-rnr-sign-btn {
color: #0761F3;
font-size: 24px;
font-weight: 400;
}
}
}
.page-rnr-main-fixed-btn-group {
display: flex;
justify-content: space-between;
.page-rnr-main-btn-before {
color: #242424 !important;
flex: 1;
height: 96px;
}
.page-rnr-main-btn-next {
flex: 2;
height: 96px;
}
}
}
}
</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