Handy_Cao 2 gadi atpakaļ
vecāks
revīzija
a1c1cc1594

+ 3 - 2
src/packages/mobile/views/swap/detail/Index.vue

@@ -3,7 +3,7 @@
         <template #header>
             <app-navbar :title="item.goodscode + '/' + item.goodsname">
                 <template #right>
-                    <div class="button-more" @click="onListing">
+                    <div class="button-more" v-if="userStore.userType != 5" @click="onListing">
                         <span>挂牌</span>
                     </div>
                 </template>
@@ -45,7 +45,7 @@ import { useComponent } from '@/hooks/component'
 import { BuyOrSell } from '@/constants/order'
 import { queryTjmdTradeOrderDetail } from '@/services/api/swap'
 import { useFuturesStore } from '@/stores'
-import { useLoginStore } from '@/stores'
+import { useLoginStore, useUserStore } from '@/stores'
 import AppPullRefresh from '@mobile/components/base/pull-refresh/index.vue'
 import AppList from '@mobile/components/base/list/index.vue'
 
@@ -69,6 +69,7 @@ const dataList = shallowRef<Model.TjmdTradeOrderDetailRsp[]>([])
 const futuresStore = useFuturesStore()
 const quote = futuresStore.getGoodsQuote(item.refgoodsid)
 const goodsCode = computed(() => quote.value?.goodscode ?? '')
+const userStore = useUserStore()
 
 const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => onTabChange())
 

+ 1 - 1
src/packages/mobile/views/swap/detail/components/delisting/Index.vue

@@ -42,7 +42,7 @@
                     <span>{{ selectedRow.orderqty }}</span>
                 </template>
             </Field>
-            <el-form-item v-if="priceMove === 3" prop="OrderPrice" :label="selectedRow.buyorsell === 0 ? '卖出价格' : '买入价格'">
+            <el-form-item v-if="priceMode != 1" prop="OrderPrice" :label="selectedRow.buyorsell === 0 ? '卖出价格' : '买入价格'">
                 <el-input-number placeholder="请输入" :min="0.0" :step="Math.pow(10, -1*(goods?.decimalplace ?? 0))" :precision="(goods?.decimalplace ?? 2)" v-model="formData.OrderPrice" @change="calculateDelistingAmount"/>
             </el-form-item>
             <Field name="OrderQty" :rules="formRules.OrderQty" label="摘牌数量">

+ 2 - 2
src/packages/mobile/views/swap/detail/components/listing/Index.vue

@@ -127,9 +127,9 @@ const priceMove = shallowRef(EPriceMode.PRICEMODE_LIMIT)
 /// 交易下单
 const { formData, formSubmit } = useOrder()
 /// 最大基差范围上限
-const maxBasis = shallowRef(0.0)
+const maxBasis = shallowRef(1000.0)
 /// 最小基差范围下限
-const minBasis = shallowRef(0.0)
+const minBasis = shallowRef(1000.0)
 /// 估算价
 const estimateprice = shallowRef(0.0)
 // 商品盘面

+ 3 - 2
src/packages/pc/views/market/trade/swap/detail/index.vue

@@ -22,7 +22,7 @@
                         <slot name="headerRight"></slot> 
                         <el-button type="primary" @click="active = false" v-if="active">买卖大厅</el-button>
                         <el-button type="primary" @click="active = true" v-else>图表</el-button>
-                        <el-button type="primary" @click="openComponent('listing')">挂牌求购</el-button>
+                        <el-button type="primary" v-if="userStore.userType != 5" @click="openComponent('listing')">挂牌求购</el-button>
                     </div>
                 </div>
             </template>
@@ -41,7 +41,7 @@ import { useComponent } from '@/hooks/component'
 import { getRunStatusName } from '@/constants/market'
 import { useRequest } from '@/hooks/request'
 import { queryMarketRun } from '@/services/api/market'
-import { useFuturesStore } from '@/stores'
+import { useFuturesStore, useUserStore } from '@/stores'
 
 const Chart = defineAsyncComponent(() => import('@pc/components/modules/goods-detail/chart/index.vue'))
 const Order = defineAsyncComponent(() => import('./order/index.vue'))
