index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. <template>
  2. <div class="wrapper">
  3. <img class="goods-cover" :src="productInfo.productImgUrl" alt="">
  4. <Flow></Flow>
  5. <ul class="form-wrap">
  6. <li>
  7. <label>
  8. <span class="label-name" v-for="(str, index) in '姓名:姓名'" :key="index">{{ str }}</span>
  9. </label>
  10. <div class="value-wrap">
  11. <div class="value-name">
  12. <input type="text" placeholder="请输入姓名" v-myBlur v-model.trim="postData.orderUserName">
  13. </div>
  14. <div class="value-gender" v-if="false">
  15. <p v-for="str in ['男', '女']" :key="str" @click="postData.orderUserSex = str">
  16. <img src="./image/btn_xingbie_sel@2x.png" alt="" v-show="postData.orderUserSex === str">
  17. <img src="./image/btn_xingbie_nor@2x.png" alt="" v-show="postData.orderUserSex !== str">
  18. <span>{{ str }}</span>
  19. </p>
  20. </div>
  21. </div>
  22. </li>
  23. <li>
  24. <label>
  25. <span v-for="(str, index) in '手机号码:'" :key="index">{{ str }}</span>
  26. </label>
  27. <div class="value-wrap">
  28. <input type="tel" placeholder="请输入手机号码" v-myBlur v-model.trim="postData.orderUserPhone"
  29. @input="funLimitLength">
  30. </div>
  31. </li>
  32. <li v-if="false">
  33. <label>
  34. <span v-for="(str, index) in '图形验证:'" :key="index">{{ str }}</span>
  35. </label>
  36. <div class="value-wrap">
  37. <input type="text" placeholder="图片验证码" v-myBlur v-model.trim="postData.capture">
  38. </div>
  39. <img class="capture-img" :src="strCapture" alt="" @click="funUpdateCapture">
  40. </li>
  41. <li>
  42. <label>
  43. <span class="label-code" v-for="(str, index) in '验证码:码'" :key="index">{{ str }}</span>
  44. </label>
  45. <div class="value-wrap">
  46. <input type="tel" placeholder="请输入验证码" v-myBlur v-model.trim="postData.code" @input="funLimitLength">
  47. </div>
  48. <button class="get-code" @click="funGetCode">{{
  49. [0, 60].includes(numCount) ? '获取' : numCount + '秒'
  50. }}
  51. </button>
  52. </li>
  53. <li v-if="false">
  54. <label>
  55. <span v-for="(str, index) in '所在城市:'" :key="index">{{ str }}</span>
  56. </label>
  57. <div class="value-wrap" @click="showPicker = true">
  58. <p class="value-city" v-show="postData.orderProvince">{{
  59. postData.orderProvince + postData.orderCity +
  60. postData.orderDistrict
  61. }}</p>
  62. <p class="value-city init" v-show="!postData.orderProvince">请选择所在城市</p>
  63. <img src="./image/btn_next@2x.png" alt="">
  64. </div>
  65. </li>
  66. <li v-if="false">
  67. <label>
  68. <span v-for="(str, index) in '小区名称:'" :key="index">{{ str }}</span>
  69. </label>
  70. <div class="value-wrap">
  71. <textarea name="" rows="1" placeholder="请输入小区名称" v-model.trim="postData.orderAddress" v-myBlur
  72. ref="myTextarea"></textarea>
  73. </div>
  74. </li>
  75. <li>
  76. <label>
  77. <span v-for="(str, index) in '需求金额(元):'" :key="index">{{ str }}</span>
  78. </label>
  79. <div class="value-wrap amount-wrap">
  80. <input :class="{'opacity-0': postData.orderPrice * 1 > 0}" type="tel" placeholder="请输入需求金额" v-myBlur
  81. v-model.trim="amount">
  82. <p v-show="postData.orderPrice * 1 > 0">{{ postData.orderPrice * 1 | toThousands }}.00</p>
  83. </div>
  84. </li>
  85. </ul>
  86. <button class="submit" @click="funSubmit">提交</button>
  87. <p class="explain">
  88. <span>申请代表您同意</span>
  89. <router-link :to="{path: '/loan/privacy'}">《用户隐私协议》</router-link>
  90. <span>和</span>
  91. <router-link :to="{path: '/loan/agreement'}">《服务协议》</router-link>
  92. </p>
  93. <van-popup v-model="showPicker" round position="bottom">
  94. <van-picker
  95. show-toolbar
  96. title="所在城市"
  97. :columns="columns"
  98. @cancel="showPicker = false"
  99. @confirm="onConfirm"/>
  100. </van-popup>
  101. </div>
  102. </template>
  103. <script>
  104. import Flow from './components/flow'
  105. import { Popup, Picker, Toast } from 'vant'
  106. import { createOrder } from './api'
  107. import { getChinaArea, sendSMS, getProductDetail } from '../../../api/common'
  108. import { funWxShare } from '../../../utils/wxShareConfig'
  109. import { isMobile, isSmscode } from '../../../utils/validate'
  110. const STRCAPTURE = process.env.API_DOMAIN + '/api/captcha'
  111. export default {
  112. name: 'apply',
  113. components: {
  114. Flow,
  115. 'van-popup': Popup,
  116. 'van-picker': Picker
  117. },
  118. props: {
  119. // 产品ID
  120. orderProductId: {
  121. type: String,
  122. default: ''
  123. },
  124. partnerId: {
  125. type: String,
  126. default: ''
  127. },
  128. RId: {
  129. type: [String, Number],
  130. default: ''
  131. }
  132. },
  133. data () {
  134. return {
  135. postData: {
  136. orderUserName: '', // 用户名称
  137. // orderUserSex: '男',
  138. orderUserPhone: '', // 手机号码
  139. code: '', // 验证码
  140. // capture: '',
  141. orderPrice: '' // 申请金额
  142. // orderProvince: '', // 省份
  143. // orderCity: '', // 城市
  144. // orderDistrict: '', // 区域
  145. // orderAddress: '' // 详细地址
  146. },
  147. numCount: 60,
  148. arrErrorList: [],
  149. strCapture: STRCAPTURE,
  150. showPicker: false,
  151. columns: [],
  152. productInfo: {},
  153. timer: null
  154. }
  155. },
  156. computed: {
  157. amount: {
  158. get () {
  159. return this.postData.orderPrice * 1 ? this.postData.orderPrice : ''
  160. },
  161. set (value) {
  162. this.postData.orderPrice = value
  163. }
  164. }
  165. },
  166. async mounted () {
  167. this.funGetChinaArea()
  168. this.funGetProductDetail()
  169. await this.$nextTick()
  170. const textarea = this.$refs.myTextarea
  171. if (textarea) {
  172. this.handleTextarea(textarea)
  173. textarea.addEventListener('input', this.handleTextarea(textarea, 1), false)
  174. this.$once('hook:beforeDestroy', () => {
  175. textarea.addEventListener('input', this.handleTextarea(textarea, 1), false)
  176. })
  177. }
  178. },
  179. activated () {
  180. if (!this.$route.meta.isUseCache) {
  181. this.postData = {
  182. orderUserName: '',
  183. // orderUserSex: '男',
  184. orderUserPhone: '',
  185. code: '',
  186. // capture: '',
  187. orderPrice: ''
  188. // orderProvince: '',
  189. // orderCity: '',
  190. // orderDistrict: '',
  191. // orderAddress: ''
  192. }
  193. this.numCount = 60
  194. this.arrErrorList = []
  195. this.showPicker = false
  196. this.timer = null
  197. } else {
  198. if (this.timer) {
  199. clearInterval(this.timer)
  200. }
  201. }
  202. this.$route.meta.isUseCache = false
  203. funWxShare('驼驼银服', '家里用钱,就找驼驼银服,省心,省力,妥妥的!', location.href, location.protocol + '//api.tuotuoyinfu.com/img/logo.jpg')
  204. },
  205. methods: {
  206. funGetChinaArea () {
  207. getChinaArea().then(res => {
  208. if (res.status) {
  209. this.columns = res.data.filter(province => province.text === '浙江')
  210. } else {
  211. Toast(res.msg)
  212. }
  213. }).catch(err => {
  214. Toast(err)
  215. })
  216. },
  217. funGetProductDetail () {
  218. getProductDetail(this.orderProductId).then(res => {
  219. if (res.status) {
  220. this.productInfo = res.data
  221. this.$nextTick(() => {
  222. this.$refreshTitle(res.data.productName)
  223. })
  224. }
  225. })
  226. },
  227. handleTextarea (el, auto) {
  228. return () => {
  229. if (auto) {
  230. el.style.height = 'auto'
  231. }
  232. el.style.height = el.scrollHeight + 'px'
  233. }
  234. },
  235. onConfirm (value) {
  236. this.showPicker = false
  237. this.$set(this.postData, 'orderProvince', value[0])
  238. this.$set(this.postData, 'orderCity', value[1])
  239. this.$set(this.postData, 'orderDistrict', value[2])
  240. },
  241. funLimitLength () {
  242. const { orderUserPhone, code } = this.postData
  243. this.postData.orderUserPhone = orderUserPhone.length > 11 ? orderUserPhone.slice(0, 11) : orderUserPhone
  244. this.postData.code = code.length > 4 ? code.slice(0, 4) : code
  245. },
  246. funCutDown () {
  247. clearInterval(this.timer)
  248. this.timer = setInterval(() => {
  249. if (this.numCount === 0) {
  250. clearInterval(this.timer)
  251. this.numCount = 0
  252. return
  253. }
  254. this.numCount--
  255. }, 1000)
  256. },
  257. // 获取验证码
  258. funGetCode () {
  259. const numCount = this.numCount
  260. const { orderUserPhone, capture } = this.postData
  261. if (numCount < 60 && numCount > 0) {
  262. return
  263. }
  264. this.numCount = 60
  265. if (!isMobile(orderUserPhone)) {
  266. Toast('请输入手机号码')
  267. return
  268. }
  269. // if (!capture) {
  270. // Toast('请输入图片验证码')
  271. // return
  272. // }
  273. this.funCutDown()
  274. sendSMS(orderUserPhone, 0, capture).then(res => {
  275. if (res.status) {
  276. Toast('发送成功')
  277. } else {
  278. Toast(res.msg)
  279. this.funUpdateCapture()
  280. clearInterval(this.timer)
  281. this.numCount = 60
  282. }
  283. }).catch(err => {
  284. Toast(err.msg)
  285. this.funUpdateCapture()
  286. clearInterval(this.timer)
  287. this.numCount = 60
  288. })
  289. },
  290. verifyData () {
  291. const {
  292. orderUserName,
  293. orderUserPhone,
  294. code,
  295. orderPrice
  296. } = this.postData
  297. this.arrErrorList = []
  298. if (!orderUserName) {
  299. this.arrErrorList.push('请输入姓名')
  300. }
  301. if (!isMobile(orderUserPhone)) {
  302. this.arrErrorList.push('请输入手机号码')
  303. }
  304. if (!isSmscode(code)) {
  305. this.arrErrorList.push('请输入验证码')
  306. }
  307. if (orderPrice * 1 <= 0) {
  308. this.arrErrorList.push('请输入需求金额')
  309. }
  310. return this.arrErrorList.length <= 0
  311. },
  312. funUpdateCapture () {
  313. this.strCapture = STRCAPTURE + '?' + new Date().getTime()
  314. },
  315. funSubmit () {
  316. const postData = {
  317. orderProductId: this.orderProductId,
  318. partnerId: this.partnerId,
  319. userNewId: this.RId,
  320. ...this.postData
  321. }
  322. delete postData.capture
  323. if (!this.verifyData()) {
  324. Toast({
  325. message: this.arrErrorList[0],
  326. forbidClick: true
  327. })
  328. return
  329. }
  330. const myToast = Toast.loading({
  331. message: '提交中...',
  332. duration: 1000 * 100,
  333. forbidClick: true
  334. })
  335. createOrder(postData).then(res => {
  336. myToast.clear()
  337. if (res.status) {
  338. const { id } = res.data
  339. Toast({
  340. type: 'success',
  341. message: '提交成功',
  342. forbidClick: true,
  343. onClose: () => {
  344. this.$router.replace({ path: '/loan/detail/' + id })
  345. }
  346. })
  347. return
  348. }
  349. Toast(res.msg)
  350. }).catch(err => {
  351. myToast.clear()
  352. Toast(err)
  353. })
  354. }
  355. },
  356. beforeRouteLeave (to, from, next) {
  357. if (['loanPrivacy', 'loanAgreement'].includes(to.name)) {
  358. from.meta.isUseCache = true
  359. }
  360. next()
  361. }
  362. }
  363. </script>
  364. <style lang="scss" scoped>
  365. .wrapper {
  366. position: relative;
  367. left: 0;
  368. top: 0;
  369. display: flex;
  370. flex-direction: column;
  371. align-items: center;
  372. width: 100%;
  373. min-height: 100vh;
  374. padding: 110px 0 200px;
  375. background: #fff;
  376. }
  377. .goods-cover {
  378. position: absolute;
  379. left: 0;
  380. top: 0;
  381. right: 0;
  382. display: block;
  383. width: 100%;
  384. min-height: 147px;
  385. }
  386. .form-wrap {
  387. li {
  388. position: relative;
  389. left: 0;
  390. top: 0;
  391. display: flex;
  392. align-items: flex-start;
  393. width: 327px;
  394. padding: 13px 14px;
  395. margin-top: 8px;
  396. border: 1px solid #E8E8E8;
  397. border-radius: 4px;
  398. &:nth-of-type(1) {
  399. margin-top: 37px;
  400. }
  401. }
  402. label {
  403. display: flex;
  404. span {
  405. line-height: 22px;
  406. font-size: 16px;
  407. font-weight: 500;
  408. color: #333;
  409. &.label-name:nth-of-type(4),
  410. &.label-name:nth-of-type(5),
  411. &.label-code:nth-of-type(5), {
  412. visibility: hidden;
  413. }
  414. }
  415. }
  416. .value-wrap {
  417. flex: 1;
  418. display: flex;
  419. margin-left: 9px;
  420. }
  421. .amount-wrap {
  422. position: relative;
  423. left: 0;
  424. top: 0;
  425. input {
  426. position: absolute;
  427. left: 0;
  428. top: 0;
  429. z-index: 1;
  430. &.opacity-0 {
  431. opacity: 0;
  432. }
  433. }
  434. p {
  435. width: 100%;
  436. padding-top: 2px;
  437. line-height: 20px;
  438. font-size: 15px;
  439. color: #333;
  440. }
  441. }
  442. input,
  443. textarea {
  444. width: 100%;
  445. min-height: 20px;
  446. padding-top: 2px;
  447. line-height: 20px;
  448. font-size: 15px;
  449. color: #333;
  450. word-break: break-all;
  451. resize: none;
  452. outline: 0 none;
  453. overflow: hidden;
  454. background: transparent;
  455. -webkit-text-fill-color: #333;
  456. opacity: 1;
  457. &::-webkit-input-placeholder {
  458. color: #999;
  459. -webkit-text-fill-color: #999;
  460. opacity: 1;
  461. }
  462. }
  463. .value-name {
  464. display: flex;
  465. width: 100%;
  466. }
  467. .value-gender {
  468. display: flex;
  469. p {
  470. display: flex;
  471. &:nth-of-type(2) {
  472. margin-left: 15px;
  473. }
  474. }
  475. span {
  476. margin-left: 4px;
  477. line-height: 22px;
  478. font-size: 16px;
  479. color: #333;
  480. }
  481. }
  482. .value-city {
  483. flex: 1;
  484. line-height: 22px;
  485. font-size: 15px;
  486. color: #333;
  487. &.init {
  488. color: #999;
  489. }
  490. }
  491. img {
  492. width: 22px;
  493. height: 22px;
  494. }
  495. .capture-img {
  496. @include vertical-center;
  497. right: 14px;
  498. z-index: 1;
  499. display: block;
  500. width: 125px;
  501. height: 31px;
  502. border-radius: 4px;
  503. overflow: hidden;
  504. }
  505. .get-code {
  506. @include vertical-center;
  507. right: 0;
  508. z-index: 1;
  509. display: block;
  510. min-width: 87px;
  511. padding: 6px;
  512. line-height: 22px;
  513. font-size: 16px;
  514. font-weight: 500;
  515. color: #C9A585;
  516. }
  517. }
  518. .submit {
  519. width: 327px;
  520. height: 45px;
  521. margin-top: 36px;
  522. border-radius: 4px;
  523. line-height: 20px;
  524. font-size: 14px;
  525. font-weight: 500;
  526. color: #fff;
  527. background: linear-gradient(90deg, #E5C7A5 0%, #CFAA7F 100%);
  528. box-shadow: 0 14px 9px -10px rgba(219, 208, 194, 1);
  529. }
  530. .explain {
  531. display: flex;
  532. margin-top: 12px;
  533. span,
  534. a {
  535. line-height: 17px;
  536. font-size: 12px;
  537. color: #666;
  538. }
  539. }
  540. </style>