@@ -68,6 +68,7 @@ const quote = futuresStore.getGoodsQuote(props.goodsId)
 const active = shallowRef(true)
 const goodsCode = computed(() => quote.value?.goodscode ?? '')
 const goodsId = computed(() => props.selectedRow?.goodsid ?? 0)
+const userStore = useUserStore()
 
 const { data: market, run } = useRequest(queryMarketRun, {
     params: {

+ 2 - 2
src/packages/pc/views/market/trade/swap/detail/listing/index.vue

@@ -84,9 +84,9 @@ const amount = shallowRef(0.0)
 /// 履约保证金
 const permargin = shallowRef(0.0)
 /// 最大基差范围上限
-const maxBasis = shallowRef(0.0)
+const maxBasis = shallowRef(1000.0)
 /// 最小基差范围下限
-const minBasis = shallowRef(0.0)
+const minBasis = shallowRef(-1000.0)
 /// 估算价
 const estimateprice = shallowRef(0.0)
 

+ 1 - 1
src/packages/pc/views/market/trade/swap/detail/order/delisting/index.vue

@@ -41,7 +41,7 @@
             <el-form-item label="可用资金">
                 <span>{{ accountStore.currentAccount.avaiableMoney?.toFixed(2) }}</span>
             </el-form-item>
-            <el-form-item v-if="priceMove === 3" prop="OrderPrice" :label="selectedRow.buyorsell === 0 ? '卖出价格' : '买入价格'">
+            <el-form-item v-if="priceMode != 1" prop="OrderPrice" :label="selectedRow.buyorsell === 0 ? '卖出价格' : '买入价格'">
                 <el-input-number placeholder="请输入" :min="0.0" :step="Math.pow(10, -1*(goods?.decimalplace ?? 0))" :precision="(goods?.decimalplace ?? 2)" v-model="formData.OrderPrice" @change="calculateDelistingAmount"/>
             </el-form-item>
             <el-form-item prop="OrderQty" label="摘牌数量">

+ 2 - 2
src/packages/pc/views/market/trade/swap/detail/order/index.vue

@@ -4,8 +4,8 @@
         <app-table class="sell" :data="sellList" v-model:columns="sellColumns" :row-key="rowKey"
             :expand-row-keys="expandKeys" @row-click="rowClick" showIndex>
             <!-- 展开行 -->
-            <template #expand="{ row, index }">
-                <div class="buttonbar" v-if="index === 0">
+            <template #expand="{ row }">
+                <div class="buttonbar">
                     <el-button type="primary" size="small" :disabled="row.userid === loginStore.userId"
                         @click="showComponent('delisting', row)">摘牌</el-button>
                 </div>

+ 1 - 1
src/packages/tjmd/router/index.ts

@@ -117,7 +117,7 @@ const routes: Array<RouteRecordRaw> = [
       {
         path: 'certification',
         name: 'account-certification',
-        component: () => import('@mobile/views/account/certification/Index.vue'),
+        component: () => import('../views/account/certification/Index.vue'),
       },
     ],
   },

+ 190 - 0
src/packages/tjmd/views/account/certification/Index.vue

@@ -0,0 +1,190 @@
+<template>
+    <app-view class="g-form account-certification">
+        <template #header>
+            <app-navbar title="实名认证" />
+        </template>
+        <Form ref="formRef" class="g-form__container" @submit="onCheckCardNum" :loading="loading">
+            <CellGroup inset>
+                <Field v-model="formData.username" name="username" label="姓名" placeholder="请输入用户姓名" :rules="formRules.username"
+                    :readonly="isReadonly" />
+                <!-- <Field v-model="formData.mobile" name="mobile" readonly label="手机号码" /> -->
+                <Field name="idCardType" label="证件类型" :rules="formRules.cardtype" is-link>
+                    <template #input>
+                        <app-select v-model="formData.cardtype" placeholder="请选择证件类型"
+                            :options="getAQCertificateTypeList()" :readonly="isReadonly" />
+                    </template>
+                </Field>
+                <Field v-model="formData.cardnum" name="cardnum" label="证件号码" placeholder="请输入证件号码" :rules="formRules.cardnum"
+                    :readonly="isReadonly" />
+                <Field name="cardfrontphotourl" label="证件正面照片" :rules="formRules.cardfrontphotourl">
+                    <template #input>
+                        <Image fit="contain" :src="getFileUrl(formData.cardfrontphotourl)" width="100" height="100"
+                            v-if="isReadonly" />
+                        <app-uploader @success="f_afterRead" v-else />
+                    </template>
+                </Field>
+                <Field name="cardbackphotourl" label="证件反面照片" :rules="formRules.cardbackphotourl">
+                    <template #input>
+                        <Image fit="contain" :src="getFileUrl(formData.cardbackphotourl)" width="100" height="100"
+                            v-if="isReadonly" />
+                        <app-uploader @success="b_afterRead" v-else />
+                    </template>
+                </Field>
+            </CellGroup>
+        </Form>
+        <img src="../../../assets/images/certification.png" />
+        <template #footer>
+            <div class="g-form__footer inset">
+                <Button type="danger" :loading="buttonLoading" @click="onSubmit" round block>提交实名认证</Button>
+            </div>
+        </template>
+        <component ref="componentRef" v-bind="{ formData }" :is="componentMap.get(componentId)" @closed="closeComponent"
+            v-if="componentId" />
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, defineAsyncComponent, onMounted, computed } from 'vue'
+import { CellGroup, Button, Field, Form, FormInstance, showFailToast, FieldRule, Image } from 'vant'
+import { fullloading, dialog } from '@/utils/vant';
+import { getFileUrl } from '@/filters';
+import { getAQCertificateTypeList } from "@/constants/account";
+import { useRequest } from '@/hooks/request'
+import { queryUserESignRecord, requestCheckCardNum } from '@/services/api/account';
+import { addAuthReq } from '@/business/user/account';
+import { validateRules } from '@/constants/regex';
+import { useComponent } from '@/hooks/component'
+import { useUserStore } from '@/stores'
+import AppSelect from '@mobile/components/base/select/index.vue'
+import AppUploader from '@mobile/components/base/uploader/index.vue'
+import { useNavigation } from '@mobile/router/navigation'
+
+const componentMap = new Map<string, unknown>([
+    ['certification-next', defineAsyncComponent(() => import('./components/certification-next/Index.vue'))], // 爱签-实名认证第二步
+])
+
+const { router } = useNavigation()
+const userStore = useUserStore()
+const formRef = shallowRef<FormInstance>()
+const { formData, formSubmit, loading } = addAuthReq()
+
+const { componentRef, componentId, openComponent, closeComponent } = useComponent(() => {
+    router.back()
+})
+
+const isReadonly = computed(() => userESignRecords.value.some((e) => e.templatetype === 1 && e.recordstatus === 3))
+
+/// 查询记录
+const { loading: buttonLoading, dataList: userESignRecords, run: getUserESignRecord } = useRequest(queryUserESignRecord, {
+    onSuccess: (res) => {
+        const record = res.data.find((e) => e.templatetype === 1 && e.recordstatus === 3)
+        if (record) {
+            const { name, idCard, idCardPhoto, idCardPhotoBackURL, mobile, idCardType } = JSON.parse(record.authinfo || '{}')
+            formData.username = name
+            formData.cardnum = idCard
+            formData.cardbackphotourl = idCardPhoto
+            formData.cardbackphotourl = idCardPhotoBackURL
+            formData.cardtype = idCardType
+            // formData.mobile = mobile
+        }
+    },
+    onError: (err) => {
+        showFailToast(err)
+    }
+})
+
+const b_afterRead = (filePath: string) => {
+    formData.cardbackphotourl = filePath
+}
+
+const f_afterRead = (filePath: string) => {
+    formData.cardfrontphotourl = filePath
+}
+
+// 表单验证规则
+const formRules: { [key in keyof Model.AddAuthReq]?: FieldRule[] } = {
+    username: [{
+        required: true,
+        message: '请输入用户姓名',
+    }],
+    mobile: [{
+        required: true,
+        message: '请输入手机号码',
+        validator: (val) => {
+            if (validateRules.phone.validate(val)) {
+                return true
+            }
+            return validateRules.phone.message
+        }
+    }],
+    cardnum: [{
+        required: true,
+        message: '请输入证件号码',
+        validator: (val) => {
+            if (validateRules.cardno.validate(val)) {
+                return true
+            }
+            return validateRules.cardno.message
+        }
+    }],
+    cardbackphotourl: [{
+        required: true,
+        message: '请上传证件背面照片',
+    }],
+    cardfrontphotourl: [{
+        required: true,
+        message: '请上传证件正面照片',
+    }],
+}
+
+const onCheckCardNum = () => {
+    fullloading((hideLoading) => {
+        requestCheckCardNum({
+            data: {
+                cardnum: formData.idCard
+            }
+        }).then(() => {
+            formSubmit().then(() => {
+                hideLoading()
+                getUserESignRecord()
+                dialog('提交请求成功').then(() => {
+                    /// 进行下一步
+                    openComponent('certification-next')
+                })
+            }).catch((err) => {
+                switch (err) {
+                    case '100020':
+                        hideLoading('个人三要素信息验证失败', 'fail')
+                        break
+                    case '100021':
+                        hideLoading('用户已存在', 'fail')
+                        break
+                    case '100726':
+                        hideLoading('该条实名记录为核验记录,无法用于添加用户', 'fail')
+                        break
+                    case '100727':
+                        hideLoading('实名认证类型和添加用户类型不匹配', 'fail')
+                        break
+                    default:
+                        hideLoading(err, 'fail')
+                }
+            })
+        }).catch((err) => {
+            hideLoading(err, 'fail')
+        })
+    })
+}
+
+const onSubmit = () => {
+    const isInclude = userESignRecords.value.some((e) => e.templatetype === 1 && e.recordstatus === 1)
+    if (!userESignRecords.value.length || isInclude) {
+        formRef.value?.submit()
+    } else {
+        openComponent('certification-next')
+    }
+}
+
+onMounted(() => {
+    formData.mobile = userStore.userInfo?.mobile2 ?? ''
+})
+</script>

+ 207 - 0
src/packages/tjmd/views/account/certification/components/certification-next/Index.vue

@@ -0,0 +1,207 @@
+<template>
+    <app-modal direction="right" height="100%" v-model:show="showModal" :refresh="refresh">
+        <app-view class="g-form">
+            <template #header>
+                <app-navbar title="实名认证" @back="closed" />
+            </template>
+            <div class="g-form__container">
+                <CellGroup inset>
+                    <Cell title="姓名" :value="formData.name" />
+                    <Cell title="手机号码" :value="formData.mobile" />
+                    <Cell title="证件类型" :value="getAQCertificateTypeListName(formData.idCardType ?? 1)" />
+                    <Cell title="证件号码" :value="formData.idCard" />
+                    <Cell title="证件正面照片">
+                        <template #value>
+                            <Image fit="contain" :src="idCardPhoto" width="100" height="100" />
+                        </template>
+                    </Cell>
+                    <Cell title="证件反面照片">
+                        <template #value>
+                            <Image fit="contain" :src="idCardPhotoBackURL" width="100" height="100" />
+                        </template>
+                    </Cell>
+                </CellGroup>
+                <CellGroup inset>
+                    <template v-for="(item, index) in secondStepList" :key="index">
+                        <Cell :title="item.templatename" :icon="iconName(item.recordstatus)" @click="signer(item)"
+                            is-link />
+                    </template>
+                    <template v-if="secondStepList.every(e => e.recordstatus === 3)">
+                        <template v-for="(item, index) in thirdStepList" :key="index">
+                            <Cell title="视频认证" :icon="iconName(item.recordstatus)" @click="faceAuth(item.recordstatus)"
+                                :is-link="item.recordstatus !== 3" />
+                        </template>
+                    </template>
+                </CellGroup>
+            </div>
+            <template #footer>
+                <div class="g-form__footer inset">
+                    <Button type="danger" :disabled="dataList.some((e) => e.recordstatus !== 3)" @click="onSubmit()" round
+                        block>提交认证</Button>
+                </div>
+            </template>
+        </app-view>
+    </app-modal>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, computed, PropType } from 'vue'
+import { CellGroup, Button, Cell, showFailToast, Image, showToast } from 'vant'
+import { fullloading, dialog } from '@/utils/vant';
+import { getAQCertificateTypeListName } from "@/constants/account";
+import { useRequest } from '@/hooks/request'
+import { queryUserESignRecord } from '@/services/api/account';
+import { useRequestCreateContractAndAddSigner, useRequestSignCompleted, useRequestWillFace } from '@/business/user/account';
+import plus from '@/utils/h5plus'
+import eventBus from '@/services/bus'
+import { getFileUrl } from '@/filters';
+import AppModal from '@/components/base/modal/index.vue'
+
+const showModal = shallowRef(true)
+// 是否刷新父组件数据
+const refresh = shallowRef(false)
+const { createSigner, templateNoFormData } = useRequestCreateContractAndAddSigner()
+/// 意愿视频认证
+const { willFace, willFaceFormData } = useRequestWillFace()
+const { signCompleted } = useRequestSignCompleted()
+
+/// 查询
+const { run, dataList } = useRequest(queryUserESignRecord)
+
+// 步骤2列表
+const secondStepList = computed(() => dataList.value.filter(obj => obj.templatetype === 2))
+// 步骤3列表
+const thirdStepList = computed(() => dataList.value.filter(obj => obj.templatetype === 3))
+
+const iconName = (type: number) => {
+    switch (type) {
+        case 2: return 'info-o'
+        case 4: return 'close'
+        case 3: return 'passed'
+        default: return 'circle'
+    }
+}
+
+// 正面照
+const idCardPhoto = computed(() => {
+    const idCardPhoto = props.formData.idCardPhoto ?? ''
+    const image = idCardPhoto.split(',')[0]
+    return getFileUrl(image)
+})
+
+// 背面照
+const idCardPhotoBackURL = computed(() => {
+    const idCardPhotoBackURL = props.formData.idCardPhotoBackURL ?? ''
+    const image = idCardPhotoBackURL.split(',')[0]
+    return getFileUrl(image)
+})
+
+const openWebview = (url: string) => {
+    const ua = window.navigator.userAgent.toLowerCase()
+    if (ua.indexOf('micromessenger') !== -1) {
+        showToast({
+            type: 'fail',
+            message: '请使用浏览器打开此页面'
+        })
+    } else {
+        plus.openWebview({
+            url,
+            titleText: '实名认证',
+            onClose: () => run()
+        })
+    }
+}
+
+const props = defineProps({
+    formData: {
+        type: Object as PropType<Model.AddUserReq>,
+        required: true,
+    }
+})
+
+const signer = (item: Model.UserESignRecordRsq) => {
+    ///  如果是已签署
+    if (item.recordstatus === 2) {
+        item.signurl ? openWebview(item.signurl) : showFailToast('合同地址错误')
+    } else if (item.recordstatus === 3) {
+        const fileUrl = getFileUrl(item.contractfileaddr)
+        item.contractfileaddr ? plus.openURL(fileUrl) : showFailToast('合同地址错误')
+    } else {
+        fullloading((hideLoading) => {
+            templateNoFormData.templateNo = item.templateno
+            /// 创建合同
+            createSigner().then((res) => {
+                hideLoading()
+                openWebview(res.data.signUrl)
+            }).catch((err) => {
+                hideLoading(err, 'fail')
+            })
+        })
+    }
+}
+
+// 视频认证
+const faceAuth = (status: number) => {
+    if (status !== 3) {
+        plus.requestPermissionCamera({
+            onSuccess: () => {
+                plus.requestPermissionRecordAudio({
+                    onSuccess: () => {
+                        /// 进行视频认证
+                        willFaceFormData.idCardNo = props.formData.idCard
+                        willFaceFormData.realName = props.formData.name
+                        /// loading
+                        fullloading((hideLoading) => {
+                            willFace().then((res) => {
+                                hideLoading()
+                                openWebview(res.data.faceUrl)
+                            }).catch((err) => {
+                                hideLoading(err, 'fail')
+                            })
+                        })
+                    },
+                    onError: (err) => {
+                        showFailToast(err)
+                    }
+                })
+            },
+            onError: (err) => {
+                showFailToast(err)
+            }
+        })
+    }
+}
+
+/// 最终提交
+const onSubmit = () => {
+    fullloading((hideLoading) => {
+        signCompleted().then(() => {
+            hideLoading()
+            dialog('实名认证提交请求成功').then(() => {
+                closed(true)
+            })
+        }).catch((err) => {
+            hideLoading(err, 'fail')
+        })
+    })
+}
+
+// 接收窗口页面状态通知
+const documentVisibilityStateNotify = eventBus.$on('DocumentVisibilityStateNotify', (state) => {
+    if (state === 'visible') {
+        run()
+    }
+})
+
+// 关闭弹窗
+const closed = (isRefresh = false) => {
+    refresh.value = isRefresh
+    showModal.value = false
+    documentVisibilityStateNotify.cancel()
+}
+
+// 暴露组件属性给父组件调用
+defineExpose({
+    closed,
+})
+</script>

+ 12 - 1
src/services/api/account/index.ts

@@ -230,4 +230,15 @@ export function requestCheckCardNum(config: RequestConfig<Model.CheckCardNumReq>
             ...config.data
         },
     })
-}
+}
+
+/**
+ * 查询用户电子签记录表
+ */
+export function QueryTencentQianNotice(config: RequestConfig<Model.QianNoticeReq> = {}) {
+    return http.goRequest<Model.QianNoticeRsp[]>({
+        method: 'get',
+        url: '/Tencent/QianNotice',
+        params: config.data,
+    })
+}

+ 5 - 2
src/types/model/bank.d.ts

@@ -215,8 +215,11 @@ declare namespace Model {
         cardbackphotourl?: string,
         // 用户id
         userid?: number;
-        halfbodyphotourl?: string; // 手持证件照
-        userinfotype?: number; // 用户类型
+        // 半身照
+        halfbodyphotourl?: string; 
+        // 手持证件照
+        // 用户类型
+        userinfotype?: number; 
     }
 
     interface AddAuthRsp {

+ 51 - 0
src/types/model/user.d.ts

@@ -67,4 +67,55 @@ declare namespace Model {
         /// 返回信息
         message?: string
     }
+
+    /** 查询腾讯用户电子签记录 请求 */
+    interface QianNoticeReq {
+        userId?: number; // 用户ID
+        memberUserId?: number; // 所属会员ID
+        recordId?: number   // 记录ID
+        templateConfigId?: number // 模板配置ID
+        templatetype?:number  // 模板类型 - 1:实名认证 2:开户协议 3:日结算单 4:交易协议
+    }
+
+    /** 查询腾讯用户电子签记录 响应 */
+    interface QianNoticeRsp {
+        // 认证信息
+        authinfo: string
+        // 合同签署文件地址
+        contractfileaddr: string
+        // 合同编号
+        contractno: string
+        // 创建时间
+        createtime: string
+        // 创建人
+        creatorid: number
+        // 显示顺序
+        orderindex: number
+        // 甲方Key(逗号分隔)
+        partakey: string
+        // 乙方Key(逗号分隔)
+        partbkey: string
+        // 记录ID(SEQ_USERESIGNRECORD)
+        recordid: number
+        // 记录状态 - 1:未签署 2:签署中 3:已签署 4:签署拒绝
+        recordstatus: number
+        // 签署备注
+        signremark: string
+        // 合同签署URL(三方URL)
+        signurl: string
+        // 模板配置ID
+        templateconfigid: number
+        // 模板名称
+        templatename: string
+        // 模板编号(电子签类型对应的模板编号)
+        templateno: string
+        // 模板类型 - 1:实名认证 2:开户协议 3:日结算单
+        templatetype: number
+        // 交易日
+        tradedate: string
+        // 更新时间
+        updatetime: string
+        // 用户ID
+        userid: number
+    }
 }