init
28
src/App.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
|
||||
<!-- <router-view v-slot="{ Component }">-->
|
||||
<!-- <transition name="fade-transition" mode="out-in">-->
|
||||
<!-- <keep-alive>-->
|
||||
<!-- <component :is="Component"></component>-->
|
||||
<!-- </keep-alive>-->
|
||||
<!-- </transition>-->
|
||||
<!-- </router-view>-->
|
||||
|
||||
<!-- v-if="showBar"-->
|
||||
<div>
|
||||
<!-- <van-sticky>-->
|
||||
<!-- </van-sticky>-->
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "style/index.scss";
|
||||
body {
|
||||
//background-color: #f8f8f8;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
</style>
|
||||
192
src/api/index.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import {http} from "@/utils/http/axios";
|
||||
import {RequestEnum} from "@/enums/httpEnum";
|
||||
|
||||
|
||||
const baseUrl = '/api'
|
||||
|
||||
/**
|
||||
* @description: getLoansInfo
|
||||
*/
|
||||
export function getLoansInfo() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/home/setting/loans`,
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: getHomeInfo
|
||||
*/
|
||||
export function getHomeInfo() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/home/setting/home`,
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: getCalLoan
|
||||
*/
|
||||
export function getCalLoan(data) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/home/loans/calLoan`,
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: getLoansUser
|
||||
*/
|
||||
export function getLoansUser() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/home/loans/loansUser`,
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: getUserInfo
|
||||
*/
|
||||
export function getUserInfo() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/customer/card/info`,
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: uploadCommon
|
||||
*/
|
||||
export function uploadCommon(file, onUploadProgress?) {
|
||||
return http.uploadFile({
|
||||
url: `${baseUrl}/v2/common/upload`,
|
||||
method: 'POST',
|
||||
onUploadProgress: (progressEvent) => {
|
||||
onUploadProgress && onUploadProgress(progressEvent)
|
||||
}
|
||||
}, {
|
||||
file: file
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* updateCustomerCard
|
||||
* @param data
|
||||
*/
|
||||
export function updateCustomerCard(data) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/customer/updateCustomerCard`,
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* getBankType
|
||||
*/
|
||||
export function getBankType() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/home/setting/bankType`,
|
||||
method: RequestEnum.GET
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* getBankType
|
||||
*/
|
||||
export function getAgreement() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/home/setting/agreement`,
|
||||
method: RequestEnum.GET
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* getCustomerInfo
|
||||
*/
|
||||
export function getCustomerInfo() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/customer/info`,
|
||||
method: RequestEnum.GET
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getCustomerInfo
|
||||
*/
|
||||
export function getStepBorrow() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/borrow/getStepBorrow`,
|
||||
method: RequestEnum.GET
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getBorrowPage
|
||||
*/
|
||||
export function getBorrowPage(params) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/borrow/page`,
|
||||
method: RequestEnum.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getBorrowInfo
|
||||
*/
|
||||
export function getBorrowInfo(params) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/borrow/info`,
|
||||
method: RequestEnum.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getBorrowWithdraw
|
||||
*/
|
||||
export function getBorrowWithdraw(params) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/borrow/withdraw`,
|
||||
method: RequestEnum.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getSetting
|
||||
*/
|
||||
export function getSetting() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/home/setting/home`,
|
||||
method: RequestEnum.GET
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getSetting
|
||||
*/
|
||||
export function getContract(params?) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/borrow/getContract`,
|
||||
method: RequestEnum.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* startBorrow
|
||||
*/
|
||||
export function startBorrow(data) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/borrow/start`,
|
||||
method: RequestEnum.POST,
|
||||
data
|
||||
});
|
||||
}
|
||||
97
src/api/login/index.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { http } from '@/utils/http/axios';
|
||||
|
||||
const baseUrl = '/api'
|
||||
/**
|
||||
* @description: sendSmsRegister
|
||||
*/
|
||||
export function sendSmsRegister(params) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/customer/open/sms/register`,
|
||||
method: 'GET',
|
||||
params
|
||||
},
|
||||
{
|
||||
withToken: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: sendSmsForget
|
||||
*/
|
||||
export function sendSmsForget(params) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/customer/open/sms/forget`,
|
||||
method: 'GET',
|
||||
params
|
||||
},
|
||||
{
|
||||
withToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: login
|
||||
*/
|
||||
export function login(data) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/customer/login`,
|
||||
method: 'POST',
|
||||
data
|
||||
},
|
||||
{
|
||||
isTransformResponse: false,
|
||||
withToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: register
|
||||
*/
|
||||
export function register(data) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/customer/open/register`,
|
||||
method: 'POST',
|
||||
data
|
||||
},
|
||||
{
|
||||
withToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description: updatePwd
|
||||
*/
|
||||
export function updatePwd(data) {
|
||||
return http.request({
|
||||
url: `${baseUrl}/customer/open/updatePwd`,
|
||||
method: 'POST',
|
||||
data
|
||||
},
|
||||
{
|
||||
withToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @description: getUserInfo
|
||||
*/
|
||||
export function getUserInfo() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/customer/info`,
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 注销账号
|
||||
* @description: closeAmount
|
||||
*/
|
||||
export function closeAmount() {
|
||||
return http.request({
|
||||
url: `${baseUrl}/app/user/close/amount`,
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
0
src/api/system/user.ts
Normal file
260
src/array.prototype.d.ts
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
interface Array {
|
||||
|
||||
/**
|
||||
* 清空数组
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* 清空数组并添加
|
||||
* @param items
|
||||
*/
|
||||
newPush(...items: T[]): void;
|
||||
|
||||
/**
|
||||
* 数组根据对象删除指定元素
|
||||
* @param val
|
||||
*/
|
||||
remove(val: T | T[]): void;
|
||||
|
||||
/**
|
||||
* 数组根据下标删除指定元素
|
||||
* @param index
|
||||
* @param {number} index
|
||||
*/
|
||||
removeByIndex(index: number): void;
|
||||
|
||||
/**
|
||||
* 数组指定位置添加元素
|
||||
* @param index
|
||||
* @param item
|
||||
* @param {number} index
|
||||
* @param {*} item
|
||||
*/
|
||||
insert(index: number, ...item: T): void;
|
||||
|
||||
/**
|
||||
* 数组指定位置替换元素
|
||||
* @param index
|
||||
* @param item
|
||||
* @param {number} index
|
||||
* @param {*} item
|
||||
*/
|
||||
replace(index: number, ...item: T): void;
|
||||
|
||||
/**
|
||||
* 数组排序(根据某一个字段排序或者直接排序)
|
||||
* @param { key, desc } options
|
||||
*/
|
||||
shellSort(options?: {key: string, desc: boolean}): void;
|
||||
|
||||
|
||||
/**
|
||||
* 数组去重 (key为元素中的某一个属性,默认对比整个对象)
|
||||
* @param key
|
||||
* @returns {*|*[]}
|
||||
*/
|
||||
deDuplication(key?: string): T[];
|
||||
|
||||
/**
|
||||
* 判断元素中是否包含此元素 (key为元素中的某一个属性,默认对比整个对象)
|
||||
* @param val
|
||||
* @param key1
|
||||
* @param key2
|
||||
* @returns {boolean}
|
||||
* @param {any} val
|
||||
* @param {string} key1
|
||||
* @param {string | null} key2
|
||||
*/
|
||||
contain(val: any, key1?: string, key2?: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数组
|
||||
*/
|
||||
Array.prototype.clear = function() {
|
||||
this.length = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数组并添加
|
||||
* @param items
|
||||
*/
|
||||
Array.prototype.newPush = function(...items: T[]) {
|
||||
this.clear()
|
||||
// 判断是否传入了多个参数
|
||||
if (items && Array.isArray(items)) {
|
||||
// 循环多个参数
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
// 判断每个参数是否为数组
|
||||
if (items[i] && Array.isArray(items[i])) {
|
||||
// 循环是数组的元素并添加
|
||||
for (let j = 0; j < items[i].length; j++) {
|
||||
this.push(items[i][j])
|
||||
}
|
||||
} else {
|
||||
this.push(items[i])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.push(items)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组根据对象删除指定元素
|
||||
* @param val
|
||||
*/
|
||||
Array.prototype.remove = function (val: T | T[]) {
|
||||
const index = this.indexOf(val)
|
||||
if (index > -1) {
|
||||
this.removeByIndex(index)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组根据下标删除指定元素
|
||||
* @param index
|
||||
* @param {number} index
|
||||
*/
|
||||
Array.prototype.removeByIndex = function (index: number) {
|
||||
this.splice(index, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组指定位置添加元素
|
||||
* @param index
|
||||
* @param item
|
||||
* @param {number} index
|
||||
* @param {*} item
|
||||
*/
|
||||
Array.prototype.insert = function (index: number, item: T) {
|
||||
const len = this.length
|
||||
if (len < index) {
|
||||
for (let i = 0; i < (index - len); i++) {
|
||||
this.push(null)
|
||||
}
|
||||
} else if (index < 0) {
|
||||
for (let i = 0; i > index + len; i--) {
|
||||
this.splice(i, 0, null)
|
||||
}
|
||||
}
|
||||
this.splice(index, 0, item)
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组指定位置替换元素
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
Array.prototype.replace = function (index: number, item: T) {
|
||||
if (index < 0) {
|
||||
return
|
||||
}
|
||||
const len = this.length
|
||||
if (len < index) {
|
||||
this.insert(index, item)
|
||||
} else {
|
||||
this.splice(index, 1, item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组排序(根据某一个字段排序或者直接排序)
|
||||
* @param { key, desc } options
|
||||
*/
|
||||
Array.prototype.shellSort = function (options?: {key: string, desc: boolean} | null = {}) {
|
||||
const {key, desc} = options
|
||||
const N = this.length
|
||||
let h = 1
|
||||
while (h < N / 3) {
|
||||
h = 3 * h + 1
|
||||
}
|
||||
while (h >= 1) {
|
||||
for (let i = h; i < N; i++) {
|
||||
if (key) {
|
||||
for (let j = i; j >= h && (desc ? this[j][key] > this[j - h][key] : this[j][key] < this[j - h][key]); j -= h) {
|
||||
const temp = this[j]
|
||||
this[j] = this[j - h]
|
||||
this[j - h] = temp
|
||||
}
|
||||
} else {
|
||||
for (let j = i; j >= h && (desc ? this[j] > this[j - h] : this[j] < this[j - h]); j -= h) {
|
||||
const temp = this[j]
|
||||
this[j] = this[j - h]
|
||||
this[j - h] = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
h = (h - 1) / 3
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组去重 (key为元素中的某一个属性,默认对比整个对象)
|
||||
* @param key
|
||||
* @returns {*|*[]}
|
||||
*/
|
||||
Array.prototype.deDuplication = function (key?: string) {
|
||||
return this.reduce((pre: Array, cur) => {
|
||||
return pre.contain(cur, key) ? pre : pre.concat(cur)
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断元素中是否包含此元素 (key为元素中的某一个属性,默认对比整个对象)
|
||||
* @param val
|
||||
* @param key1
|
||||
* @param key2
|
||||
* @returns {boolean}
|
||||
* @param {any} val
|
||||
* @param {string} key1
|
||||
* @param {string} key2
|
||||
*/
|
||||
Array.prototype.contain = function (val: any, key1?: string, key2?: string): boolean {
|
||||
if (key1) {
|
||||
if (key2) {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (typeof val === 'object') {
|
||||
if (this[i][key1][key2] === val[key1][key2]) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (this[i][key1][key2] === val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (typeof val === 'object') {
|
||||
if (this[i][key1] === val[key1]) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// console.log(this[i][key1], '===', val)
|
||||
if (this[i][key1] === val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return this.includes(val)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
src/assets/images/common/bg_wallet.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
src/assets/images/common/idCard.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/images/common/img.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/images/common/rectangle.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/common/yhd.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/common/zyzgzs.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/images/home/apply_bg.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
src/assets/images/home/banner_home.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
src/assets/images/home/detail_bg.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
src/assets/images/home/strip-add.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/home/strip-block.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/images/home/strip-reduce.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/home/xlb.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/my/head_img.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/images/my/idcard1.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/images/my/idcard2.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/my/idcard3.png
Normal file
|
After Width: | Height: | Size: 1012 B |
BIN
src/assets/images/my/idcard4.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/my/idcard5.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/my/my_info.png
Normal file
|
After Width: | Height: | Size: 971 B |
BIN
src/assets/images/my/my_info1.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/my/my_info2.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/my/my_info3.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/images/my/my_info4.png
Normal file
|
After Width: | Height: | Size: 892 B |
BIN
src/assets/images/my/my_info5.png
Normal file
|
After Width: | Height: | Size: 762 B |
BIN
src/assets/images/my/my_info66.png
Normal file
|
After Width: | Height: | Size: 1004 B |
BIN
src/assets/images/my/user_card.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
src/assets/images/my/uuuu1.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/images/my/uuuu2.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/assets/images/my/uuuu3.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/images/tabBar/artificer-active.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/images/tabBar/artificer-inactive.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/images/tabBar/home-active.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/images/tabBar/home-inactive.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/tabBar/my-active.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/tabBar/my-inactive.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
22
src/components/JGap/JGap.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div :style="{height: px2vw(<number>baseProps.height), background: baseProps.background, opacity: baseProps.opacity ? 0 : 'none'}">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {px2vw} from "@/utils";
|
||||
const baseProps = defineProps({
|
||||
height: Number,
|
||||
opacity: Boolean,
|
||||
background: {
|
||||
type: String,
|
||||
default: () => '#ffffff00'
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
54
src/components/JLabel/JLabel.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div :style="{...props.style, ...(placement === 'bottom' ? { display: 'block' } : {})}" class="j-label">
|
||||
<div
|
||||
class="label"
|
||||
:class="{'j-class-label' : props.labelBorder, 'required': props.required}"
|
||||
v-if="label"
|
||||
:style="{...labelStyleComputed, minWidth: (isNumber(props.labelWidth) ? props.labelWidth + 'px' : props.labelWidth)}"
|
||||
>
|
||||
{{ props.label }}
|
||||
</div>
|
||||
<div :class="props.labelBorder ? '' : 'content'">
|
||||
<slot name="default"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isNumber } from "@/utils/is";
|
||||
import { basicProps } from './props';
|
||||
import { addStyle } from "@/utils";
|
||||
import {computed} from "vue";
|
||||
// todo
|
||||
const props = defineProps({ ...basicProps });
|
||||
const labelStyleComputed = computed(() => {
|
||||
return addStyle(props.labelStyle)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.j-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
.required {
|
||||
&:after {
|
||||
content: '*';
|
||||
color: #F35534;
|
||||
}
|
||||
}
|
||||
}
|
||||
.j-class-label {
|
||||
font-weight: bolder;
|
||||
padding-left: 6px;
|
||||
border-left: 3px solid #008ded;
|
||||
}
|
||||
|
||||
</style>
|
||||
1
src/components/JLabel/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as JLabel } from './JLabel.vue';
|
||||
39
src/components/JLabel/props.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { PropType } from "vue";
|
||||
|
||||
export const basicProps = {
|
||||
// 标签宽度
|
||||
labelWidth: {
|
||||
type: [Number, String] as PropType<number | string>,
|
||||
default: '70px',
|
||||
},
|
||||
// 标签样式
|
||||
labelStyle: {
|
||||
type: [Object, String] as PropType<Object | string>,
|
||||
default: '',
|
||||
},
|
||||
// 样式
|
||||
style: {
|
||||
type: [Object] as PropType<Object>,
|
||||
default: {}
|
||||
},
|
||||
// 标签
|
||||
label: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
// 标签
|
||||
placement: {
|
||||
type: String as PropType<'left' | 'bottom'>,
|
||||
default: 'left'
|
||||
},
|
||||
// 标签
|
||||
labelBorder: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
// 标签
|
||||
required: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
110
src/components/JList/JList.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<van-pull-refresh v-model="loadingUp" @refresh="onRefresh">
|
||||
<van-list
|
||||
v-model:loading="loadingDown"
|
||||
:finished="finished"
|
||||
finished-text=""
|
||||
@load="onLoad"
|
||||
>
|
||||
<div style="min-height: 78vh">
|
||||
123
|
||||
|
||||
|
||||
<van-empty v-if="orderList?.length <= 0" description="没有暂时还没有内容~"/>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from "vue";
|
||||
import {onMounted, reactive} from "vue";
|
||||
|
||||
const baseProps = defineProps({
|
||||
orderList: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
serviceStatus: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'cancelOrder',
|
||||
'removeOrder',
|
||||
'refundOrder',
|
||||
'commitOrder',
|
||||
'goInfo',
|
||||
])
|
||||
|
||||
const orderList: any[] = reactive([])
|
||||
|
||||
|
||||
const loadingUp = ref(false);
|
||||
const loadingDown = ref(false);
|
||||
const finished = ref(false);
|
||||
const page = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 5,
|
||||
total: 20,
|
||||
})
|
||||
|
||||
const onRefresh = () => {
|
||||
page.pageNum = 1
|
||||
finished.value = true
|
||||
orderList.clear()
|
||||
_getUserOrderPage()
|
||||
};
|
||||
const onLoad = () => {
|
||||
if (finished.value) {
|
||||
return
|
||||
}
|
||||
page.pageNum = page.pageNum + 1
|
||||
_getUserOrderPage()
|
||||
};
|
||||
|
||||
|
||||
defineExpose({
|
||||
onRefresh
|
||||
})
|
||||
|
||||
const _getUserOrderPage = () => {
|
||||
|
||||
}
|
||||
|
||||
const cancelOrderBtn = (order) => {
|
||||
emit('cancelOrder', order)
|
||||
}
|
||||
|
||||
const removeOrderBtn = (order) => {
|
||||
emit('removeOrder', order)
|
||||
}
|
||||
|
||||
|
||||
const refundOrderBtn = (order) => {
|
||||
emit('refundOrder', order)
|
||||
}
|
||||
|
||||
|
||||
const commitOrderBtn = (order) => {
|
||||
emit('commitOrder', order)
|
||||
}
|
||||
|
||||
const goInfoBtn = (order) => {
|
||||
emit('goInfo', order)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
_getUserOrderPage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
62
src/components/JNavBar/JNavBar.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<van-nav-bar
|
||||
:placeholder="baseProps.placeholder"
|
||||
fixed
|
||||
safe-area-inset-top
|
||||
:title="title"
|
||||
:style="{
|
||||
'--van-nav-bar-background': baseProps.navBarBackground,
|
||||
'--van-nav-bar-icon-color': baseProps.color,
|
||||
'--van-nav-bar-text-color': baseProps.color,
|
||||
'--van-nav-bar-title-text-color': baseProps.color,
|
||||
'--van-border-width': '0',
|
||||
}"
|
||||
:left-arrow="showBar"
|
||||
@click-left="onClickLeft"
|
||||
/>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const baseProps = defineProps({
|
||||
navBarBackground: {
|
||||
type: String,
|
||||
default: '#151515'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
placeholder: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const onClickLeft = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const title = computed(() => {
|
||||
return baseProps.title || route.meta.title
|
||||
})
|
||||
const showBar = computed(() => {
|
||||
return route.meta.showBar
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
41
src/date.prototype.d.ts
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
interface Date {
|
||||
format(format: string): string
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间格式化
|
||||
* @param format
|
||||
*/
|
||||
Date.prototype.format = function (format: DateType | undefined = 'datetime'): string {
|
||||
if (format === 'date') {
|
||||
format = 'yyyy-MM-dd'
|
||||
} else if (format === 'datetime') {
|
||||
format = 'yyyy-MM-dd hh:mm:ss'
|
||||
} else if (format === 'time') {
|
||||
format = 'hh:mm:ss'
|
||||
}
|
||||
const o = {
|
||||
"M+": this.getMonth() + 1, //month
|
||||
"d+": this.getDate(), //day
|
||||
"h+": this.getHours(), //hour
|
||||
"m+": this.getMinutes(), //minute
|
||||
"s+": this.getSeconds(), //second
|
||||
"q+": Math.floor((this.getMonth() + 3) / 3), //quarter
|
||||
"S": this.getMilliseconds() //millisecond
|
||||
}
|
||||
if (/(y+)/.test(format)) {
|
||||
format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
|
||||
}
|
||||
for (const k in o) {
|
||||
if (new RegExp("(" + k + ")").test(format)) {
|
||||
format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
// String.prototype.formatDate = function (format) {
|
||||
// const time = this.replaceAll(/-/g, "/");
|
||||
// return new Date(time).format(format);
|
||||
// }
|
||||
34
src/enums/httpEnum.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @description: 请求结果集
|
||||
*/
|
||||
export enum ResultEnum {
|
||||
SUCCESS = 200,
|
||||
ERROR = 400,
|
||||
TIMEOUT = 10042,
|
||||
TYPE = 'success',
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 请求方法
|
||||
*/
|
||||
export enum RequestEnum {
|
||||
GET = 'GET',
|
||||
POST = 'POST',
|
||||
PATCH = 'PATCH',
|
||||
PUT = 'PUT',
|
||||
DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 常用的contentTyp类型
|
||||
*/
|
||||
export enum ContentTypeEnum {
|
||||
// json
|
||||
JSON = 'application/json;charset=UTF-8',
|
||||
// json
|
||||
TEXT = 'text/plain;charset=UTF-8',
|
||||
// form-data 一般配合qs
|
||||
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
// form-data 上传
|
||||
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||||
}
|
||||
52
src/enums/orderEnum.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
export const OrderEnum = {
|
||||
NO_PAY: {
|
||||
value: 0,
|
||||
name: '待支付'
|
||||
},
|
||||
NO_ORDER: {
|
||||
value: 1,
|
||||
name: '待接单'
|
||||
},
|
||||
ALREADY_ORDER: {
|
||||
value: 2,
|
||||
name: '待消费'
|
||||
},
|
||||
ALREADY_EVALUATION: {
|
||||
value: 90,
|
||||
name: '待评价'
|
||||
},
|
||||
COMMIT: {
|
||||
value: 99,
|
||||
name: '已完成'
|
||||
},
|
||||
CANCEL: {
|
||||
value: -1,
|
||||
name: '已取消'
|
||||
},
|
||||
REFUND: {
|
||||
value: -2,
|
||||
name: '已退款'
|
||||
}
|
||||
}
|
||||
|
||||
export const OrderEnumMap = {
|
||||
0: '待支付',
|
||||
1: '待接单',
|
||||
2: '待消费',
|
||||
90: '待评价',
|
||||
99: '已完成',
|
||||
'-1': '已取消',
|
||||
'-2': '已退款',
|
||||
}
|
||||
|
||||
|
||||
export const OrderIconMap = {
|
||||
0: 'order/pay.png',
|
||||
1: 'order/djd.png',
|
||||
2: 'order/daixiaofei.png',
|
||||
90: 'order/dpj.png',
|
||||
99: 'order/ywc.png',
|
||||
'-1': 'order/yqx.png',
|
||||
'-2': 'order/ytk.png',
|
||||
}
|
||||
|
||||
26
src/hooks/useBackgroundHook.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {onMounted, onUnmounted} from "vue";
|
||||
|
||||
export const useBackgroundHook = () => {
|
||||
|
||||
|
||||
let bodyList = document.getElementsByTagName('body');
|
||||
let body = bodyList[0]
|
||||
let bodyBackground = body.style.background
|
||||
|
||||
|
||||
const setBodyBackground = (option?: {mounted?: Function | undefined, unmounted?: Function | undefined}) => {
|
||||
let {mounted, unmounted} = option || {}
|
||||
|
||||
onMounted(() => {
|
||||
body.style.background = 'linear-gradient(167.96deg, #E6FAE1 0%, #F2E7B7 98.44%) no-repeat'
|
||||
mounted && mounted()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
body.style.background = bodyBackground
|
||||
unmounted && unmounted()
|
||||
})
|
||||
}
|
||||
|
||||
return { setBodyBackground }
|
||||
}
|
||||
37
src/hooks/useMittHook.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import mitt from 'mitt'
|
||||
const emitter = mitt()
|
||||
|
||||
export const useMittHook = () => {
|
||||
|
||||
const emit = (eventName, params) => {
|
||||
emitter.emit(eventName, params)
|
||||
emitter.emit(`${eventName}Once`, eventName)
|
||||
}
|
||||
|
||||
const on = (eventName, fun) => {
|
||||
emitter.on(eventName, fun)
|
||||
}
|
||||
|
||||
const once = (eventName, fun) => {
|
||||
emitter.on(eventName, fun)
|
||||
emitter.on(`${eventName}Once`, (en) => {
|
||||
emitter.off(en)
|
||||
})
|
||||
}
|
||||
|
||||
const off = (eventName, params?) => {
|
||||
emitter.off(eventName, params)
|
||||
}
|
||||
|
||||
const clearAll = () => {
|
||||
emitter.all.clear()
|
||||
}
|
||||
|
||||
return {
|
||||
emit,
|
||||
on,
|
||||
once,
|
||||
off,
|
||||
clearAll
|
||||
}
|
||||
}
|
||||
20
src/main.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import './array.prototype.d.ts';
|
||||
import './date.prototype.d.ts';
|
||||
import './string.prototype.d.ts';
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from "./router";
|
||||
import {setupStore} from "@/store";
|
||||
import {setupCustomComponents} from "@/plugins/customComponents";
|
||||
// 2. 引入组件样式
|
||||
import 'vant/lib/index.css';
|
||||
import 'vant/es/toast/style'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
setupStore(app)
|
||||
|
||||
setupCustomComponents(app)
|
||||
14
src/plugins/customComponents.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { App } from "vue";
|
||||
import { JLabel } from "@/components/JLabel";
|
||||
import JGap from "@/components/JGap/JGap.vue";
|
||||
import JNavBar from "@/components/JNavBar/JNavBar.vue";
|
||||
|
||||
/**
|
||||
* 全局注册自定义组件 待完善
|
||||
* @param app
|
||||
*/
|
||||
export function setupCustomComponents(app: App<Element>) {
|
||||
app.component('JGap',JGap)
|
||||
app.component('JLabel',JLabel)
|
||||
app.component('JNavBar',JNavBar)
|
||||
}
|
||||
253
src/router/index.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
import {createRouter, createWebHashHistory, RouteRecordRaw} from 'vue-router';
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
|
||||
export const constantRouter: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Root',
|
||||
redirect: '/home',
|
||||
meta: {
|
||||
title: 'Root',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
component: () => import('@/views/index/index.vue'),
|
||||
meta: {
|
||||
title: '首页',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/home',
|
||||
name: 'Home',
|
||||
component: () => import('@/views/index/home/index.vue'),
|
||||
meta: {
|
||||
title: '首页',
|
||||
showBar: false
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/serveList',
|
||||
name: 'serveList',
|
||||
component: () => import('@/views/index/serveList/index.vue'),
|
||||
meta: {
|
||||
isPermissions: true,
|
||||
title: '钱包',
|
||||
showBar: false
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/message',
|
||||
name: 'message',
|
||||
component: () => import('@/views/index/message/index.vue'),
|
||||
meta: {
|
||||
isPermissions: true,
|
||||
title: '聊天',
|
||||
showBar: false
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/my',
|
||||
name: 'my',
|
||||
component: () => import('@/views/index/my/index.vue'),
|
||||
meta: {
|
||||
isPermissions: true,
|
||||
title: '我的',
|
||||
showBar: false
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/userInfo',
|
||||
name: 'userInfo',
|
||||
component: () => import('@/views/my/userInfo/index.vue'),
|
||||
meta: {
|
||||
title: '我的资料',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/userInfo1',
|
||||
name: 'userInfo1',
|
||||
component: () => import('@/views/my/userInfo1/index.vue'),
|
||||
meta: {
|
||||
title: '基本信息',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/signature',
|
||||
name: 'signature',
|
||||
component: () => import('@/views/my/signature/index.vue'),
|
||||
meta: {
|
||||
title: '签名',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/contract',
|
||||
name: 'contract',
|
||||
component: () => import('@/views/my/contract/index.vue'),
|
||||
meta: {
|
||||
title: '合同',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/userInfo2',
|
||||
name: 'userInfo2',
|
||||
component: () => import('@/views/my/userInfo2/index.vue'),
|
||||
meta: {
|
||||
title: '提交资料',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/userInfo3',
|
||||
name: 'userInfo3',
|
||||
component: () => import('@/views/my/userInfo3/index.vue'),
|
||||
meta: {
|
||||
title: '收款银行卡',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/loansInfo',
|
||||
name: 'loansInfo',
|
||||
component: () => import('@/views/loans/info/index.vue'),
|
||||
meta: {
|
||||
title: '借款详情',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/loansInfo1',
|
||||
name: 'loansInfo1',
|
||||
component: () => import('@/views/loans/info1/index.vue'),
|
||||
meta: {
|
||||
title: '提现',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/myLoan',
|
||||
name: 'myLoan',
|
||||
component: () => import('@/views/my/myLoan/index.vue'),
|
||||
meta: {
|
||||
title: '我的借款',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/myRepayment',
|
||||
name: 'myRepayment',
|
||||
component: () => import('@/views/my/myRepayment/index.vue'),
|
||||
meta: {
|
||||
title: '我的还款',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: '/borrowInfo',
|
||||
name: 'borrowInfo',
|
||||
component: () => import('@/views/borrowInfo/index.vue'),
|
||||
meta: {
|
||||
title: '贷款详情',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/uploadPassword',
|
||||
name: 'uploadPassword',
|
||||
component: () => import('@/views/uploadPassword/index.vue'),
|
||||
meta: {
|
||||
title: '修改密码',
|
||||
isPermissions: true,
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
showBar: false
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: () => import('@/views/register/index.vue'),
|
||||
meta: {
|
||||
title: '注册',
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/forget',
|
||||
name: 'forget',
|
||||
component: () => import('@/views/forget/index.vue'),
|
||||
meta: {
|
||||
title: '忘记密码',
|
||||
showBar: true
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/agreement',
|
||||
name: 'agreement',
|
||||
component: () => import('@/views/agreement/agreement.vue'),
|
||||
meta: {
|
||||
title: '协议',
|
||||
showBar: true
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
//需要验证权限
|
||||
|
||||
//普通路由 无需验证权限
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(''),
|
||||
routes: constantRouter,
|
||||
strict: true,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
});
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.title) { // 判断是否有标题
|
||||
document.title = to.meta.title as string;
|
||||
}
|
||||
if (to.meta.isPermissions) {
|
||||
const userStore = useUserStore()
|
||||
if (!userStore.getToken) {
|
||||
next({
|
||||
path: '/login'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
export default router;
|
||||
10
src/store/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { App } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
const store = createPinia();
|
||||
|
||||
export function setupStore(app: App<Element>) {
|
||||
app.use(store);
|
||||
}
|
||||
|
||||
export { store };
|
||||
14
src/store/modules/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const allModules: any = import.meta.glob('./*/index.ts', { eager: true });
|
||||
const modules = {} as any;
|
||||
Object.keys(allModules).forEach((path) => {
|
||||
const fileName = path.split('/')[1];
|
||||
modules[fileName] = allModules[path][fileName] || allModules[path].default || allModules[path];
|
||||
});
|
||||
|
||||
// export default modules
|
||||
// @ts-ignore
|
||||
import user from './user';
|
||||
|
||||
export default {
|
||||
user,
|
||||
};
|
||||
137
src/store/modules/user.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { store } from '@/store';
|
||||
import { storage } from '@/utils/Storage';
|
||||
import {ACCESS_TOKEN, CURRENT_USER, LOCATION_MAP, OPEN_ID, WX_CONFIG} from "@/store/mutation-types";
|
||||
import {getUserInfo, login} from "@/api/login";
|
||||
import {ResultEnum} from "@/enums/httpEnum";
|
||||
|
||||
export interface IUserState {
|
||||
showCoupon: boolean;
|
||||
token: string;
|
||||
openId: string;
|
||||
username: string;
|
||||
welcome: string;
|
||||
avatar: string;
|
||||
permissions: any[];
|
||||
info: any;
|
||||
wxConfig: any;
|
||||
locationMap: any;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'app-user',
|
||||
state: (): IUserState => ({
|
||||
showCoupon: true,
|
||||
token: storage.get(ACCESS_TOKEN, ''),
|
||||
openId: storage.get(OPEN_ID, ''),
|
||||
// openId: storage.get(OPEN_ID, 'omWdJ62bH_6HXLQVOIefzN9J1oi4'),
|
||||
username: '',
|
||||
welcome: '',
|
||||
avatar: '',
|
||||
permissions: [],
|
||||
info: storage.get(CURRENT_USER, {}),
|
||||
wxConfig: storage.get(WX_CONFIG, null),
|
||||
locationMap: storage.get(LOCATION_MAP, {}),
|
||||
}),
|
||||
getters: {
|
||||
getShowCoupon(): boolean {
|
||||
return this.showCoupon;
|
||||
},
|
||||
getToken(): string {
|
||||
return this.token;
|
||||
},
|
||||
getOpenId(): string {
|
||||
return this.openId;
|
||||
},
|
||||
getAvatar(): string {
|
||||
return this.avatar;
|
||||
},
|
||||
getNickname(): string {
|
||||
return this.username;
|
||||
},
|
||||
getPermissions(): [any][] {
|
||||
return this.permissions;
|
||||
},
|
||||
getUserInfo(): object {
|
||||
return this.info;
|
||||
},
|
||||
getWxConfig(): object {
|
||||
return this.wxConfig;
|
||||
},
|
||||
getLocationMap(): object {
|
||||
return this.locationMap;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setShowCoupon(showCoupon: boolean) {
|
||||
this.showCoupon = showCoupon;
|
||||
},
|
||||
setToken(token: string) {
|
||||
const ex = 1 * 4 * 60 * 60;
|
||||
storage.set(ACCESS_TOKEN, token, ex);
|
||||
this.token = token;
|
||||
},
|
||||
setOpenId(openId: string) {
|
||||
const ex = 1000 * 24 * 60 * 60;
|
||||
storage.set(OPEN_ID, openId, ex);
|
||||
this.openId = openId;
|
||||
},
|
||||
setAvatar(avatar: string) {
|
||||
this.avatar = avatar;
|
||||
},
|
||||
setPermissions(permissions) {
|
||||
this.permissions = permissions;
|
||||
},
|
||||
setUserInfo(info) {
|
||||
const ex = 1 * 4 * 60 * 60;
|
||||
storage.set(CURRENT_USER, info, ex);
|
||||
this.info = info;
|
||||
},
|
||||
setWxConfig(wxConfig) {
|
||||
const ex = 7000;
|
||||
storage.set(WX_CONFIG, wxConfig, ex);
|
||||
this.wxConfig = wxConfig;
|
||||
},
|
||||
setLocationMap(locationMap) {
|
||||
const ex = 7000;
|
||||
storage.set(LOCATION_MAP, locationMap, ex);
|
||||
this.locationMap = locationMap;
|
||||
},
|
||||
// 登录
|
||||
async login(loginData) {
|
||||
try {
|
||||
const {data, code, msg} = await login(loginData)
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
this.setToken(data.token);
|
||||
const userInfo = await getUserInfo()
|
||||
this.setUserInfo(userInfo)
|
||||
return Promise.resolve(data.token);
|
||||
} else {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
// 登出
|
||||
async logout() {
|
||||
this.setToken('');
|
||||
this.setUserInfo(null);
|
||||
this.setWxConfig(null);
|
||||
// this.setLocationMap(null);
|
||||
this.setUserInfo(null);
|
||||
this.setOpenId('');
|
||||
storage.remove(ACCESS_TOKEN);
|
||||
storage.remove(CURRENT_USER);
|
||||
storage.remove(WX_CONFIG);
|
||||
// storage.remove(LOCATION_MAP);
|
||||
storage.remove(OPEN_ID);
|
||||
return Promise.resolve('');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useUserStoreWidthOut() {
|
||||
return useUserStore(store);
|
||||
}
|
||||
5
src/store/mutation-types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
|
||||
export const OPEN_ID = 'OPEN-ID'; // 用户token
|
||||
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
|
||||
export const WX_CONFIG = 'WX-CONFIG'; // 当前用户信息
|
||||
export const LOCATION_MAP = 'LOCATION-MAP'; // 当前用户信息
|
||||
96
src/string.prototype.d.ts
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
interface String {
|
||||
clearSpaces(pos?: string | null): string;
|
||||
clearBr(): string;
|
||||
clearSpacesAndBr(): string;
|
||||
clearLeftSpaces(): string;
|
||||
clearRightSpaces(): string;
|
||||
clearBothSidesSpaces(): string;
|
||||
clearInsideSpaces(): string;
|
||||
formatDate(format: string): string;
|
||||
replaceAll(searchValue: string | RegExp, replaceValue: string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除字符串所有空格
|
||||
* @param pos both(左右)|left|right|all|inner 默认all
|
||||
* @constructor
|
||||
*/
|
||||
String.prototype.clearSpaces = function (pos?: string | null = 'all') {
|
||||
if (pos === 'all') {
|
||||
return this.replace(/\s+/g, '')
|
||||
}
|
||||
if (pos === 'both') {
|
||||
return this.replace(/^\s+|\s+$/g, '')
|
||||
}
|
||||
if (pos === 'left') {
|
||||
return this.replace(/^\s*/, '')
|
||||
}
|
||||
if (pos === 'right') {
|
||||
return this.replace(/(\s*$)/g, '')
|
||||
}
|
||||
if (pos === 'inner') {
|
||||
return this.replace(/\s/g, '')
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除字符串所有换行
|
||||
* @return {string}
|
||||
*/
|
||||
String.prototype.clearBr = function () {
|
||||
const str = this.replace(/<\/?.+?>/g, '')
|
||||
str.replace(/[\r\n]/g, '')
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除字符串所有空格和回车
|
||||
* @return {string}
|
||||
*/
|
||||
String.prototype.clearSpacesAndBr = function () {
|
||||
let str = this.clearBr()
|
||||
str = str.clearSpaces()
|
||||
return str
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 清除字符串左边空格
|
||||
// * @return {string}
|
||||
// */
|
||||
// String.prototype.clearLeftSpaces = function () {
|
||||
// return this.replace(/^\s*/g, '')
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 清除字符串右边空格
|
||||
// * @return {string}
|
||||
// */
|
||||
// String.prototype.clearRightSpaces = function () {
|
||||
// return this.replace(/\s*$/g, '')
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 清除字符串两边空格
|
||||
// * @return {string}
|
||||
// */
|
||||
// String.prototype.clearBothSidesSpaces = function () {
|
||||
// return this.replace(/(^\s*)|(\s*$)/g, '')
|
||||
// }
|
||||
|
||||
/**
|
||||
* 清除字符串中间空格
|
||||
* @return {string}
|
||||
*/
|
||||
String.prototype.clearInsideSpaces = function () {
|
||||
return this.replace(/\s/g, '')
|
||||
}
|
||||
|
||||
String.prototype.formatDate = function (format) {
|
||||
const time = this.replaceAll(/-/g, "/");
|
||||
return new Date(time).format(format);
|
||||
}
|
||||
String.prototype.replaceAll = function (searchValue: string | RegExp, replaceValue: string): string{
|
||||
const target = this;
|
||||
return target.replace(new RegExp(searchValue, 'g'), replaceValue);
|
||||
}
|
||||
112
src/style/index.scss
Normal file
@@ -0,0 +1,112 @@
|
||||
#app,
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei,
|
||||
'\5FAE\8F6F\96C5\9ED1', Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
//background-color: #f7f7f7;
|
||||
background-color: #f6f9fd;
|
||||
//background: #f6f9fd
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
//重置样式
|
||||
.anticon {
|
||||
svg {
|
||||
vertical-align: initial;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2d8cf0;
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #57a3f3;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #2b85e4;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 滚动条凹槽的颜色,还可以设置边框属性 */
|
||||
*::-webkit-scrollbar-track-piece {
|
||||
background-color: #f8f8f8;
|
||||
-webkit-border-radius: 2em;
|
||||
-moz-border-radius: 2em;
|
||||
border-radius: 2em;
|
||||
}
|
||||
|
||||
/* 滚动条的宽度 */
|
||||
*::-webkit-scrollbar {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
/* 滚动条的设置 */
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #ddd;
|
||||
background-clip: padding-box;
|
||||
-webkit-border-radius: 2em;
|
||||
-moz-border-radius: 2em;
|
||||
border-radius: 2em;
|
||||
}
|
||||
|
||||
/* 滚动条鼠标移上去 */
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
/* router view transition */
|
||||
.zoom-fade-enter-active,
|
||||
.zoom-fade-leave-active {
|
||||
transition: transform 0.35s, opacity 0.28s ease-in-out;
|
||||
}
|
||||
|
||||
.zoom-fade-enter-from {
|
||||
opacity: 0;
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.zoom-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.j-ellipsis {
|
||||
word-break:keep-all;/* 不换行 */
|
||||
white-space:nowrap;/* 不换行 */
|
||||
overflow:hidden;/* 内容超出宽度时隐藏超出部分的内容 */
|
||||
text-overflow:ellipsis;/* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
|
||||
}
|
||||
|
||||
.yellow_color {
|
||||
color: #BC7C1C;
|
||||
}
|
||||
.yellow_color1 {
|
||||
color: #F9BF3A;
|
||||
}
|
||||
.gray_color {
|
||||
color: #858B9C;
|
||||
}
|
||||
101
src/type/global.d.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
import type {
|
||||
ComponentRenderProxy,
|
||||
VNode,
|
||||
VNodeChild,
|
||||
ComponentPublicInstance,
|
||||
FunctionalComponent,
|
||||
PropType as VuePropType,
|
||||
} from 'vue';
|
||||
|
||||
declare global {
|
||||
const __APP_INFO__: {
|
||||
pkg: {
|
||||
name: string;
|
||||
version: string;
|
||||
dependencies: Recordable<string>;
|
||||
devDependencies: Recordable<string>;
|
||||
};
|
||||
lastBuildTime: string;
|
||||
};
|
||||
// declare interface Window {
|
||||
// // Global vue app instance
|
||||
// __APP__: App<Element>;
|
||||
// }
|
||||
|
||||
// vue
|
||||
declare type PropType<T> = VuePropType<T>;
|
||||
declare type VueNode = VNodeChild | JSX.Element;
|
||||
|
||||
export type Writable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
};
|
||||
|
||||
declare type Nullable<T> = T | null;
|
||||
declare type NonNullable<T> = T extends null | undefined ? never : T;
|
||||
declare type Recordable<T = any> = Record<string, T>;
|
||||
declare type ReadonlyRecordable<T = any> = {
|
||||
readonly [key: string]: T;
|
||||
};
|
||||
declare type Indexable<T = any> = {
|
||||
[key: string]: T;
|
||||
};
|
||||
declare type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
declare type TimeoutHandle = ReturnType<typeof setTimeout>;
|
||||
declare type IntervalHandle = ReturnType<typeof setInterval>;
|
||||
|
||||
declare interface ChangeEvent extends Event {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
|
||||
declare interface WheelEvent {
|
||||
path?: EventTarget[];
|
||||
}
|
||||
|
||||
interface ImportMetaEnv extends ViteEnv {
|
||||
__: unknown;
|
||||
}
|
||||
|
||||
declare interface ViteEnv {
|
||||
VITE_PORT: number;
|
||||
VITE_USE_MOCK: boolean;
|
||||
VITE_PUBLIC_PATH: string;
|
||||
VITE_GLOB_APP_TITLE: string;
|
||||
VITE_GLOB_APP_SHORT_NAME: string;
|
||||
VITE_DROP_CONSOLE: boolean;
|
||||
VITE_GLOB_IMG_URL: string;
|
||||
VITE_PROXY: [string, string][];
|
||||
VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none';
|
||||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean;
|
||||
}
|
||||
|
||||
declare function parseInt(s: string | number, radix?: number): number;
|
||||
|
||||
declare function parseFloat(string: string | number): number;
|
||||
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
type Element = VNode;
|
||||
// tslint:disable no-empty-interface
|
||||
type ElementClass = ComponentRenderProxy;
|
||||
|
||||
interface ElementAttributesProperty {
|
||||
$props: any;
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
|
||||
interface IntrinsicAttributes {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
export type JSXComponent<Props = any> =
|
||||
| { new (): ComponentPublicInstance<Props> }
|
||||
| FunctionalComponent<Props>;
|
||||
}
|
||||
28
src/type/index.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
declare interface Fn<T = any, R = T> {
|
||||
(...arg: T[]): R;
|
||||
}
|
||||
|
||||
declare interface PromiseFn<T = any, R = T> {
|
||||
(...arg: T[]): Promise<R>;
|
||||
}
|
||||
|
||||
declare type RefType<T> = T | null;
|
||||
|
||||
declare type LabelValueOptions = {
|
||||
label: string;
|
||||
value: any;
|
||||
disabled: boolean;
|
||||
[key: string]: string | number | boolean;
|
||||
}[];
|
||||
|
||||
declare type EmitType = (event: string, ...args: any[]) => void;
|
||||
|
||||
declare type TargetContext = '_self' | '_blank';
|
||||
|
||||
declare interface ComponentElRef<T extends HTMLElement = HTMLDivElement> {
|
||||
$el: T;
|
||||
}
|
||||
|
||||
declare type ComponentRef<T extends HTMLElement = HTMLDivElement> = ComponentElRef<T> | null;
|
||||
|
||||
declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>;
|
||||
127
src/utils/Storage.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// 默认缓存期限为7天
|
||||
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
|
||||
|
||||
/**
|
||||
* 创建本地缓存对象
|
||||
* @param {string=} prefixKey -
|
||||
* @param {Object} [storage=localStorage] - sessionStorage | localStorage
|
||||
*/
|
||||
export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) => {
|
||||
/**
|
||||
* 本地缓存类
|
||||
* @class Storage
|
||||
*/
|
||||
const Storage = class {
|
||||
private storage = storage;
|
||||
private prefixKey?: string = prefixKey;
|
||||
|
||||
private getKey(key: string) {
|
||||
return `${this.prefixKey}${key}`.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置缓存
|
||||
* @param {string} key 缓存键
|
||||
* @param {*} value 缓存值
|
||||
* @param expire
|
||||
*/
|
||||
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
|
||||
const stringData = JSON.stringify({
|
||||
value,
|
||||
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
|
||||
});
|
||||
this.storage.setItem(this.getKey(key), stringData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @param {string} key 缓存键
|
||||
* @param {*=} def 默认值
|
||||
*/
|
||||
get(key: string, def: any = null) {
|
||||
const item = this.storage.getItem(this.getKey(key));
|
||||
if (item) {
|
||||
try {
|
||||
const data = JSON.parse(item);
|
||||
const { value, expire } = data;
|
||||
// 在有效期内直接返回
|
||||
if (expire === null || expire >= Date.now()) {
|
||||
return value;
|
||||
}
|
||||
this.remove(key);
|
||||
} catch (e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存删除某项
|
||||
* @param {string} key
|
||||
*/
|
||||
remove(key: string) {
|
||||
this.storage.removeItem(this.getKey(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有缓存
|
||||
* @memberOf Cache
|
||||
*/
|
||||
clear(): void {
|
||||
this.storage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置cookie
|
||||
* @param {string} name cookie 名称
|
||||
* @param {*} value cookie 值
|
||||
* @param {number=} expire 过期时间
|
||||
* 如果过期时间未设置,默认关闭浏览器自动删除
|
||||
* @example
|
||||
*/
|
||||
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
|
||||
document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名字获取cookie值
|
||||
* @param name
|
||||
*/
|
||||
getCookie(name: string): string {
|
||||
const cookieArr = document.cookie.split('; ');
|
||||
for (let i = 0, length = cookieArr.length; i < length; i++) {
|
||||
const kv = cookieArr[i].split('=');
|
||||
if (kv[0] === this.getKey(name)) {
|
||||
return kv[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名字删除指定的cookie
|
||||
* @param {string} key
|
||||
*/
|
||||
removeCookie(key: string) {
|
||||
this.setCookie(key, 1, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空cookie,使所有cookie失效
|
||||
*/
|
||||
clearCookie(): void {
|
||||
const keys = document.cookie.match(/[^ =;]+(?==)/g);
|
||||
if (keys) {
|
||||
for (let i = keys.length; i--; ) {
|
||||
document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return new Storage();
|
||||
};
|
||||
|
||||
export const storage = createStorage();
|
||||
|
||||
export default Storage;
|
||||
41
src/utils/dataUtil.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 重置对象会自动判断类型
|
||||
* 当你传了第二个值之后会把两个对象合并
|
||||
* 所有的值都置空
|
||||
* 数字变为0
|
||||
* @param obj
|
||||
* @param newData
|
||||
*/
|
||||
export const resetData = (obj: object, newData?: object | null) => {
|
||||
// 清空对象属性
|
||||
Object.keys(obj).forEach(key => {
|
||||
let type = "Undefined";
|
||||
if (obj[key] !== undefined) {
|
||||
type = toString.call(obj[key]).slice(8, -1); // 获取属性值的类型
|
||||
}
|
||||
switch (type) {
|
||||
case "String":
|
||||
obj[key] = "";
|
||||
break;
|
||||
case "Number":
|
||||
obj[key] = null;
|
||||
break;
|
||||
case "Boolean":
|
||||
obj[key] = false;
|
||||
break;
|
||||
case "Array":
|
||||
obj[key] = [];
|
||||
break;
|
||||
case "Date":
|
||||
obj[key] = null; // 日期类型置为 null
|
||||
break;
|
||||
case "Object":
|
||||
resetData(obj[key]); // 对象类型递归遍历
|
||||
break;
|
||||
default:
|
||||
obj[key] = null; // 其他类型都置为 null
|
||||
}
|
||||
});
|
||||
// 合并新数据到原对象上
|
||||
Object.assign(obj, newData);
|
||||
};
|
||||
212
src/utils/http/axios/Axios.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios';
|
||||
|
||||
import axios from 'axios';
|
||||
import { AxiosCanceler } from './axiosCancel';
|
||||
import { isFunction } from '@/utils/is';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
|
||||
import { ContentTypeEnum } from '@/enums/httpEnum';
|
||||
|
||||
export * from './axiosTransform';
|
||||
|
||||
/**
|
||||
* @description: axios模块
|
||||
*/
|
||||
export class VAxios {
|
||||
private axiosInstance: AxiosInstance;
|
||||
private options: CreateAxiosOptions;
|
||||
|
||||
constructor(options: CreateAxiosOptions) {
|
||||
this.options = options;
|
||||
this.axiosInstance = axios.create(options);
|
||||
this.setupInterceptors();
|
||||
}
|
||||
|
||||
getAxios(): AxiosInstance {
|
||||
return this.axiosInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 重新配置axios
|
||||
*/
|
||||
configAxios(config: CreateAxiosOptions) {
|
||||
if (!this.axiosInstance) {
|
||||
return;
|
||||
}
|
||||
this.createAxios(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 设置通用header
|
||||
*/
|
||||
setHeader(headers: any): void {
|
||||
if (!this.axiosInstance) {
|
||||
return;
|
||||
}
|
||||
Object.assign(this.axiosInstance.defaults.headers, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 请求方法
|
||||
*/
|
||||
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
let conf: AxiosRequestConfig = cloneDeep(config);
|
||||
const transform = this.getTransform();
|
||||
|
||||
const { requestOptions } = this.options;
|
||||
|
||||
const opt: RequestOptions = Object.assign({}, requestOptions, options);
|
||||
|
||||
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
|
||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
||||
conf = beforeRequestHook(conf, opt);
|
||||
}
|
||||
|
||||
//这里重新 赋值成最新的配置
|
||||
// @ts-ignore
|
||||
conf.requestOptions = opt;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axiosInstance
|
||||
.request<any, AxiosResponse<Result>>(conf)
|
||||
.then((res: AxiosResponse<Result>) => {
|
||||
// 请求是否被取消
|
||||
const isCancel = axios.isCancel(res);
|
||||
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
|
||||
try {
|
||||
const ret = transformRequestData(res, opt);
|
||||
resolve(ret);
|
||||
} catch (err) {
|
||||
reject(err || new Error('request error!'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
resolve(res as unknown as Promise<T>);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (requestCatch && isFunction(requestCatch)) {
|
||||
reject(requestCatch(e));
|
||||
return;
|
||||
}
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 创建axios实例
|
||||
*/
|
||||
private createAxios(config: CreateAxiosOptions): void {
|
||||
this.axiosInstance = axios.create(config);
|
||||
}
|
||||
|
||||
private getTransform() {
|
||||
const { transform } = this.options;
|
||||
return transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 文件上传
|
||||
*/
|
||||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
|
||||
const formData = new window.FormData();
|
||||
const customFilename = params.name || 'file';
|
||||
|
||||
let conf: AxiosRequestConfig = cloneDeep(config);
|
||||
const transform = this.getTransform();
|
||||
|
||||
const { requestOptions } = this.options;
|
||||
|
||||
const opt: RequestOptions = Object.assign({}, requestOptions);
|
||||
|
||||
const { beforeRequestHook } = transform || {};
|
||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
||||
conf = beforeRequestHook(conf, opt);
|
||||
}
|
||||
|
||||
if (params.filename) {
|
||||
formData.append(customFilename, params.file, params.filename);
|
||||
} else {
|
||||
formData.append(customFilename, params.file);
|
||||
}
|
||||
|
||||
if (params.data) {
|
||||
Object.keys(params.data).forEach((key) => {
|
||||
const value = params.data![key];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => {
|
||||
formData.append(`${key}[]`, item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
formData.append(key, params.data![key]);
|
||||
});
|
||||
}
|
||||
|
||||
return this.axiosInstance.request<T>({
|
||||
...conf,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
ignoreCancelToken: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 拦截器配置
|
||||
*/
|
||||
private setupInterceptors() {
|
||||
const transform = this.getTransform();
|
||||
if (!transform) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
requestInterceptors,
|
||||
requestInterceptorsCatch,
|
||||
responseInterceptors,
|
||||
responseInterceptorsCatch,
|
||||
} = transform;
|
||||
|
||||
const axiosCanceler = new AxiosCanceler();
|
||||
|
||||
// 请求拦截器配置处理
|
||||
// @ts-ignore
|
||||
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
|
||||
// @ts-ignore
|
||||
const { headers: { ignoreCancelToken } } = config;
|
||||
const ignoreCancel =
|
||||
ignoreCancelToken !== undefined
|
||||
? ignoreCancelToken
|
||||
: this.options.requestOptions?.ignoreCancelToken;
|
||||
|
||||
!ignoreCancel && axiosCanceler.addPending(config);
|
||||
if (requestInterceptors && isFunction(requestInterceptors)) {
|
||||
config = requestInterceptors(config, this.options);
|
||||
}
|
||||
return config;
|
||||
}, undefined);
|
||||
|
||||
// 请求拦截器错误捕获
|
||||
requestInterceptorsCatch &&
|
||||
isFunction(requestInterceptorsCatch) &&
|
||||
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch);
|
||||
|
||||
// 响应结果拦截器处理
|
||||
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
|
||||
res && axiosCanceler.removePending(res.config);
|
||||
if (responseInterceptors && isFunction(responseInterceptors)) {
|
||||
res = responseInterceptors(res, {});
|
||||
}
|
||||
return res;
|
||||
}, undefined);
|
||||
|
||||
// 响应结果拦截器错误捕获
|
||||
responseInterceptorsCatch &&
|
||||
isFunction(responseInterceptorsCatch) &&
|
||||
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
|
||||
}
|
||||
}
|
||||
61
src/utils/http/axios/axiosCancel.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import axios, { AxiosRequestConfig, Canceler } from 'axios';
|
||||
import qs from 'qs';
|
||||
|
||||
import { isFunction } from '@/utils/is';
|
||||
|
||||
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
|
||||
let pendingMap = new Map<string, Canceler>();
|
||||
|
||||
export const getPendingUrl = (config: AxiosRequestConfig) =>
|
||||
[config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&');
|
||||
|
||||
export class AxiosCanceler {
|
||||
/**
|
||||
* 添加请求
|
||||
* @param {Object} config
|
||||
*/
|
||||
addPending(config: AxiosRequestConfig) {
|
||||
this.removePending(config);
|
||||
const url = getPendingUrl(config);
|
||||
config.cancelToken =
|
||||
config.cancelToken ||
|
||||
new axios.CancelToken((cancel) => {
|
||||
if (!pendingMap.has(url)) {
|
||||
// 如果 pending 中不存在当前请求,则添加进去
|
||||
pendingMap.set(url, cancel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 清空所有pending
|
||||
*/
|
||||
removeAllPending() {
|
||||
pendingMap.forEach((cancel) => {
|
||||
cancel && isFunction(cancel) && cancel();
|
||||
});
|
||||
pendingMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除请求
|
||||
* @param {Object} config
|
||||
*/
|
||||
removePending(config: AxiosRequestConfig) {
|
||||
const url = getPendingUrl(config);
|
||||
|
||||
if (pendingMap.has(url)) {
|
||||
// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
|
||||
const cancel = pendingMap.get(url);
|
||||
cancel && cancel(url);
|
||||
pendingMap.delete(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 重置
|
||||
*/
|
||||
reset(): void {
|
||||
pendingMap = new Map<string, Canceler>();
|
||||
}
|
||||
}
|
||||
52
src/utils/http/axios/axiosTransform.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 数据处理类,可以根据项目自行配置
|
||||
*/
|
||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import type { RequestOptions, Result } from './types';
|
||||
|
||||
export interface CreateAxiosOptions extends AxiosRequestConfig {
|
||||
authenticationScheme?: string;
|
||||
transform?: AxiosTransform;
|
||||
requestOptions?: RequestOptions;
|
||||
}
|
||||
|
||||
export abstract class AxiosTransform {
|
||||
/**
|
||||
* @description: 请求之前处理配置
|
||||
* @description: Process configuration before request
|
||||
*/
|
||||
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;
|
||||
|
||||
/**
|
||||
* @description: 请求成功处理
|
||||
*/
|
||||
transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
|
||||
|
||||
/**
|
||||
* @description: 请求失败处理
|
||||
*/
|
||||
requestCatch?: (e: Error) => Promise<any>;
|
||||
|
||||
/**
|
||||
* @description: 请求之前的拦截器
|
||||
*/
|
||||
requestInterceptors?: (
|
||||
config: AxiosRequestConfig,
|
||||
options: CreateAxiosOptions
|
||||
) => AxiosRequestConfig;
|
||||
|
||||
/**
|
||||
* @description: 请求之后的拦截器
|
||||
*/
|
||||
responseInterceptors?: (res: AxiosResponse<any>, options: RequestOptions) => AxiosResponse<any>;
|
||||
|
||||
/**
|
||||
* @description: 请求之前的拦截器错误处理
|
||||
*/
|
||||
requestInterceptorsCatch?: (error: Error) => void;
|
||||
|
||||
/**
|
||||
* @description: 请求之后的拦截器错误处理
|
||||
*/
|
||||
responseInterceptorsCatch?: (error: Error) => void;
|
||||
}
|
||||
47
src/utils/http/axios/checkStatus.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { showFailToast } from 'vant';
|
||||
export function checkStatus(status: number, msg: string): void {
|
||||
switch (status) {
|
||||
case 400:
|
||||
showFailToast(msg);
|
||||
break;
|
||||
// 401: 未登录
|
||||
// 未登录则跳转登录页面,并携带当前页面的路径
|
||||
// 在登录成功后返回当前页面,这一步需要在登录页操作。
|
||||
case 401:
|
||||
showFailToast('用户没有权限(令牌、用户名、密码错误)!');
|
||||
break;
|
||||
case 403:
|
||||
showFailToast('用户得到授权,但是访问是被禁止的。!');
|
||||
break;
|
||||
// 404请求不存在
|
||||
case 404:
|
||||
showFailToast('网络请求错误,未找到该资源!');
|
||||
break;
|
||||
case 405:
|
||||
showFailToast('网络请求错误,请求方法未允许!');
|
||||
break;
|
||||
case 408:
|
||||
showFailToast('网络请求超时');
|
||||
break;
|
||||
case 500:
|
||||
showFailToast('服务器错误,请联系管理员!');
|
||||
break;
|
||||
case 501:
|
||||
showFailToast('网络未实现');
|
||||
break;
|
||||
case 502:
|
||||
showFailToast('网络错误');
|
||||
break;
|
||||
case 503:
|
||||
showFailToast('服务不可用,服务器暂时过载或维护!');
|
||||
break;
|
||||
case 504:
|
||||
showFailToast('网络超时');
|
||||
break;
|
||||
case 505:
|
||||
showFailToast('http版本不支持该请求!');
|
||||
break;
|
||||
default:
|
||||
showFailToast(msg);
|
||||
}
|
||||
}
|
||||
47
src/utils/http/axios/helper.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { isObject, isString } from '@/utils/is';
|
||||
|
||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
|
||||
|
||||
export function joinTimestamp<T extends boolean>(
|
||||
join: boolean,
|
||||
restful: T
|
||||
): T extends true ? string : object;
|
||||
|
||||
export function joinTimestamp(join: boolean, restful = false): string | object {
|
||||
if (!join) {
|
||||
return restful ? '' : {};
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
if (restful) {
|
||||
return `?_t=${now}`;
|
||||
}
|
||||
return { _t: now };
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Format request parameter time
|
||||
*/
|
||||
export function formatRequestDate(params: Recordable) {
|
||||
if (Object.prototype.toString.call(params) !== '[object Object]') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in params) {
|
||||
if (params[key] && params[key]._isAMomentObject) {
|
||||
params[key] = params[key].format(DATE_TIME_FORMAT);
|
||||
}
|
||||
if (isString(key)) {
|
||||
const value = params[key];
|
||||
if (value) {
|
||||
try {
|
||||
params[key] = isString(value) ? value.trim() : value;
|
||||
} catch (error) {
|
||||
throw new Error(error as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isObject(params[key])) {
|
||||
formatRequestDate(params[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
289
src/utils/http/axios/index.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
|
||||
import {showDialog, showFailToast, showSuccessToast} from 'vant';
|
||||
import { VAxios } from './Axios';
|
||||
import { AxiosTransform } from './axiosTransform';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { checkStatus } from './checkStatus';
|
||||
import { joinTimestamp, formatRequestDate } from './helper';
|
||||
import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum';
|
||||
|
||||
import { isString } from '@/utils/is/';
|
||||
import { deepMerge, isUrl } from '@/utils';
|
||||
import { setObjToUrlParams } from '@/utils/urlUtils';
|
||||
|
||||
import { RequestOptions, Result, CreateAxiosOptions } from './types';
|
||||
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
const urlPrefix = '';
|
||||
|
||||
// import router from '@/router';
|
||||
// import { storage } from '@/utils/Storage';
|
||||
|
||||
/**
|
||||
* @description: 数据处理,方便区分多种处理方式
|
||||
*/
|
||||
const transform: AxiosTransform = {
|
||||
/**
|
||||
* @description: 处理请求数据
|
||||
*/
|
||||
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||||
const {
|
||||
isShowMessage = false,
|
||||
isShowErrorMessage,
|
||||
isShowSuccessMessage,
|
||||
successMessageText,
|
||||
errorMessageText,
|
||||
isTransformResponse,
|
||||
isReturnNativeResponse,
|
||||
} = options;
|
||||
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
if (isReturnNativeResponse) {
|
||||
return res;
|
||||
}
|
||||
// 不进行任何处理,直接返回
|
||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||
if (!isTransformResponse) {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
// const $dialog = window['$dialog'];
|
||||
// const $message = window['$message'];
|
||||
|
||||
if (!res.data) {
|
||||
// return '[HTTP] Request has no return value';
|
||||
throw new Error('请求出错,请稍候重试');
|
||||
}
|
||||
// rows 特殊处理
|
||||
if (res.data.rows) {
|
||||
res.data.data = res.data.rows
|
||||
}
|
||||
// 这里 code,result,message为 后台统一的字段,需要修改为项目自己的接口返回格式
|
||||
const { code, data, msg } = res.data;
|
||||
// 是否显示提示信息
|
||||
if (isShowMessage) {
|
||||
// 请求成功
|
||||
const hasSuccess = data && Reflect.has(res.data, 'code') && code === ResultEnum.SUCCESS;
|
||||
if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
|
||||
// 是否显示自定义信息提示
|
||||
showSuccessToast(successMessageText || msg || '操作成功!');
|
||||
} else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
|
||||
// 是否显示自定义信息提示
|
||||
showFailToast(msg || errorMessageText || '操作失败!');
|
||||
} else if (!hasSuccess && options.errorMessageMode === 'modal') {
|
||||
// errorMessageMode=‘custom-modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
||||
showDialog({
|
||||
title: '提示',
|
||||
message: msg,
|
||||
confirmButtonText: '确定'
|
||||
}).then(() => {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 接口请求成功,直接返回结果
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
return data;
|
||||
}
|
||||
// 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
|
||||
let errorMsg = msg;
|
||||
switch (code) {
|
||||
// 请求失败
|
||||
case ResultEnum.ERROR:
|
||||
showFailToast(errorMsg);
|
||||
break;
|
||||
// 登录超时
|
||||
// case ResultEnum.TIMEOUT:
|
||||
// const LoginName = PageEnum.BASE_LOGIN_NAME;
|
||||
// const LoginPath = PageEnum.BASE_LOGIN;
|
||||
// if (router.currentRoute.value?.name === LoginName) return;
|
||||
// // 到登录页
|
||||
// errorMsg = '登录超时,请重新登录!';
|
||||
// $dialog.warning({
|
||||
// title: '提示',
|
||||
// content: '登录身份已失效,请重新登录!',
|
||||
// positiveText: '确定',
|
||||
// //negativeText: '取消',
|
||||
// closable: false,
|
||||
// maskClosable: false,
|
||||
// onPositiveClick: () => {
|
||||
// storage.clear();
|
||||
// window.location.href = LoginPath;
|
||||
// },
|
||||
// onNegativeClick: () => {},
|
||||
// });
|
||||
// break;
|
||||
}
|
||||
throw new Error(errorMsg);
|
||||
},
|
||||
|
||||
// 请求之前处理config
|
||||
beforeRequestHook: (config, options) => {
|
||||
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
|
||||
|
||||
const isUrlStr = isUrl(config.url as string);
|
||||
|
||||
if (!isUrlStr && joinPrefix) {
|
||||
config.url = `${urlPrefix}${config.url}`;
|
||||
}
|
||||
|
||||
if (!isUrlStr && apiUrl && isString(apiUrl)) {
|
||||
config.url = `${apiUrl}${config.url}`;
|
||||
}
|
||||
const params = config.params || {};
|
||||
const data = config.data || false;
|
||||
if (config.method?.toUpperCase() === RequestEnum.GET) {
|
||||
if (!isString(params)) {
|
||||
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
|
||||
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
|
||||
} else {
|
||||
// 兼容restful风格
|
||||
config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
|
||||
config.params = undefined;
|
||||
}
|
||||
} else {
|
||||
if (!isString(params)) {
|
||||
formatDate && formatRequestDate(params);
|
||||
if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
|
||||
config.data = data;
|
||||
config.params = params;
|
||||
} else {
|
||||
config.data = params;
|
||||
config.params = undefined;
|
||||
}
|
||||
if (joinParamsToUrl) {
|
||||
config.url = setObjToUrlParams(
|
||||
config.url as string,
|
||||
Object.assign({}, config.params, config.data)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 兼容restful风格
|
||||
config.url = config.url + params;
|
||||
config.params = undefined;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
/**
|
||||
* @description: 请求拦截器处理
|
||||
*/
|
||||
requestInterceptors: (config, options) => {
|
||||
// 请求之前处理config
|
||||
const headers = (config as Recordable).headers
|
||||
const userStore = useUserStoreWidthOut();
|
||||
const token = userStore.getToken;
|
||||
headers.TYPE = 'TBS'
|
||||
headers.channelId = 'MP'
|
||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
||||
// jwt token
|
||||
headers.Authorization = options.authenticationScheme
|
||||
? `${options.authenticationScheme} ${token}`
|
||||
: token;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
/**
|
||||
* @description: 响应错误处理
|
||||
*/
|
||||
responseInterceptorsCatch: (error: any) => {
|
||||
// const $dialog = window['$dialog'];
|
||||
// const $message = window['$message'];
|
||||
const { response, code, message } = error || {};
|
||||
// TODO 此处要根据后端接口返回格式修改
|
||||
const msg: string =
|
||||
response && response.data && response.data.message ? response.data.message : '';
|
||||
const err: string = error.toString();
|
||||
try {
|
||||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
||||
showFailToast('接口请求超时,请刷新页面重试!');
|
||||
return;
|
||||
}
|
||||
if (err && err.includes('Network Error')) {
|
||||
showFailToast('请检查您的网络连接是否正常')
|
||||
// $dialog.info({
|
||||
// title: '网络异常',
|
||||
// content: '请检查您的网络连接是否正常',
|
||||
// positiveText: '确定',
|
||||
// //negativeText: '取消',
|
||||
// closable: false,
|
||||
// maskClosable: false,
|
||||
// onPositiveClick: () => {},
|
||||
// onNegativeClick: () => {},
|
||||
// });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(error as any);
|
||||
}
|
||||
// 请求是否被取消
|
||||
const isCancel = axios.isCancel(error);
|
||||
if (!isCancel) {
|
||||
checkStatus(error.response && error.response.status, msg);
|
||||
} else {
|
||||
console.warn(error, '请求被取消!');
|
||||
}
|
||||
//return Promise.reject(error);
|
||||
return Promise.reject(response?.data);
|
||||
},
|
||||
};
|
||||
|
||||
function createAxios(opt?: Partial<CreateAxiosOptions>) {
|
||||
return new VAxios(
|
||||
deepMerge(
|
||||
{
|
||||
timeout: 10 * 1000,
|
||||
authenticationScheme: '',
|
||||
// 接口前缀
|
||||
prefixUrl: urlPrefix,
|
||||
headers: { 'Content-Type': ContentTypeEnum.JSON },
|
||||
// 数据处理方式
|
||||
transform,
|
||||
// 配置项,下面的选项都可以在独立的接口请求中覆盖
|
||||
requestOptions: {
|
||||
// 默认将prefix 添加到url
|
||||
joinPrefix: true,
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
isReturnNativeResponse: false,
|
||||
// 需要对返回数据进行处理
|
||||
isTransformResponse: true,
|
||||
// post请求的时候添加参数到url
|
||||
joinParamsToUrl: false,
|
||||
// 格式化提交参数时间
|
||||
formatDate: true,
|
||||
// 消息提示类型
|
||||
errorMessageMode: 'none',
|
||||
// 接口地址
|
||||
apiUrl: 'http://101.32.189.213:2781',
|
||||
// apiUrl: '',
|
||||
// 接口拼接地址
|
||||
urlPrefix: urlPrefix,
|
||||
// 是否加入时间戳
|
||||
joinTime: true,
|
||||
// 忽略重复请求
|
||||
ignoreCancelToken: true,
|
||||
// 是否携带token
|
||||
withToken: true,
|
||||
},
|
||||
withCredentials: false,
|
||||
},
|
||||
opt || {}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const http = createAxios();
|
||||
|
||||
// 项目,多个不同 api 地址,直接在这里导出多个
|
||||
// src/api ts 里面接口,就可以单独使用这个请求,
|
||||
// import { httpTwo } from '@/utils/http/axios'
|
||||
// export const httpTwo = createAxios({
|
||||
// requestOptions: {
|
||||
// apiUrl: 'http://localhost:9001',
|
||||
// urlPrefix: 'api',
|
||||
// },
|
||||
// });
|
||||
66
src/utils/http/axios/types.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { AxiosTransform } from '@/utils/http/axios/axiosTransform';
|
||||
|
||||
export interface CreateAxiosOptions extends AxiosRequestConfig {
|
||||
transform?: AxiosTransform;
|
||||
requestOptions?: RequestOptions;
|
||||
authenticationScheme?: string;
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
export interface UploadFileParams {
|
||||
// 其他参数
|
||||
data?: Recordable;
|
||||
// 文件参数接口字段名
|
||||
name?: string;
|
||||
// 文件
|
||||
file: File | Blob;
|
||||
// 文件名称
|
||||
filename?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface RequestOptions {
|
||||
// 请求参数拼接到url
|
||||
joinParamsToUrl?: boolean;
|
||||
// 格式化请求参数时间
|
||||
formatDate?: boolean;
|
||||
// 是否显示提示信息
|
||||
isShowMessage?: boolean;
|
||||
// 是否解析成JSON
|
||||
isParseToJson?: boolean;
|
||||
// 成功的文本信息
|
||||
successMessageText?: string;
|
||||
// 是否显示成功信息
|
||||
isShowSuccessMessage?: boolean;
|
||||
// 是否显示失败信息
|
||||
isShowErrorMessage?: boolean;
|
||||
// 错误的文本信息
|
||||
errorMessageText?: string;
|
||||
// 是否加入url
|
||||
joinPrefix?: boolean;
|
||||
// 接口地址, 不填则使用默认apiUrl
|
||||
apiUrl?: string;
|
||||
// 请求拼接路径
|
||||
urlPrefix?: string;
|
||||
// 错误消息提示类型
|
||||
errorMessageMode?: 'none' | 'modal';
|
||||
// 是否添加时间戳
|
||||
joinTime?: boolean;
|
||||
// 不进行任何处理,直接返回
|
||||
isTransformResponse?: boolean;
|
||||
// 是否返回原生响应头
|
||||
isReturnNativeResponse?: boolean;
|
||||
//忽略重复请求
|
||||
ignoreCancelToken?: boolean;
|
||||
// 是否携带token
|
||||
withToken?: boolean;
|
||||
}
|
||||
|
||||
export interface Result<T = any> {
|
||||
code: Number;
|
||||
type?: 'success' | 'error' | 'warning';
|
||||
msg: string;
|
||||
data?: T;
|
||||
rows?: T;
|
||||
}
|
||||
278
src/utils/index.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import {isObject} from "@/utils/is";
|
||||
|
||||
/** px 转 vw, 375 是设置的屏幕宽度 */
|
||||
export const px2vw = (px: number): string => {
|
||||
return `${window.screen.width / 750 * px / window.screen.width * 100}vw`
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否 url
|
||||
* */
|
||||
export function isUrl(url: string) {
|
||||
return /^(http|https):\/\//g.test(url);
|
||||
}
|
||||
|
||||
|
||||
export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
|
||||
let key: string;
|
||||
for (key in target) {
|
||||
src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
// ¥6,285
|
||||
export function toIntMark(value) {
|
||||
try {
|
||||
if (value.toString().includes('%')) {
|
||||
return value
|
||||
}
|
||||
const newVal = parseFloat(value).toFixed(0)
|
||||
if (newVal == 'NaN') {
|
||||
return value
|
||||
}
|
||||
return '¥' + newVal.replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, '$1,')
|
||||
} catch (e) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// 6,285
|
||||
export function toInt(value) {
|
||||
try {
|
||||
if (value.toString().includes('%')) {
|
||||
return value
|
||||
}
|
||||
const newVal = parseFloat(value).toFixed(0)
|
||||
if (newVal == 'NaN') {
|
||||
return value
|
||||
}
|
||||
return newVal.replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, '$1,')
|
||||
} catch (e) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// ¥6k
|
||||
export function toThousand(value) {
|
||||
try {
|
||||
if (!value) {
|
||||
return 0
|
||||
}
|
||||
if (value.toString().includes('%')) {
|
||||
return value
|
||||
}
|
||||
const newVal = parseFloat(value / 1000 + '').toFixed(0)
|
||||
if (newVal == 'NaN') {
|
||||
return value
|
||||
}
|
||||
return '¥' + newVal.replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, '$1,') + 'k'
|
||||
} catch (e) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// ¥6,285.00
|
||||
export function toRoundMark(value: number | string) {
|
||||
value = value || 0
|
||||
const options = {
|
||||
style: 'currency',
|
||||
currency: 'CNY',
|
||||
};
|
||||
return value.toLocaleString('zh-CN', options)
|
||||
}
|
||||
|
||||
// 6285.00
|
||||
export function toRound(value) {
|
||||
try {
|
||||
if (value.toString().includes('%')) {
|
||||
return value
|
||||
}
|
||||
const newVal = parseFloat(value).toFixed(2)
|
||||
if (newVal == 'NaN') {
|
||||
return value
|
||||
}
|
||||
return newVal
|
||||
} catch (e) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export const regFenToYuan = (fen) => {
|
||||
let num;
|
||||
num=fen*0.01;
|
||||
num+='';
|
||||
const reg = num.indexOf('.') > -1 ? /(\d{1,3})(?=(?:\d{3})+\.)/g : /(\d{1,3})(?=(?:\d{3})+$)/g;
|
||||
num=num.replace(reg,'$1');
|
||||
num = toDecimal2(num)
|
||||
return num
|
||||
}
|
||||
|
||||
export const toDecimal2 = (x) => {
|
||||
let f = parseFloat(x);
|
||||
if (isNaN(f)) {
|
||||
return false;
|
||||
}
|
||||
f = Math.round(x * 100) / 100;
|
||||
let s = f.toString();
|
||||
let rs = s.indexOf('.');
|
||||
if (rs < 0) {
|
||||
rs = s.length;
|
||||
s += '.';
|
||||
}
|
||||
while (s.length <= rs + 2) {
|
||||
s += '0';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param customStyle
|
||||
* @param target
|
||||
*/
|
||||
export function addStyle(customStyle, target = 'object') {
|
||||
// 字符串转字符串,对象转对象情形,直接返回
|
||||
if (empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&
|
||||
typeof(customStyle) === 'string') {
|
||||
return customStyle
|
||||
}
|
||||
// 字符串转对象
|
||||
if (target === 'object') {
|
||||
// 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
|
||||
customStyle = customStyle.clearSpaces('both')
|
||||
// 根据";"将字符串转为数组形式
|
||||
const styleArray = customStyle.split(';')
|
||||
const style = {}
|
||||
// 历遍数组,拼接成对象
|
||||
for (let i = 0; i < styleArray.length; i++) {
|
||||
// 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
|
||||
if (styleArray[i]) {
|
||||
const item = styleArray[i].split(':')
|
||||
style[item[0].clearSpaces('both')] = item[1].clearSpaces('both')
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
// 这里为对象转字符串形式
|
||||
let string = ''
|
||||
for (const i in customStyle) {
|
||||
// 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
|
||||
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||
string += `${key}:${customStyle[i]};`
|
||||
}
|
||||
// 去除两端空格
|
||||
return string.clearSpaces('both')
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为空
|
||||
*/
|
||||
export function empty(value) {
|
||||
switch (typeof value) {
|
||||
case 'undefined':
|
||||
return true
|
||||
case 'string':
|
||||
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length === 0) return true
|
||||
break
|
||||
case 'boolean':
|
||||
if (!value) return true
|
||||
break
|
||||
case 'number':
|
||||
if (value === 0 || isNaN(value)) return true
|
||||
break
|
||||
case 'object':
|
||||
if (value === null || value.length === 0) return true
|
||||
// for (const i in value) {
|
||||
// return false
|
||||
// }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取本地图片
|
||||
* @param name // 文件名 如 doc.png
|
||||
* @returns {*|string}
|
||||
*/
|
||||
export function getAssetsImages(name) {
|
||||
return new URL(`/src/assets/images/${name}`, import.meta.url).href;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加法 (处理精度问题)
|
||||
* @param num1
|
||||
* @param num2
|
||||
* @returns {string}
|
||||
*/
|
||||
export function numAdd(num1, num2) {
|
||||
let baseNum, baseNum1, baseNum2, precision // 精度
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length
|
||||
} catch (e) {
|
||||
baseNum1 = 0
|
||||
}
|
||||
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length
|
||||
} catch (e) {
|
||||
baseNum2 = 0
|
||||
}
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
|
||||
precision = (baseNum1 >= baseNum2) ? baseNum1 : baseNum2
|
||||
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision)
|
||||
}
|
||||
|
||||
/**
|
||||
* 减法 (处理精度问题)
|
||||
* @param num1
|
||||
* @param num2
|
||||
* @returns {string}
|
||||
*/
|
||||
export function numSub(num1, num2) {
|
||||
let baseNum, baseNum1, baseNum2, precision // 精度
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length
|
||||
} catch (e) {
|
||||
baseNum1 = 0
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length
|
||||
} catch (e) {
|
||||
baseNum2 = 0
|
||||
}
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
|
||||
precision = (baseNum1 >= baseNum2) ? baseNum1 : baseNum2
|
||||
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打电话
|
||||
* @param phoneNumber
|
||||
*/
|
||||
export const callPhone = (phoneNumber) => {
|
||||
if (phoneNumber) {
|
||||
window.location.href = "tel://" + phoneNumber;
|
||||
}
|
||||
}
|
||||
|
||||
export function debounce(func: Function, time: number, immediate = false) {
|
||||
let timer: number | null = null;
|
||||
return (...args: any) => {
|
||||
if (timer) clearInterval(timer)
|
||||
if (immediate) {
|
||||
if (!timer) func.apply(this, args);
|
||||
timer = window.setTimeout(() => {
|
||||
timer = null
|
||||
}, time)
|
||||
} else {
|
||||
timer = window.setTimeout(() => {
|
||||
func.apply(this, args)
|
||||
}, time)
|
||||
}
|
||||
}
|
||||
}
|
||||
118
src/utils/is/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
const toString = Object.prototype.toString;
|
||||
|
||||
/**
|
||||
* @description: 判断值是否未某个类型
|
||||
*/
|
||||
export function is(val: unknown, type: string) {
|
||||
return toString.call(val) === `[object ${type}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为函数
|
||||
*/
|
||||
export function isFunction<T = Function>(val: unknown): val is T {
|
||||
return is(val, 'Function') || is(val, 'AsyncFunction');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否已定义
|
||||
*/
|
||||
export const isDef = <T = unknown>(val?: T): val is T => {
|
||||
return typeof val !== 'undefined';
|
||||
};
|
||||
|
||||
export const isUnDef = <T = unknown>(val?: T): val is T => {
|
||||
return !isDef(val);
|
||||
};
|
||||
/**
|
||||
* @description: 是否为对象
|
||||
*/
|
||||
export const isObject = (val: any): val is Record<any, any> => {
|
||||
return val !== null && is(val, 'Object');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: 是否为时间
|
||||
*/
|
||||
export function isDate(val: unknown): val is Date {
|
||||
return is(val, 'Date');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为数值
|
||||
*/
|
||||
export function isNumber(val: unknown): val is number {
|
||||
return is(val, 'Number');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为AsyncFunction
|
||||
*/
|
||||
export function isAsyncFunction<T = any>(val: unknown): val is () => Promise<T> {
|
||||
return is(val, 'AsyncFunction');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为promise
|
||||
*/
|
||||
export function isPromise<T = any>(val: unknown): val is Promise<T> {
|
||||
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为字符串
|
||||
*/
|
||||
export function isString(val: unknown): val is string {
|
||||
return is(val, 'String');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为boolean类型
|
||||
*/
|
||||
export function isBoolean(val: unknown): val is boolean {
|
||||
return is(val, 'Boolean');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为数组
|
||||
*/
|
||||
export function isArray(val: any): val is Array<any> {
|
||||
return val && Array.isArray(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否客户端
|
||||
*/
|
||||
export const isClient = () => {
|
||||
return typeof window !== 'undefined';
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: 是否为浏览器
|
||||
*/
|
||||
export const isWindow = (val: any): val is Window => {
|
||||
return typeof window !== 'undefined' && is(val, 'Window');
|
||||
};
|
||||
|
||||
export const isElement = (val: unknown): val is Element => {
|
||||
return isObject(val) && !!val.tagName;
|
||||
};
|
||||
|
||||
export const isServer = typeof window === 'undefined';
|
||||
|
||||
// 是否为图片节点
|
||||
export function isImageDom(o: Element) {
|
||||
return o && ['IMAGE', 'IMG'].includes(o.tagName);
|
||||
}
|
||||
|
||||
export function isNull(val: unknown): val is null {
|
||||
return val === null;
|
||||
}
|
||||
|
||||
export function isNullAndUnDef(val: unknown): val is null | undefined {
|
||||
return isUnDef(val) && isNull(val);
|
||||
}
|
||||
|
||||
export function isNullOrUnDef(val: unknown): val is null | undefined {
|
||||
return isUnDef(val) || isNull(val);
|
||||
}
|
||||
47
src/utils/mapUtil.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import AMapLoader from "@amap/amap-jsapi-loader";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
|
||||
|
||||
|
||||
export const getLocation = () => {
|
||||
const user = useUserStore()
|
||||
|
||||
|
||||
AMapLoader.load({
|
||||
key: 'cd9769b4feec118fe176763bd10168a0',
|
||||
version: "2.0",
|
||||
AMapUI: {
|
||||
version: "1.1",
|
||||
},
|
||||
Loca: {
|
||||
version: "2.0.0"
|
||||
},
|
||||
}).then((AMap) => {
|
||||
const map = new AMap.Map("container", {
|
||||
zoom: 15,
|
||||
features: ['bg', 'point', 'road', 'building'],
|
||||
viewMode: '2D', //设置地图模式
|
||||
// center: [116.47394,39.904211],
|
||||
resizeEnable: true
|
||||
})
|
||||
|
||||
AMap.plugin(['AMap.AutoComplete', 'AMap.PlaceSearch', 'AMap.Geolocation', 'AMap.CitySearch'], () => {
|
||||
const geolocation = new AMap.Geolocation({
|
||||
enableHighAccuracy: true, // 是否使用高精度定位,默认:true
|
||||
timeout: 10000, // 设置定位超时时间,默认:无穷大
|
||||
showButton: true, //显示定位按钮,默认:true
|
||||
needAddress: true, //是否需要将定位结果进行逆地理编码操作
|
||||
convert: true, //自动偏移坐标,偏移后的坐标为高德坐标,默认:true
|
||||
position: 'RB',
|
||||
offset: [10, 20],
|
||||
})
|
||||
map.addControl(geolocation)
|
||||
geolocation.getCurrentPosition()
|
||||
// 获取用户当前的精确位置信息
|
||||
geolocation.getCurrentPosition((status, result) => {
|
||||
// myCity.value = result.addressComponent.province
|
||||
user.setLocationMap(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
37
src/utils/urlUtils.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 将对象添加当作参数拼接到URL上面
|
||||
* @param baseUrl 需要拼接的url
|
||||
* @param obj 参数对象
|
||||
* @returns {string} 拼接后的对象
|
||||
* 例子:
|
||||
* let obj = {a: '3', b: '4'}
|
||||
* setObjToUrlParams('www.baidu.com', obj)
|
||||
* ==>www.baidu.com?a=3&b=4
|
||||
*/
|
||||
export function setObjToUrlParams(baseUrl: string, obj: any): string {
|
||||
let parameters = '';
|
||||
let url = '';
|
||||
for (const key in obj) {
|
||||
parameters += key + '=' + encodeURIComponent(obj[key]) + '&';
|
||||
}
|
||||
parameters = parameters.replace(/&$/, '');
|
||||
if (/\?$/.test(baseUrl)) {
|
||||
url = baseUrl + parameters;
|
||||
} else {
|
||||
url = baseUrl.replace(/\/?$/, '?') + parameters;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据name从url中取值
|
||||
* @param name
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getQueryString(name) {
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r != null) return unescape(r[2]);
|
||||
return null;
|
||||
}
|
||||
99
src/utils/wexin.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import wx from "weixin-js-sdk";
|
||||
import {jsapiToken} from "@/api/pay";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import {showToast} from "vant";
|
||||
|
||||
export const getWxConfig = (url) => {
|
||||
const user = useUserStore()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (user.getWxConfig) {
|
||||
resolve(user.getWxConfig)
|
||||
} else {
|
||||
jsapiToken({
|
||||
url: url
|
||||
}).then(res => {
|
||||
user.setWxConfig(res)
|
||||
resolve(user.getWxConfig)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const getLocation = (params, callback) => {
|
||||
getWxConfig(params.url).then(res => {
|
||||
wx.config({
|
||||
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
|
||||
appId: res.appId, // 必填,公众号的唯一标识
|
||||
timestamp: res.timestamp, // 必填,生成签名的时间戳
|
||||
nonceStr: res.nonceStr, // 必填,生成签名的随机串
|
||||
signature: res.signature, // 必填,签名
|
||||
jsApiList: ['checkJsApi', 'getLocation'] // 必填,需要使用的JS接口列表
|
||||
})
|
||||
wx.ready(() => {
|
||||
wx.checkJsApi({
|
||||
jsApiList: ['getLocation'],
|
||||
success: function () {
|
||||
wx.getLocation({
|
||||
type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
|
||||
success: (res) => {
|
||||
console.log('res11111')
|
||||
callback(res)
|
||||
},
|
||||
fail: (res) => {
|
||||
// showToast('定位失败 -- > ' + JSON.stringify(res))
|
||||
console.error('定位失败')
|
||||
},
|
||||
cancel: function (res) {
|
||||
console.error(res)
|
||||
},
|
||||
});
|
||||
},
|
||||
fail: function (res) {
|
||||
showToast('fail -- > ' + JSON.stringify(res))
|
||||
console.error(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
wx.error(err => {
|
||||
showToast('err -- > ' + JSON.stringify(err))
|
||||
console.error(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const openLocation = (params) => {
|
||||
getWxConfig(params.url).then(res => {
|
||||
wx.config({
|
||||
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
|
||||
appId: res.appId, // 必填,公众号的唯一标识
|
||||
timestamp: res.timestamp, // 必填,生成签名的时间戳
|
||||
nonceStr: res.nonceStr, // 必填,生成签名的随机串
|
||||
signature: res.signature, // 必填,签名
|
||||
jsApiList: ['checkJsApi', 'openLocation'] // 必填,需要使用的JS接口列表
|
||||
})
|
||||
wx.ready(() => {
|
||||
wx.checkJsApi({
|
||||
jsApiList: ['openLocation'],
|
||||
success: function () {
|
||||
wx.openLocation({
|
||||
latitude: params.latitude, // 纬度,浮点数,范围为90 ~ -90
|
||||
longitude: params.longitude, // 经度,浮点数,范围为180 ~ -180。
|
||||
name: '', // 位置名
|
||||
address: '', // 地址详情说明
|
||||
scale: 1, // 地图缩放级别,整型值,范围从1~28。默认为最大
|
||||
infoUrl: '' // 在查看位置界面底部显示的超链接,可点击跳转
|
||||
});
|
||||
},
|
||||
fail: function (res) {
|
||||
console.error(res)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
wx.error(err => {
|
||||
console.error(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
54
src/utils/wexinPay.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import wx from 'weixin-js-sdk'
|
||||
export function pay(signInfo, payData,callback,errorCallBack?) {
|
||||
wx.config({
|
||||
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
|
||||
appId: signInfo.appId, // 必填,公众号的唯一标识
|
||||
timestamp: signInfo.timestamp, // 必填,生成签名的时间戳
|
||||
nonceStr: signInfo.nonceStr, // 必填,生成签名的随机串
|
||||
signature: signInfo.signature, // 必填,签名
|
||||
jsApiList: ['checkJsApi', 'chooseWXPay'] // 必填,需要使用的JS接口列表
|
||||
})
|
||||
|
||||
wx.ready(() => {
|
||||
wx.checkJsApi({
|
||||
jsApiList: ['chooseWXPay'],
|
||||
success: function () {
|
||||
wx.chooseWXPay({
|
||||
// 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
|
||||
// appId: payData.appId,
|
||||
timestamp: payData.timeStamp, // 必填,生成签名的时间戳
|
||||
nonceStr: payData.nonceStr, // 必填,生成签名的随机串
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.paySign, // 必填,签名
|
||||
success: function (res) { // 支付成功后的回调函数
|
||||
console.log('支付成功' + res)
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
fail: function (reg) {
|
||||
console.log(JSON.stringify(reg))
|
||||
if (errorCallBack) {
|
||||
errorCallBack()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: function (res) {
|
||||
console.log(JSON.stringify(res))
|
||||
if (errorCallBack) {
|
||||
errorCallBack()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
wx.error(err => {
|
||||
console.log(JSON.stringify(err))
|
||||
if (errorCallBack) {
|
||||
errorCallBack()
|
||||
}
|
||||
})
|
||||
}
|
||||
40
src/views/agreement/agreement.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<j-nav-bar navBarBackground="#fff" color="#000" :title="tittleMap[route.query.key]"/>
|
||||
<div :style="{padding: px2vw(20)}">
|
||||
<div v-html="agreement">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {getAgreement} from "@/api";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import {px2vw} from "@/utils";
|
||||
|
||||
const route = useRoute()
|
||||
const agreement = ref('')
|
||||
|
||||
const _getSystemConfigOne = (key) => {
|
||||
getAgreement().then(res => {
|
||||
agreement.value = res[key]
|
||||
})
|
||||
}
|
||||
const tittleMap = {
|
||||
loansAgreement: '借款协议',
|
||||
serviceAgreement: '平台服务协议',
|
||||
authAgreement: '委托授权协议',
|
||||
lawAgreement: '法律协议',
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tittleMap[route.query.key] ? document.title = tittleMap[route.query.key] : ''
|
||||
|
||||
_getSystemConfigOne(route.query.key)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
230
src/views/borrowInfo/index.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#f9bf3a" :placeholder="false"/>
|
||||
<div class="content">
|
||||
<j-gap height="120" background="#F9BF3A" opacity="0"/>
|
||||
<div class="action">
|
||||
<view class="tt">申请时间 {{ new Date(borrowInfo.createTime).format('yyyy-MM-dd hh:mm:ss') }}</view>
|
||||
|
||||
|
||||
<view class="footer-content">
|
||||
<view class="footer-content-1">
|
||||
<van-steps active-icon="success" active-color="#07c160" :active="active">
|
||||
<van-step v-for="(item, index) in stepBorrow.borrowStep" :key="index">{{ item.name }}</van-step>
|
||||
</van-steps>
|
||||
</view>
|
||||
<view class="footer-content-2">
|
||||
<view class="footer-content-2-tit gray_color">温馨提示</view>
|
||||
<view class="footer-content-2-content" :style="{color: stepBorrow.borrowNameStyle || '#e84a10'}">
|
||||
{{ stepBorrow.borrowRemark }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</div>
|
||||
|
||||
<div class="action">
|
||||
<view class="tt">
|
||||
<view>贷款详情</view>
|
||||
</view>
|
||||
<van-cell title="贷款编号" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="borrowInfo.tradeNo" />
|
||||
<van-cell title="借款金额" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="toRoundMark(borrowInfo.totalLoanMoney)" />
|
||||
<!-- <van-cell title="借款期限" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="borrowInfo.totalMonth + '个月'" />-->
|
||||
<van-cell title="贷款周期" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="borrowInfo.totalMonth + '个月'" />
|
||||
<van-cell title="提现银行" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="borrowInfo.bankType" />
|
||||
<van-cell title="每期还款" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="toRoundMark(borrowInfo.avgRepayment)" />
|
||||
<van-cell title="描述" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="borrowInfo.noteRemark" />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {getBorrowInfo, getUserInfo} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {useRoute} from "vue-router";
|
||||
import {toRoundMark} from "@/utils";
|
||||
|
||||
const route = useRoute()
|
||||
const active = ref(0);
|
||||
|
||||
const stepBorrow = reactive({
|
||||
"borrowNameStyle": "",
|
||||
"borrowRemark": "",
|
||||
"borrowStep": [
|
||||
{
|
||||
"name": "提交成功",
|
||||
"over": true
|
||||
},
|
||||
{
|
||||
"name": "银行卡异常",
|
||||
"over": true
|
||||
},
|
||||
{
|
||||
"name": "到账成功",
|
||||
"over": false
|
||||
}
|
||||
]
|
||||
})
|
||||
const borrowInfo = reactive({
|
||||
"auditFlag": true,
|
||||
"avgRepayment": 0,
|
||||
"backCardNum": "",
|
||||
"bankType": "",
|
||||
"borrowName": "",
|
||||
"borrowNameStyle": "",
|
||||
"borrowRemark": "",
|
||||
"cardBackPicture": "",
|
||||
"cardFrontPicture": "",
|
||||
"cardNum": "",
|
||||
"companyAddress": "",
|
||||
"companyAddressInfo": "",
|
||||
"companyName": "",
|
||||
"companyPhone": "",
|
||||
"companyTitle": "",
|
||||
"companyYear": "",
|
||||
"createTime": "",
|
||||
"customerAddress": "",
|
||||
"customerAddressInfo": "",
|
||||
"customerId": 0,
|
||||
"dueDate": 0,
|
||||
"firstBackCardNum": "",
|
||||
"firstBankType": "",
|
||||
"firstRepayment": 0,
|
||||
"id": 0,
|
||||
"infoJson": "",
|
||||
"kinsfolkName": "",
|
||||
"kinsfolkPhone": "",
|
||||
"kinsfolkRef": "",
|
||||
"loanMonthRate": 0,
|
||||
"loanProcessResp": {
|
||||
"borrowNameStyle": "",
|
||||
"borrowRemark": "",
|
||||
"borrowStep": [
|
||||
{
|
||||
"name": "",
|
||||
"over": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"loanYearRate": 0,
|
||||
"noteRemark": "",
|
||||
"realName": "",
|
||||
"remitFlag": 0,
|
||||
"repayRemark": "",
|
||||
"totalInterest": 0,
|
||||
"totalLoanMoney": 0,
|
||||
"totalMonth": 0,
|
||||
"totalRepayment": 0,
|
||||
"tradeNo": "",
|
||||
"updateBackNum": 0,
|
||||
"updateTime": ""
|
||||
})
|
||||
const _getBorrowInfo = () => {
|
||||
getBorrowInfo({tradeNo: route.query.tradeNo}).then(res => {
|
||||
resetData(borrowInfo, res)
|
||||
resetData(stepBorrow, borrowInfo.loanProcessResp)
|
||||
stepBorrow.borrowStep.forEach((sb, index) => {
|
||||
if (sb.over) {
|
||||
active.value = index
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
_getBorrowInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
background-image: linear-gradient(to bottom, #f9bf3a, #ffffff) !important;
|
||||
padding-bottom: 30px;
|
||||
height: 100vh;
|
||||
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.id-card-box {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tt {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
background: #f6f9fd;
|
||||
color: #000;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
padding: 20px 20px;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.font-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
.footer-content {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-1 {
|
||||
padding: 20px 80px;
|
||||
//background: #f9fafb;
|
||||
}
|
||||
&-2 {
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
&-tit {
|
||||
display: block;
|
||||
width: 220px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
&-content {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
src/views/forget/index.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<j-nav-bar :placeholder="false" color="#FFF" nav-bar-background="#F9BF3A00"/>
|
||||
<div class="content">
|
||||
|
||||
<j-gap height="50" background="#F9BF3A" opacity="0"/>
|
||||
<j-gap height="120" opacity="1"/>
|
||||
<van-field
|
||||
v-model="loginData.phoneNumber"
|
||||
v-if="flag === '1'"
|
||||
class="login-btn"
|
||||
label="手机号码"
|
||||
placeholder="请输入手机号码"
|
||||
label-align="top"
|
||||
style="background: #12332100"
|
||||
type="tel"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="loginData.code"
|
||||
class="login-btn"
|
||||
v-if="flag === '1'"
|
||||
label="验证码"
|
||||
placeholder="请输入验证码"
|
||||
label-align="top"
|
||||
style="background: #12332100"
|
||||
type="number"
|
||||
>
|
||||
<template #button>
|
||||
<div class="password-btn">
|
||||
<van-count-down v-show="countDownFlag" ref="countDown" :auto-start="false" :time="time" @finish="onFinish">
|
||||
<template #default="timeData">
|
||||
<span class="block">{{ timeData.seconds }}秒</span>
|
||||
</template>
|
||||
</van-count-down>
|
||||
<div v-show="!countDownFlag" style="color: #bc7c1c" @click="start">发送验证码</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field
|
||||
v-model="loginData.password"
|
||||
class="login-btn"
|
||||
v-if="flag === '2'"
|
||||
label="登录密码"
|
||||
label-align="top"
|
||||
placeholder="请设置6-16位密码"
|
||||
style="background: #12332100"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="loginData.confirmPassword"
|
||||
class="login-btn"
|
||||
v-if="flag === '2'"
|
||||
label="确认密码"
|
||||
label-align="top"
|
||||
placeholder="请再次输入密码"
|
||||
style="background: #12332100"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<van-button
|
||||
:disabled="!loginData.phoneNumber || !loginData.code"
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; margin: 20px 0"
|
||||
v-if="flag === '1'"
|
||||
@click.stop="next"
|
||||
>
|
||||
下一步
|
||||
</van-button>
|
||||
|
||||
<van-button
|
||||
:disabled="!loginData.password || !loginData.confirmPassword"
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; margin: 20px 0"
|
||||
v-if="flag === '2'"
|
||||
@click.stop="updatePwdBtn"
|
||||
>
|
||||
确认修改
|
||||
</van-button>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {reactive, ref} from "vue";
|
||||
import {sendSmsForget, updatePwd} from "@/api/login";
|
||||
import {showToast} from "vant";
|
||||
|
||||
const loginData = reactive({
|
||||
phone: null,
|
||||
phoneNumber: '',
|
||||
code: null,
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
checkCode: '',
|
||||
})
|
||||
|
||||
const flag = ref('1')
|
||||
|
||||
|
||||
const time = ref(60 * 1000);
|
||||
const countDown = ref();
|
||||
const countDownFlag = ref(false);
|
||||
const start = () => {
|
||||
if (loginData.phoneNumber) {
|
||||
countDown.value.start();
|
||||
countDownFlag.value = true
|
||||
sendSmsForget({
|
||||
phoneNumber: loginData.phoneNumber
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
loginData.code = res
|
||||
loginData.checkCode = res
|
||||
onFinish()
|
||||
})
|
||||
} else {
|
||||
showToast('请输入手机号')
|
||||
}
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
flag.value = '2'
|
||||
}
|
||||
|
||||
const updatePwdBtn = () => {
|
||||
updatePwd({
|
||||
checkCode: loginData.checkCode,
|
||||
confirmPassword: loginData.confirmPassword,
|
||||
password: loginData.password
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const onFinish = () => {
|
||||
countDownFlag.value = false
|
||||
countDown.value.reset();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
padding: 0 10px;
|
||||
background-image: linear-gradient(to bottom, #F9BF3A, #ffffff, #ffffff, #ffffff, #ffffff);
|
||||
height: 100vh;
|
||||
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
444
src/views/index/home/index.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<div>
|
||||
<j-nav-bar/>
|
||||
<!-- banner -->
|
||||
<div :style="{paddingBottom: px2vw(20)}" style="overflow: hidden;">
|
||||
<van-swipe :autoplay="3000" lazy-render>
|
||||
<van-swipe-item v-for="banner in bannerList" :key="banner.bannerUrl">
|
||||
<van-image :src="banner.bannerUrl"></van-image>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</div>
|
||||
<div :style="{padding: '0 ' + px2vw(30) + ' ' + px2vw(20)}">
|
||||
|
||||
<div class="product">
|
||||
<div class="product-title">产品详情</div>
|
||||
|
||||
<div class="product-content">
|
||||
<div class="product-content-label">
|
||||
<div>最低日息</div>
|
||||
<div><span>{{ calLoan.loanRate }}%</span></div>
|
||||
</div>
|
||||
<div class="product-content-label">
|
||||
<div>借款额度</div>
|
||||
<div><span>¥{{ loans.loansMinAccount }}-{{ loans.loansMaxAccount }}</span></div>
|
||||
</div>
|
||||
<div class="product-content-label">
|
||||
<div>分期期限</div>
|
||||
<div><span>可选{{ loans.loansMonth.replaceAll(',', '/') }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="apply">
|
||||
<div class="apply-title yellow_color">申请金额(元)</div>
|
||||
|
||||
<div class="apply-money">{{ strip }}</div>
|
||||
|
||||
<div class="apply-strip-box">
|
||||
<div class="reduce" @click="moneyReduce(100)"></div>
|
||||
|
||||
<div class="slider">
|
||||
<van-slider bar-height="16" active-color="linear-gradient(to bottom, #f3654d, #f9ad7d)" :max="loans.loansMaxAccount" :min="loans.loansMinAccount" :step="100" v-model="strip">
|
||||
<template #button>
|
||||
<div class="custom-button"></div>
|
||||
</template>
|
||||
</van-slider>
|
||||
</div>
|
||||
|
||||
<div class="add" @click="moneyAdd(100)"></div>
|
||||
</div>
|
||||
|
||||
<div class="apply-jkqx">
|
||||
<div class="apply-jkqx-title">借款期限</div>
|
||||
<div class="apply-jkqx-item-box">
|
||||
<div v-for="lm in loans.loansMonthList" :key="lm" class="apply-jkqx-item" :class="{'checked': lm === lmChecked}" @click="lmChecked = lm">
|
||||
{{ lm }}个月
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<j-gap height="2" background="#fff"/>
|
||||
|
||||
<div class="apply-mqhk">
|
||||
<div>每期还款</div>
|
||||
<div>¥{{ calLoan.avgRepayment }}</div>
|
||||
<div>(日利率{{ calLoan.loanRate }}% 总利息¥{{ calLoan.totalInterest }})</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notice">
|
||||
<van-icon :name="getAssetsImages('home/xlb.png')" size="30"/>
|
||||
<div>{{ loansUser.time }}</div>
|
||||
<div style="color: #ec6401">{{ loansUser.phone }}</div>
|
||||
<div>成功借款</div>
|
||||
<div style="color: #BC7C1C; font-weight: 600;">¥{{ loansUser.amount }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="xieyi">
|
||||
<van-checkbox v-model="checked" :icon-size="px2vw(28)" style="align-items: baseline" checked-color="linear-gradient(to right, #F9D88D, #D2A64C)">
|
||||
我已阅读并同意
|
||||
<span class="yellow_color1" @click.stop="go('authAgreement')">《委托授权协议》</span>
|
||||
<span class="yellow_color1" @click.stop="go('serviceAgreement')">《平台服务协议》</span>
|
||||
<span class="yellow_color1" @click.stop="go('loansAgreement')">《借款协议》</span>
|
||||
</van-checkbox>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="padding: 0 20px 0">
|
||||
<van-button
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; "
|
||||
@click.stop="immediateBorrowing"
|
||||
>
|
||||
立即借款
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<j-gap :height="170" opacity/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {debounce, getAssetsImages, px2vw} from "@/utils";
|
||||
import {computed, onMounted, reactive, ref,onUnmounted} from "vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import {getCalLoan, getHomeInfo, getLoansInfo, getLoansUser, getUserInfo} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {watch} from "vue-demi";
|
||||
import {showToast} from "vant";
|
||||
import JGap from "@/components/JGap/JGap.vue";
|
||||
|
||||
const user = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
const bannerList = [
|
||||
{
|
||||
bannerUrl: getAssetsImages('home/banner_home.png')
|
||||
}
|
||||
]
|
||||
|
||||
const checked = ref(false);
|
||||
const strip = ref(3000)
|
||||
const lmChecked = ref('')
|
||||
|
||||
|
||||
const moneyReduce = (step: number) => {
|
||||
strip.value-=step
|
||||
}
|
||||
|
||||
const moneyAdd = (step: number) => {
|
||||
strip.value+=step
|
||||
}
|
||||
|
||||
const go = (key) => {
|
||||
// agreement
|
||||
router.push({
|
||||
path: '/agreement',
|
||||
query: {
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const immediateBorrowing = () => {
|
||||
if (checked.value) {
|
||||
if (userInfo.infoFlag && userInfo.bankFlag && userInfo.cardFlag) {
|
||||
router.push({
|
||||
path: '/loansInfo',
|
||||
query: {
|
||||
"totalLoanMoney": strip.value,
|
||||
"totalMonth": lmChecked.value
|
||||
}
|
||||
})
|
||||
} else {
|
||||
showToast('请先填写个人资料')
|
||||
router.push({
|
||||
path: '/userInfo'
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
showToast('请阅读并同意相关协议')
|
||||
}
|
||||
|
||||
const loans = reactive({
|
||||
dueDate: 0,
|
||||
id: 0,
|
||||
loansInitAccount: 0,
|
||||
loansInitMonth: "",
|
||||
loansMaxAccount: 0,
|
||||
loansMinAccount: 0,
|
||||
loansMonth: "",
|
||||
loansMonthList: [],
|
||||
serviceRate: "",
|
||||
serviceRateList: [],
|
||||
})
|
||||
const _getLoansInfo = () => {
|
||||
getLoansInfo().then(res => {
|
||||
resetData(loans, res)
|
||||
loans.loansMonthList.newPush(loans.loansMonth.split(','))
|
||||
loans.serviceRateList.newPush(loans.serviceRate.split(','))
|
||||
lmChecked.value = loans.loansInitMonth
|
||||
strip.value = loans.loansInitAccount
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const homeInfo = reactive({
|
||||
"id": "1",
|
||||
"homeTitle": "123123",
|
||||
"bannerOne": "http://localhost:8082/profile/upload/2023/11/29/5ea3a159-81e9-4630-a72d-3ff2ce68b306.jpg",
|
||||
"commonSeal": null
|
||||
})
|
||||
const _getHomeInfo = () => {
|
||||
getHomeInfo().then(res => {
|
||||
resetData(homeInfo, res)
|
||||
bannerList.newPush({
|
||||
bannerUrl: homeInfo.bannerOne
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const calLoan = reactive({
|
||||
"avgRepayment": 0,
|
||||
"firstRepayment": 0,
|
||||
"loanRate": 0,
|
||||
"totalInterest": 0,
|
||||
"totalLoanMoney": 0,
|
||||
"totalMonth": 0,
|
||||
"totalRepayment": 0
|
||||
})
|
||||
|
||||
const params = computed(() => {
|
||||
return {
|
||||
"totalLoanMoney": strip.value,
|
||||
"totalMonth": lmChecked.value
|
||||
}
|
||||
})
|
||||
|
||||
watch(params, () => {
|
||||
if (params.value.totalLoanMoney && params.value.totalMonth) {
|
||||
_getCalLoan()
|
||||
}
|
||||
})
|
||||
|
||||
const _getCalLoan = debounce(() => {
|
||||
getCalLoan(params.value).then(res => {
|
||||
resetData(calLoan, res)
|
||||
})
|
||||
}, 200)
|
||||
|
||||
|
||||
const loansUser = reactive({
|
||||
amount : "48000",
|
||||
phone : "153****0552",
|
||||
time : "2023/11/27"
|
||||
})
|
||||
|
||||
const timer = ref(0)
|
||||
const _getLoansUser = () => {
|
||||
getLoansUser().then(res => {
|
||||
resetData(loansUser, res)
|
||||
})
|
||||
timer.value = setInterval(() => {
|
||||
getLoansUser().then(res => {
|
||||
resetData(loansUser, res)
|
||||
})
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
const userInfo = reactive({
|
||||
cardFlag: false,
|
||||
infoFlag: false,
|
||||
bankFlag: false
|
||||
})
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
_getLoansInfo()
|
||||
_getHomeInfo()
|
||||
_getLoansUser()
|
||||
_getUserInfo()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer.value);
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.product {
|
||||
&-title {
|
||||
font-weight: bold;
|
||||
font-size: 40px;
|
||||
padding: 0 0 20px 0;
|
||||
}
|
||||
|
||||
&-content {
|
||||
background: url("../../../assets/images/home/detail_bg.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
color: #a2a2a2;
|
||||
padding: 30px;
|
||||
|
||||
&-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 7px 0;
|
||||
|
||||
span {
|
||||
color: #F9BF3A
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apply {
|
||||
background: url("../../../assets/images/home/apply_bg.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding: 30px;
|
||||
margin: 30px 0 15px;
|
||||
&-title {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 34px;
|
||||
&:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
height: 1px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 12px;
|
||||
background-image: -webkit-gradient(linear, left top, right top, from(#fbde60), to(#e46f00));
|
||||
background-image: linear-gradient(to right, #fbde60, #e46f00);
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
height: 1px;
|
||||
margin-left: 20px;
|
||||
margin-bottom: 12px;
|
||||
background-image: linear-gradient(to right, #e46f00, #fbde60);
|
||||
}
|
||||
}
|
||||
|
||||
&-money {
|
||||
font-size: 80px;
|
||||
text-align: center;
|
||||
color: #5c2d00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-strip-box{
|
||||
margin-bottom: 60px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.reduce {
|
||||
background: url("../../../assets/images/home/strip-reduce.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.add {
|
||||
background: url("../../../assets/images/home/strip-add.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
background: url("../../../assets/images/home/strip-block.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
height: 58px;
|
||||
width: 43px;
|
||||
}
|
||||
}
|
||||
|
||||
&-jkqx {
|
||||
margin-bottom: 40px;
|
||||
&-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141414;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
&-item-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.apply-jkqx-item{
|
||||
border-radius: 40px;
|
||||
text-align: center;
|
||||
background: #fffbba;
|
||||
padding: 4px 16px;
|
||||
color: #BC7C1C;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.checked {
|
||||
background-image: linear-gradient(to bottom, #f3654d, #f9ad7d);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
&-mqhk {
|
||||
margin-top: 40px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: end;
|
||||
font-size: 28px;
|
||||
|
||||
div:nth-child(1) {
|
||||
font-size: 25px;
|
||||
font-weight: 600;
|
||||
color: #141414;
|
||||
}
|
||||
div:nth-child(2) {
|
||||
color: #b25700;
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
background-image: linear-gradient(to right, #fae1cf, #fcfaf8);
|
||||
padding: 8px 20px;
|
||||
margin: 0 30px;
|
||||
border-radius: 40px;
|
||||
font-size: 24px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.xieyi {
|
||||
padding: 30px 35px;
|
||||
font-size: 24px;
|
||||
}
|
||||
</style>
|
||||
68
src/views/index/index.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<!-- <div style="background: linear-gradient(167.96deg, #E6FAE1 0%, #F2E7B7 98.44%) no-repeat;min-height: 100%" >-->
|
||||
<div>
|
||||
|
||||
<router-view />
|
||||
|
||||
<van-tabbar route v-model="active" active-color="#F9BF3A">
|
||||
<van-tabbar-item v-for="tabBar in tabBarList" :key="tabBar.name" :replace="tabBar.replace" :to="tabBar.to">
|
||||
<span>{{ tabBar.text }}</span>
|
||||
<template #icon="props">
|
||||
<van-icon :color="props.active ? '' : 'rgb(229, 229, 229)'" :name="props.active ? tabBar.active : tabBar.inactive" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref} from "vue";
|
||||
import JGap from "@/components/JGap/JGap.vue";
|
||||
import {getAssetsImages} from "@/utils";
|
||||
import {useBackgroundHook} from "@/hooks/useBackgroundHook";
|
||||
|
||||
const tabBarList = [
|
||||
{
|
||||
to: '/home',
|
||||
name: 'home',
|
||||
text: '首页',
|
||||
replace: true,
|
||||
active: getAssetsImages('tabBar/home-active.png'),
|
||||
inactive: getAssetsImages('tabBar/home-inactive.png')
|
||||
},
|
||||
{
|
||||
to: '/serveList',
|
||||
name: 'serveList',
|
||||
text: '钱包',
|
||||
replace: true,
|
||||
active: getAssetsImages('tabBar/artificer-active.png'),
|
||||
inactive: getAssetsImages('tabBar/artificer-inactive.png')
|
||||
},
|
||||
{
|
||||
to: '/message',
|
||||
name: 'message',
|
||||
text: '客服',
|
||||
replace: true,
|
||||
active: 'chat',
|
||||
inactive: 'chat'
|
||||
},
|
||||
{
|
||||
to: '/my',
|
||||
name: 'my',
|
||||
text: '我的',
|
||||
replace: true,
|
||||
active: getAssetsImages('tabBar/my-active.png'),
|
||||
inactive: getAssetsImages('tabBar/my-inactive.png')
|
||||
}
|
||||
]
|
||||
|
||||
const active = ref(0)
|
||||
|
||||
// const {setBodyBackground} = useBackgroundHook()
|
||||
// setBodyBackground()
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
46
src/views/index/message/index.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<j-nav-bar/>
|
||||
<div ref="root" class="frame" style="height: calc(100vh - 50px - 46px)">
|
||||
<iframe :src="iframeSrc" class="frame-iframe" ref="frameRef"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from "vue";
|
||||
import {getSetting} from "@/api";
|
||||
import {onMounted} from "vue";
|
||||
|
||||
const iframeSrc = ref('')
|
||||
// const iframeSrc = ref('https://chatlink.mstatik.com/widget/standalone.html?eid=329d34187acc7ebda66a12a0671e3d70')
|
||||
|
||||
|
||||
const _getSetting = () => {
|
||||
getSetting().then(res => {
|
||||
iframeSrc.value = res.chatUrl
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
_getSetting()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.frame {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
&-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
200
src/views/index/my/index.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<j-nav-bar/>
|
||||
<div class="header">
|
||||
<div class="header-bj">
|
||||
<div class="header-head" :style="{'--bg-image': `url(${headerImage}) no-repeat`}"></div>
|
||||
<div class="header-text">{{ customerInfo.nickName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
||||
<div class="action">
|
||||
<van-cell title="我的资料" is-link to="userInfo" >
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">
|
||||
<van-icon :name="getAssetsImages('my/my_info.png')" />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell title="我的借款" is-link to="myLoan" >
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">
|
||||
<van-icon :name="getAssetsImages('my/my_info1.png')" />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell title="我的还款" is-link to="myRepayment" >
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">
|
||||
<van-icon :name="getAssetsImages('my/my_info2.png')" />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<!-- <van-cell title="获取开户权限" is-link to="" >-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">-->
|
||||
<!-- <van-icon name="/src/assets/images/my/my_info3.png" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </van-cell>-->
|
||||
<van-cell title="法律责任" is-link @click="go('/agreement', 'lawAgreement')" >
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">
|
||||
<van-icon :name="getAssetsImages('my/my_info4.png')" />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<!-- <van-cell title="签名" is-link @click="go('/signature', null)" >-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">-->
|
||||
<!-- <van-icon name="invitation" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </van-cell>-->
|
||||
<!-- <van-cell title="我的合同" is-link @click="go('/contract', null)" >-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">-->
|
||||
<!-- <van-icon name="completed" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </van-cell>-->
|
||||
</div>
|
||||
<div class="action">
|
||||
<van-cell title="修改密码" is-link to="uploadPassword" >
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">
|
||||
<van-icon :name="getAssetsImages('my/my_info5.png')" />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell title="退出登录" is-link @click="logout" >
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 5px">
|
||||
<van-icon :name="getAssetsImages('my/my_info66.png')" />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {showConfirmDialog} from "vant";
|
||||
import {getAssetsImages} from "@/utils";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import {useRouter} from "vue-router";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {getCustomerInfo} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
const headerImage = ref('https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg')
|
||||
|
||||
const logout = () => {
|
||||
|
||||
showConfirmDialog({
|
||||
title: '提示',
|
||||
message: '您确定要退出登录吗',
|
||||
width: '500px'
|
||||
})
|
||||
.then(() => {
|
||||
userStore.logout().then(res => {
|
||||
router.push({
|
||||
path: '/login'
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
|
||||
const go = (url, key) => {
|
||||
// agreement
|
||||
router.push({
|
||||
path: url,
|
||||
query: {
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const customerInfo = reactive({
|
||||
"account": 0,
|
||||
"borrowAccount": 0,
|
||||
"id": 0,
|
||||
"lastLoginIp": "",
|
||||
"lastLoginTime": "",
|
||||
"loansFlag": 0,
|
||||
"nickName": "",
|
||||
"phoneNumber": "",
|
||||
"realNameAuth": 0,
|
||||
"repaymentAccount": 0,
|
||||
"status": 0,
|
||||
"updateTime": "",
|
||||
"withdrawFlag": 0
|
||||
})
|
||||
const _getCustomerInfo = () => {
|
||||
getCustomerInfo().then(res => {
|
||||
resetData(customerInfo, res)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
_getCustomerInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.header {
|
||||
background: #151515;
|
||||
height: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: end;
|
||||
overflow: hidden;
|
||||
|
||||
&-bj {
|
||||
background: url("../../../assets/images/my/user_card.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
height: 330px;
|
||||
width: 666px;
|
||||
transform: translateY(80px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-flow: column;
|
||||
.header-head {
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
border: 10px solid #f9c947;
|
||||
border-radius: 100px;
|
||||
background: #f9c947;
|
||||
transform: translateY(-90px);
|
||||
background: var(--bg-image);
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
.header-text {
|
||||
font-size: 38px;
|
||||
font-weight: bold;
|
||||
color: #111a34;
|
||||
transform: translateY(-60px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
290
src/views/index/serveList/index.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#F9BF3A" />
|
||||
<div class="content">
|
||||
<view class="bg-1">
|
||||
<view class="bg-1-1">
|
||||
<div class="header-head" :style="{'--bg-image': `url(${headerImage}) no-repeat`}"></div>
|
||||
<view class="bg-1-1-text">我的贷款:{{toRoundMark(customerInfo.borrowAccount)}}</view>
|
||||
</view>
|
||||
|
||||
<view class="accountBalance" style="border-bottom: 1px #fff dashed;">
|
||||
<view class="yellow_color">账户余额(元)</view>
|
||||
<view class="money">{{ toRoundMark(customerInfo.account) }}</view>
|
||||
</view>
|
||||
|
||||
<view class="accountBalance">
|
||||
<view class="yellow_color">待还款金额(元)</view>
|
||||
<view class="money">{{ toRoundMark(customerInfo.repaymentAccount) }}</view>
|
||||
</view>
|
||||
|
||||
<view class="btn">
|
||||
|
||||
<van-button
|
||||
color="#fff"
|
||||
round
|
||||
style="color: #e6a600;width: 80%"
|
||||
@click.stop="withdrawalBtn"
|
||||
>
|
||||
立即提现
|
||||
</van-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<view class="footer-t gray_color ">
|
||||
<van-icon :name="getAssetsImages('common/yhd.png')" size="18"/>
|
||||
账户资金安全由银行保障
|
||||
</view>
|
||||
|
||||
<view class="footer-content">
|
||||
<view class="footer-content-1">
|
||||
<van-steps active-icon="success" active-color="#07c160" :active="active">
|
||||
<van-step v-for="(item, index) in stepBorrow.borrowStep" :key="index">{{ item.name }}</van-step>
|
||||
</van-steps>
|
||||
</view>
|
||||
<view class="footer-content-2">
|
||||
<view class="footer-content-2-tit gray_color">温馨提示</view>
|
||||
<view class="footer-content-2-content" :style="{color: stepBorrow.borrowNameStyle || '#e84a10'}">
|
||||
{{ stepBorrow.borrowRemark }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</div>
|
||||
<van-dialog :width="px2vw(650)" v-model:show="withdrawalShow" title="提现" show-cancel-button @confirm="saveUserInfoBtn">
|
||||
<van-field v-model="withdrawAmount" type="number" label="提现金额" placeholder="请输入你的提现金额"/>
|
||||
</van-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {showConfirmDialog, showToast} from "vant";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getBorrowWithdraw, getCustomerInfo, getStepBorrow} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {getAssetsImages, px2vw, toRoundMark} from "../../../utils";
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const withdrawalShow = ref(false);
|
||||
const withdrawAmount = ref(0);
|
||||
const active = ref(0);
|
||||
const headerImage = ref('https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg')
|
||||
|
||||
|
||||
const go = (key) => {
|
||||
// agreement
|
||||
router.push({
|
||||
path: '/agreement',
|
||||
query: {
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const customerInfo = reactive({
|
||||
"account": 0,
|
||||
"borrowAccount": 0,
|
||||
"id": 0,
|
||||
"lastLoginIp": "",
|
||||
"lastLoginTime": "",
|
||||
"loansFlag": 0,
|
||||
"nickName": "",
|
||||
"phoneNumber": "",
|
||||
"realNameAuth": 0,
|
||||
"repaymentAccount": 0,
|
||||
"status": 0,
|
||||
"updateTime": "",
|
||||
"withdrawFlag": 0
|
||||
})
|
||||
const _getCustomerInfo = () => {
|
||||
getCustomerInfo().then(res => {
|
||||
resetData(customerInfo, res)
|
||||
})
|
||||
}
|
||||
|
||||
const stepBorrow = reactive({
|
||||
"borrowNameStyle": "",
|
||||
"borrowRemark": "",
|
||||
"borrowStep": [
|
||||
{
|
||||
"name": "提交成功",
|
||||
"over": true
|
||||
},
|
||||
{
|
||||
"name": "银行卡异常",
|
||||
"over": true
|
||||
},
|
||||
{
|
||||
"name": "到账成功",
|
||||
"over": false
|
||||
}
|
||||
]
|
||||
})
|
||||
const _getStepBorrow = () => {
|
||||
getStepBorrow().then(res => {
|
||||
resetData(stepBorrow, res)
|
||||
stepBorrow.borrowStep.forEach((sb, index) => {
|
||||
if (sb.over) {
|
||||
active.value = index
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const withdrawalBtn = () => {
|
||||
|
||||
withdrawalShow.value = true
|
||||
withdrawAmount.value = customerInfo.account
|
||||
// router.push({
|
||||
// path: '/loansInfo1',
|
||||
// query: {
|
||||
// account: customerInfo.account
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
const saveUserInfoBtn = () => {
|
||||
|
||||
// showConfirmDialog({
|
||||
// title: '提示',
|
||||
// message: '您确定要提现吗',
|
||||
// width: '500px'
|
||||
// })
|
||||
// .then(() => {
|
||||
getBorrowWithdraw({withdrawAmount: withdrawAmount.value}).then(res => {
|
||||
showToast('提现成功')
|
||||
router.push({
|
||||
path: '/serveList'
|
||||
})
|
||||
})
|
||||
// })
|
||||
// .catch(() => {
|
||||
// // on cancel
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
_getCustomerInfo()
|
||||
_getStepBorrow()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
padding: 0 20px;
|
||||
background-image: linear-gradient(to bottom, #F9BF3A, #ffffff);
|
||||
height: 100vh;
|
||||
|
||||
.bg-1 {
|
||||
display: block;
|
||||
background: url("../../../assets/images/common/bg_wallet.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding-top: 100px;
|
||||
width: 692px;
|
||||
height: 680px;
|
||||
margin: 0 auto;
|
||||
|
||||
&-1 {
|
||||
display: flex;
|
||||
&-text {
|
||||
color: #111a34;
|
||||
font-weight: 600;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.header-head {
|
||||
height: 180px;
|
||||
width: 180px;
|
||||
border: 10px solid #f9c947;
|
||||
border-radius: 100px;
|
||||
background: #f9c947;
|
||||
transform: translateY(-90px);
|
||||
background: var(--bg-image);
|
||||
background-size: 100% 100%;
|
||||
margin-left: 45px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
.accountBalance {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: start;
|
||||
padding-left: 80px;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
margin: 0 20px;
|
||||
transform: translateY(-70px);
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
|
||||
|
||||
.yellow_color {
|
||||
color: #b25700;
|
||||
}
|
||||
.money {
|
||||
padding-top: 10px;
|
||||
font-size: 56px;
|
||||
color: #5c2d00;
|
||||
}
|
||||
|
||||
}
|
||||
.btn {
|
||||
//padding-top: 100px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translateY(-70px);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 30px;
|
||||
flex-flow: column;
|
||||
&-t {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
&-content {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-1 {
|
||||
padding: 20px 80px;
|
||||
//background: #f9fafb;
|
||||
}
|
||||
&-2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
&-tit {
|
||||
display: block;
|
||||
width: 220px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
&-content {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
211
src/views/loans/info/index.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#f9bf3a" :placeholder="false"/>
|
||||
<div class="content">
|
||||
<j-gap height="120" background="#F9BF3A" opacity="0"/>
|
||||
<div class="action">
|
||||
<view class="tt">
|
||||
确认借款信息
|
||||
</view>
|
||||
<van-cell title="借款金额" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="toRoundMark(loansInfo.totalLoanMoney)" />
|
||||
<van-cell title="借款期限" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="loansInfo.totalMonth + '个月'" />
|
||||
<van-cell title="到账银行" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="userInfo.bankType" />
|
||||
<van-cell title="收款账号" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="backCardNumDesensitization(userInfo.backCardNum)" />
|
||||
</div>
|
||||
|
||||
<div class="action">
|
||||
<view class="tt">
|
||||
借款用途
|
||||
</view>
|
||||
<van-field
|
||||
v-model="loansInfo.noteRemark"
|
||||
rows="5"
|
||||
autosize
|
||||
type="textarea"
|
||||
placeholder="请输入你的借款用途"
|
||||
/>
|
||||
</div>
|
||||
<div class="xieyi">
|
||||
<van-checkbox v-model="checked" :icon-size="px2vw(28)" style="align-items: baseline" checked-color="linear-gradient(to right, #F9D88D, #D2A64C)">
|
||||
我已阅读并同意
|
||||
<span class="yellow_color1" @click.stop="go('loansAgreement')">《借款协议》</span>
|
||||
</van-checkbox>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="padding: 0 20px 60px">
|
||||
<van-button
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; "
|
||||
@click.stop="saveUserInfoBtn"
|
||||
:disabled="!checked"
|
||||
>
|
||||
提交申请
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {px2vw, toRoundMark} from "@/utils";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import {getUserInfo, startBorrow} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {showConfirmDialog, showToast} from "vant";
|
||||
|
||||
const useUser = useUserStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const loansInfo = reactive({
|
||||
"customerId": useUser.getUserInfo.id,
|
||||
"noteRemark": "",
|
||||
"totalLoanMoney": 0,
|
||||
"totalMonth": 0
|
||||
})
|
||||
|
||||
const checked = ref(false);
|
||||
const go = (key) => {
|
||||
// agreement
|
||||
router.push({
|
||||
path: '/agreement',
|
||||
query: {
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const saveUserInfoBtn = () => {
|
||||
|
||||
showConfirmDialog({
|
||||
title: '提示',
|
||||
message: '您确定要申请贷款吗',
|
||||
width: '500px'
|
||||
})
|
||||
.then(() => {
|
||||
startBorrow(loansInfo).then(res => {
|
||||
showToast('申请成功')
|
||||
router.push({
|
||||
path: '/serveList'
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
const userInfo = reactive({
|
||||
backCardNum: '',
|
||||
bankType: '',
|
||||
cardBackPicture: '',
|
||||
cardFrontPicture: '',
|
||||
cardNum: '',
|
||||
companyAddress: '',
|
||||
companyAddressInfo: '',
|
||||
companyName: '',
|
||||
companyPhone: '',
|
||||
companyTitle: '',
|
||||
companyYear: '',
|
||||
customerAddress: '',
|
||||
customerAddressInfo: '',
|
||||
customerId: 0,
|
||||
id: 0,
|
||||
kinsfolkName: '',
|
||||
kinsfolkPhone: '',
|
||||
kinsfolkRef: '',
|
||||
kinsfolkRefText: '',
|
||||
realName: ''
|
||||
})
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
})
|
||||
}
|
||||
|
||||
// 收款账号脱敏
|
||||
const backCardNumDesensitization = (backCardNum: string) => {
|
||||
const blen = backCardNum.length
|
||||
if (blen < 8) {
|
||||
return backCardNum
|
||||
}
|
||||
let str = ''
|
||||
for (let i = 0; i < blen - 7; i++) {
|
||||
str+='*'
|
||||
}
|
||||
|
||||
return backCardNum.slice(0, 4) + str + backCardNum.slice(blen-3, blen)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loansInfo.totalLoanMoney = route.query.totalLoanMoney
|
||||
loansInfo.totalMonth = route.query.totalMonth
|
||||
_getUserInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
background-image: linear-gradient(to bottom, #f9bf3a, #ffffff) !important;
|
||||
padding-bottom: 30px;
|
||||
height: 100vh;
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.id-card-box {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tt {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background: #f6f9fd;
|
||||
color: #738aa4;
|
||||
padding: 20px 0;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
.xieyi {
|
||||
padding: 30px 35px;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.font-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
</style>
|
||||
204
src/views/loans/info1/index.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#f9bf3a" :placeholder="false"/>
|
||||
<div class="content">
|
||||
<j-gap height="120" background="#F9BF3A" opacity="0"/>
|
||||
<div class="action">
|
||||
<view class="tt">
|
||||
确认提现信息
|
||||
</view>
|
||||
<van-cell title="到账银行" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="userInfo.bankType" />
|
||||
<van-cell title="收款账号" title-style="color: #8997ae;" style="--van-cell-value-color: #000" :value="backCardNumDesensitization(userInfo.backCardNum)" />
|
||||
</div>
|
||||
|
||||
<div class="action">
|
||||
<view class="tt">
|
||||
提现金额
|
||||
</view>
|
||||
<van-field
|
||||
v-model="withdrawAmount"
|
||||
label="金额"
|
||||
type="number"
|
||||
placeholder="请输入你的提现金额"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div class="xieyi">-->
|
||||
<!-- <van-checkbox v-model="checked" :icon-size="px2vw(28)" style="align-items: baseline" checked-color="linear-gradient(to right, #F9D88D, #D2A64C)">-->
|
||||
<!-- 我已阅读并同意-->
|
||||
<!-- <span class="yellow_color1" @click.stop="go('loansAgreement')">《借款协议》</span>-->
|
||||
<!-- </van-checkbox>-->
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
<div style="padding: 0 20px 60px">
|
||||
<van-button
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; "
|
||||
@click.stop="saveUserInfoBtn"
|
||||
:disabled="!withdrawAmount || withdrawAmount == 0"
|
||||
>
|
||||
提现
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import {getBorrowWithdraw, getUserInfo} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {showConfirmDialog, showToast} from "vant";
|
||||
|
||||
const useUser = useUserStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
const withdrawAmount = ref()
|
||||
|
||||
const go = (key) => {
|
||||
// agreement
|
||||
router.push({
|
||||
path: '/agreement',
|
||||
query: {
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const saveUserInfoBtn = () => {
|
||||
|
||||
showConfirmDialog({
|
||||
title: '提示',
|
||||
message: '您确定要提现吗',
|
||||
width: '500px'
|
||||
})
|
||||
.then(() => {
|
||||
getBorrowWithdraw({withdrawAmount: withdrawAmount.value}).then(res => {
|
||||
showToast('提现成功')
|
||||
router.push({
|
||||
path: '/serveList'
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
const userInfo = reactive({
|
||||
backCardNum: '',
|
||||
bankType: '',
|
||||
cardBackPicture: '',
|
||||
cardFrontPicture: '',
|
||||
cardNum: '',
|
||||
companyAddress: '',
|
||||
companyAddressInfo: '',
|
||||
companyName: '',
|
||||
companyPhone: '',
|
||||
companyTitle: '',
|
||||
companyYear: '',
|
||||
customerAddress: '',
|
||||
customerAddressInfo: '',
|
||||
customerId: 0,
|
||||
id: 0,
|
||||
kinsfolkName: '',
|
||||
kinsfolkPhone: '',
|
||||
kinsfolkRef: '',
|
||||
kinsfolkRefText: '',
|
||||
realName: ''
|
||||
})
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
})
|
||||
}
|
||||
|
||||
// 收款账号脱敏
|
||||
const backCardNumDesensitization = (backCardNum: string) => {
|
||||
const blen = backCardNum.length
|
||||
if (blen < 8) {
|
||||
return backCardNum
|
||||
}
|
||||
let str = ''
|
||||
for (let i = 0; i < blen - 7; i++) {
|
||||
str+='*'
|
||||
}
|
||||
|
||||
return backCardNum.slice(0, 4) + str + backCardNum.slice(blen-3, blen)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
_getUserInfo()
|
||||
withdrawAmount.value = route.query.account
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
background-image: linear-gradient(to bottom, #f9bf3a, #ffffff) !important;
|
||||
padding-bottom: 30px;
|
||||
height: 100vh;
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.id-card-box {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tt {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background: #f6f9fd;
|
||||
color: #738aa4;
|
||||
padding: 20px 0;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
.xieyi {
|
||||
padding: 30px 35px;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.font-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
</style>
|
||||
152
src/views/login/index.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#ffffff00" :placeholder="false"/>
|
||||
<div class="content">
|
||||
<j-gap height="50" background="#F9BF3A" opacity="0"/>
|
||||
<div class="slogan">
|
||||
<div class="slogan-1">登录</div>
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<!-- 输入手机号,调起手机号键盘 -->
|
||||
<van-field
|
||||
v-model="loginData.mobile"
|
||||
label="账号"
|
||||
class="login-btn"
|
||||
placeholder="请输入账号"
|
||||
label-align="top"
|
||||
type="tel"
|
||||
/>
|
||||
|
||||
<!-- 允许输入数字,调起带符号的纯数字键盘 -->
|
||||
<van-field
|
||||
v-model="loginData.password"
|
||||
label="密码"
|
||||
placeholder="请输入密码"
|
||||
class="login-btn"
|
||||
label-align="top"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<van-button
|
||||
:disabled="!loginData.password || !loginData.mobile"
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; margin: 20px 0"
|
||||
@click.stop="loginBtn"
|
||||
>
|
||||
登录
|
||||
</van-button>
|
||||
|
||||
<view class="op">
|
||||
<view @click="goRegister" class="register">注册账号</view>
|
||||
<view @click="goForget" class="forget">忘记密码</view>
|
||||
</view>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {showToast} from "vant";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loginData = reactive({
|
||||
phone: null,
|
||||
mobile: '',
|
||||
// mobile: '15302786929',
|
||||
mobileCode: null,
|
||||
password: '',
|
||||
// password: '123123',
|
||||
loginRole: 5,
|
||||
openId: '',
|
||||
ticket: null,
|
||||
randStr: null
|
||||
})
|
||||
|
||||
|
||||
const go = (key) => {
|
||||
// agreement
|
||||
router.push({
|
||||
path: '/agreement',
|
||||
query: {
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
const goRegister = () => {
|
||||
router.push({
|
||||
path: '/register'
|
||||
})
|
||||
}
|
||||
const goForget = () => {
|
||||
router.push({
|
||||
path: '/forget'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const loginBtn = () => {
|
||||
userStore.login(loginData).then(res => {
|
||||
router.replace('/home')
|
||||
}, err => {
|
||||
showToast(err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
// userStore.setOpenId('omWdJ62bH_6HXLQVOIefzN9J1oi4')
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
padding: 0 20px;
|
||||
background-image: linear-gradient(to bottom, #F9BF3A, #ffffff, #ffffff, #ffffff, #ffffff);
|
||||
height: 100vh;
|
||||
|
||||
.slogan {
|
||||
padding-top: 168px;
|
||||
|
||||
.slogan-1 {
|
||||
font-size: 70px;
|
||||
color: #3f3f3f;
|
||||
}
|
||||
|
||||
.slogan-2 {
|
||||
font-weight: 400;
|
||||
font-size: 32px;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.input-box {
|
||||
padding-top: 120px;
|
||||
|
||||
.login-btn {
|
||||
margin-bottom: 40px;
|
||||
background: #12332100;
|
||||
}
|
||||
|
||||
.password-btn {
|
||||
border-left: 1px solid #CCCCCC;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.op {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
50
src/views/my/contract/index.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div>
|
||||
<j-nav-bar />
|
||||
<div class="contract" v-if="contractHtml" v-html="contractHtml"></div>
|
||||
<div v-else class="noData">暂无合同</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, ref} from 'vue'
|
||||
import {getContract} from "@/api";
|
||||
import {showToast} from "vant";
|
||||
import {useRoute} from "vue-router";
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const contractHtml = ref('')
|
||||
|
||||
|
||||
|
||||
const _getContract = () => {
|
||||
if (route.query.tradeNo) {
|
||||
getContract({tradeNo: route.query.tradeNo}).then(res => {
|
||||
contractHtml.value = res
|
||||
})
|
||||
} else {
|
||||
showToast('暂无合同')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
_getContract()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contract {
|
||||
//width: 100%;
|
||||
padding: 20px ;
|
||||
}
|
||||
.noData {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #cccccc;
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
128
src/views/my/myLoan/index.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#f9bf3a"/>
|
||||
<div>
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
|
||||
<view class="j-item" v-for="item in borrowList" :key="item.id" @click="goInfo(item.tradeNo)">
|
||||
<view class="j-item-t">贷款编号:{{item.tradeNo}}</view>
|
||||
|
||||
<view style="display: flex; justify-content: space-between">
|
||||
|
||||
<view class="j-item-c">
|
||||
<view>贷款总额:{{ item.totalLoanMoney }}元</view>
|
||||
<view class="yellow_color1">每期还款:{{ item.avgRepayment }}*{{ item.totalMonth }}</view>
|
||||
<view>贷款申请日期:{{ new Date(item.createTime).format('yyyy-MM-dd hh:mm:ss') }}</view>
|
||||
</view>
|
||||
|
||||
<view :style="{paddingRight: px2vw(20)}" @click.stop="toContract(item.tradeNo)" style="display: flex; justify-content: center; align-items: center; flex-flow: column">
|
||||
<van-icon size="50" name="orders-o" />
|
||||
合同
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {getBorrowPage} from "@/api";
|
||||
import {useRouter} from "vue-router";
|
||||
import {px2vw} from "@/utils";
|
||||
const list = ref([]);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
const refreshing = ref(false);
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const onLoad = () => {
|
||||
if (refreshing.value) {
|
||||
list.value = [];
|
||||
refreshing.value = false;
|
||||
}
|
||||
|
||||
_getBorrowPage()
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
// 清空列表数据
|
||||
finished.value = false;
|
||||
|
||||
// 重新加载数据
|
||||
// 将 loading 设置为 true,表示处于加载状态
|
||||
loading.value = true;
|
||||
onLoad();
|
||||
};
|
||||
|
||||
const borrowPage = {
|
||||
pageNum: 0,
|
||||
pageSize: 100,
|
||||
}
|
||||
const borrowList = reactive([])
|
||||
const _getBorrowPage = () => {
|
||||
getBorrowPage(borrowPage).then(res => {
|
||||
borrowList.newPush(res)
|
||||
})
|
||||
}
|
||||
|
||||
const goInfo = (tradeNo) => {
|
||||
router.push({
|
||||
path: '/borrowInfo',
|
||||
query: {
|
||||
tradeNo: tradeNo
|
||||
}
|
||||
})
|
||||
}
|
||||
const toContract = (tradeNo) => {
|
||||
router.push({
|
||||
path: '/contract',
|
||||
query: {
|
||||
tradeNo: tradeNo
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
_getBorrowPage()
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.j-item {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
&-t {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: #f9fbff;
|
||||
color: #738aa4;
|
||||
}
|
||||
&-c {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
111
src/views/my/myRepayment/index.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#f9bf3a"/>
|
||||
<div>
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
|
||||
<view class="j-item" v-for="item in borrowList" :key="item.id" @click="goInfo(item.tradeNo)">
|
||||
<view class="j-item-t">贷款编号:{{item.tradeNo}}</view>
|
||||
|
||||
<view class="j-item-c">
|
||||
<view>贷款总额:{{ item.totalLoanMoney }}元</view>
|
||||
<view class="yellow_color1">每期还款:{{ item.avgRepayment }}*{{ item.totalMonth }}</view>
|
||||
<view>贷款申请日期:{{ new Date(item.createTime).format('yyyy-MM-dd hh:mm:ss') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {getBorrowPage} from "@/api";
|
||||
import {useRouter} from "vue-router";
|
||||
const list = ref([]);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
const refreshing = ref(false);
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const onLoad = () => {
|
||||
if (refreshing.value) {
|
||||
list.value = [];
|
||||
refreshing.value = false;
|
||||
}
|
||||
|
||||
_getBorrowPage()
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
// 清空列表数据
|
||||
finished.value = false;
|
||||
|
||||
// 重新加载数据
|
||||
// 将 loading 设置为 true,表示处于加载状态
|
||||
loading.value = true;
|
||||
onLoad();
|
||||
};
|
||||
|
||||
const borrowPage = {
|
||||
pageNum: 0,
|
||||
pageSize: 100,
|
||||
}
|
||||
const borrowList = reactive([])
|
||||
const _getBorrowPage = () => {
|
||||
getBorrowPage(borrowPage).then(res => {
|
||||
borrowList.newPush(res)
|
||||
})
|
||||
}
|
||||
|
||||
const goInfo = (tradeNo) => {
|
||||
router.push({
|
||||
path: '/borrowInfo',
|
||||
query: {
|
||||
tradeNo: tradeNo
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
_getBorrowPage()
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.j-item {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
&-t {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: #f9fbff;
|
||||
color: #738aa4;
|
||||
}
|
||||
&-c {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
65
src/views/my/signature/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<j-nav-bar v-if="!useEdit" />
|
||||
<van-signature v-if="!useEdit" @submit="onSubmit" @clear="onClear" />
|
||||
<div class="my-signature">我的签名:</div>
|
||||
<van-image class="my-signature-image" v-if="userInfo.signature" :src="userInfo.signature" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {reactive, ref, onMounted} from 'vue';
|
||||
import {showToast} from "vant";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getBorrowPage, getUserInfo, updateCustomerCard} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
const image = ref('');
|
||||
const onSubmit = (data) => {
|
||||
userInfo.signature = data.image;
|
||||
saveUserInfoBtn()
|
||||
};
|
||||
|
||||
const borrowPage = {
|
||||
pageNum: 0,
|
||||
pageSize: 3,
|
||||
}
|
||||
const useEdit = ref(true)
|
||||
const _getBorrowPage = () => {
|
||||
getBorrowPage(borrowPage).then(res => {
|
||||
useEdit.value = res.length !== 0;
|
||||
})
|
||||
}
|
||||
|
||||
const onClear = () => showToast('clear');
|
||||
|
||||
const userInfo = reactive({
|
||||
signature: ''
|
||||
})
|
||||
const router = useRouter()
|
||||
const saveUserInfoBtn = () => {
|
||||
updateCustomerCard(userInfo).then(res => {
|
||||
console.log('')
|
||||
router.back()
|
||||
})
|
||||
}
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
_getUserInfo()
|
||||
_getBorrowPage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-signature {
|
||||
padding: 20px;
|
||||
}
|
||||
.my-signature-image {
|
||||
margin: 20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
131
src/views/my/userInfo/index.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#ffffff00" :placeholder="false"/>
|
||||
<div class="content">
|
||||
<j-gap height="120" background="#F9BF3A" opacity="0"/>
|
||||
<van-cell-group inset>
|
||||
<van-cell :style="{ padding: px2vw(40) + ' ' + px2vw(35)}" title="我的资料" is-link to="userInfo1" :value="userInfo.cardFlag ? '完整' : '不完整'">
|
||||
<template #title>
|
||||
<div class="t">身份信息</div>
|
||||
<div class="t2">*让我们了解您的资料信息</div>
|
||||
</template>
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 12px">
|
||||
<van-icon :name="getAssetsImages('my/uuuu1.png')" size="45"/>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
|
||||
<van-cell :style="{ padding: px2vw(40) + ' ' + px2vw(35)}" title="我的借款" is-link to="userInfo2" :value="userInfo.infoFlag ? '完整' : '不完整'">
|
||||
<template #title>
|
||||
<div class="t">资料信息</div>
|
||||
<div class="t2">*让我们了解您的资料信息</div>
|
||||
</template>
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 12px">
|
||||
<van-icon :name="getAssetsImages('my/uuuu2.png')" size="45"/>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
|
||||
<van-cell :style="{ padding: px2vw(40) + ' ' + px2vw(35)}" title="我的还款" is-link to="userInfo3" :value="userInfo.bankFlag ? '完整' : '不完整'">
|
||||
<template #title>
|
||||
<div class="t">收款银行卡</div>
|
||||
<div class="t2">*让我们了解您的资料信息</div>
|
||||
</template>
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 12px">
|
||||
<van-icon :name="getAssetsImages('my/uuuu3.png')" size="45"/>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell v-if="userInfo.allowSignature" :style="{ padding: px2vw(40) + ' ' + px2vw(35)}" title="我的借款" is-link to="signature" :value="userInfo.signatureFlag ? '完整' : '不完整'" >
|
||||
<template #title>
|
||||
<div class="t">签名信息</div>
|
||||
<div class="t2">*让我们了解您的资料信息</div>
|
||||
</template>
|
||||
<template #icon>
|
||||
<div style="display: flex; justify-content: center; align-items: center; padding-right: 12px">
|
||||
<van-icon :name="getAssetsImages('my/uuuu2.png')" size="45"/>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
|
||||
|
||||
<div style="padding: 60px 20px 60px">
|
||||
<van-button
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; "
|
||||
@click.stop="goHome"
|
||||
>
|
||||
立即借款
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {getAssetsImages, px2vw} from "@/utils";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getBorrowPage, getUserInfo} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {onMounted, reactive} from "vue";
|
||||
|
||||
const router = useRouter()
|
||||
const goHome = () => {
|
||||
router.push({
|
||||
path: '/home'
|
||||
})
|
||||
}
|
||||
const userInfo = reactive({
|
||||
cardFlag: false,
|
||||
infoFlag: false,
|
||||
bankFlag: false,
|
||||
signatureFlag: false,
|
||||
allowSignature: true
|
||||
})
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
userInfo.signatureFlag = !!res.signature
|
||||
})
|
||||
}
|
||||
const borrowPage = {
|
||||
pageNum: 0,
|
||||
pageSize: 100,
|
||||
}
|
||||
const borrowList = reactive([])
|
||||
const _getBorrowPage = () => {
|
||||
getBorrowPage(borrowPage).then(res => {
|
||||
borrowList.newPush(res)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
_getUserInfo()
|
||||
_getBorrowPage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
background-image: linear-gradient(to bottom, #f9bf3a, #ffffff) !important;
|
||||
height: 100vh;
|
||||
|
||||
.t {
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
color: #111a34;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.t2 {
|
||||
margin-top: 4px;
|
||||
font-size: 24px;
|
||||
color: #858b9c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
266
src/views/my/userInfo1/index.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#f9bf3a" :placeholder="false"/>
|
||||
<van-form @submit="saveUserInfoBtn">
|
||||
<div class="content">
|
||||
<j-gap height="120" background="#F9BF3A" opacity="0"/>
|
||||
<div class="action">
|
||||
<view class="tt">填写真实有效的信息,审核才会通过哦~</view>
|
||||
<van-field required v-model="userInfo.realName" type="text" label="姓名" placeholder="请输入真实姓名" :rules="[{ required: true, message: '请输入真实姓名' }]"/>
|
||||
<van-field required v-model="userInfo.cardNum" type="text" label="身份证号" placeholder="请输入真实身份证号" :rules="[{ required: true, message: '请输入真实身份证号' }]"/>
|
||||
<!-- <van-field required label="身份证号" placeholder="请输入真实身份证号" v-model="userInfo.cardNum" readonly clickable @touchstart.stop="idCardKeyboardShow = true" :rules="[{ required: true, message: '请输入真实身份证号' }]"/>-->
|
||||
|
||||
<!-- <van-number-keyboard-->
|
||||
<!-- :show="idCardKeyboardShow"-->
|
||||
<!-- v-model="userInfo.cardNum"-->
|
||||
<!-- extra-key="X"-->
|
||||
<!-- close-button-text="完成"-->
|
||||
<!-- @blur="idCardKeyboardShow = false"-->
|
||||
<!-- />-->
|
||||
</div>
|
||||
|
||||
<div class="action">
|
||||
<view class="tt">
|
||||
<view>*需本人身份证,且内容清晰可辨</view>
|
||||
<view>请您确认拍照权限已开启</view>
|
||||
</view>
|
||||
|
||||
<view class="action-content">
|
||||
|
||||
<view class="action-content-item">
|
||||
<van-uploader :rules="[{ required: true, message: '点击上传身份证人像面' }]" required :after-read="afterReadIdFront" :before-read="beforeRead" :max-count="1">
|
||||
<div class="id-card-box">
|
||||
<van-image v-if="userInfo.cardBackPicture" :height="px2vw(156)" :src="userInfo.cardBackPicture"
|
||||
:width="px2vw(236)"/>
|
||||
<van-image v-if="!userInfo.cardBackPicture" :height="px2vw(156)" :src="getAssetsImages('my/idcard1.png')"
|
||||
:width="px2vw(236)"/>
|
||||
<div :style="{fontSize: px2vw(26)}" style="color: #858B9C; width: 100%; text-align: center; padding-top: 10px">
|
||||
点击上传身份证人像面
|
||||
</div>
|
||||
</div>
|
||||
</van-uploader>
|
||||
</view>
|
||||
|
||||
<view class="action-content-item">
|
||||
<van-uploader :rules="[{ required: true, message: '点击上传身份证国徽面' }]" required :after-read="afterReadIdBack" :before-read="beforeRead" :max-count="1">
|
||||
<div class="id-card-box">
|
||||
<van-image v-if="userInfo.cardFrontPicture" :height="px2vw(156)" :src="userInfo.cardFrontPicture"
|
||||
:width="px2vw(236)"/>
|
||||
<van-image v-if="!userInfo.cardFrontPicture" :height="px2vw(156)" :src="getAssetsImages('my/idcard1.png')"
|
||||
:width="px2vw(236)"/>
|
||||
<div :style="{fontSize: px2vw(26)}" style="color: #858B9C; width: 100%; text-align: center; padding-top: 10px">
|
||||
点击上传身份证国徽面
|
||||
</div>
|
||||
</div>
|
||||
</van-uploader>
|
||||
</view>
|
||||
|
||||
<view class="action-content-item">
|
||||
<van-uploader :rules="[{ required: true, message: '点击上传手持身份证照' }]" required :after-read="afterReadAvatar" :before-read="beforeRead" :max-count="1">
|
||||
<div class="id-card-box">
|
||||
<van-image v-if="userInfo.handCardPicture" :height="px2vw(156)" :src="userInfo.handCardPicture"
|
||||
:width="px2vw(236)"/>
|
||||
<van-image v-if="!userInfo.handCardPicture" :height="px2vw(156)" :src="getAssetsImages('my/idcard1.png')"
|
||||
:width="px2vw(236)"/>
|
||||
<div :style="{fontSize: px2vw(26)}" style="color: #858B9C; width: 100%; text-align: center; padding-top: 10px">
|
||||
点击上传手持身份证照
|
||||
</div>
|
||||
</div>
|
||||
</van-uploader>
|
||||
</view>
|
||||
|
||||
|
||||
<view style="width: 100%; padding: 20px 0">
|
||||
<view>拍摄要求</view>
|
||||
<view style="display: flex; justify-content: space-between; align-items: center">
|
||||
<view style="display: flex; justify-content: center; align-items: center; flex-flow: column">
|
||||
<van-image :height="px2vw(92)" :width="px2vw(148)" :src="getAssetsImages('my/idcard2.png')"/>
|
||||
<view class="gray_color font-22">√标准拍摄</view>
|
||||
</view>
|
||||
<view style="display: flex; justify-content: center; align-items: center; flex-flow: column">
|
||||
<van-image :height="px2vw(92)" :width="px2vw(148)" :src="getAssetsImages('my/idcard3.png')"/>
|
||||
<view class="gray_color font-22">×边框缺失</view>
|
||||
</view>
|
||||
<view style="display: flex; justify-content: center; align-items: center; flex-flow: column">
|
||||
<van-image :height="px2vw(92)" :width="px2vw(148)" :src="getAssetsImages('my/idcard4.png')"/>
|
||||
<view class="gray_color font-22">×照片模糊</view>
|
||||
</view>
|
||||
<view style="display: flex; justify-content: center; align-items: center; flex-flow: column">
|
||||
<van-image :height="px2vw(92)" :width="px2vw(148)" :src="getAssetsImages('my/idcard5.png')"/>
|
||||
<view class="gray_color font-22">×闪光强烈</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 0 20px 60px">
|
||||
<van-button
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; "
|
||||
native-type="submit"
|
||||
:disabled="!useEdit || !(userInfo.realName && userInfo.cardNum)"
|
||||
>
|
||||
提交
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {getBorrowPage, getUserInfo, updateCustomerCard, uploadCommon} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {getAssetsImages, px2vw} from "@/utils";
|
||||
import {showToast} from "vant";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
const idCardKeyboardShow = ref(false)
|
||||
|
||||
const userInfo = reactive({
|
||||
backCardNum: '',
|
||||
bankType: '',
|
||||
cardBackPicture: '',
|
||||
cardFrontPicture: '',
|
||||
handCardPicture: '',
|
||||
cardNum: '',
|
||||
companyAddress: '',
|
||||
companyAddressInfo: '',
|
||||
companyName: '',
|
||||
companyPhone: '',
|
||||
companyTitle: '',
|
||||
companyYear: '',
|
||||
customerAddress: '',
|
||||
customerAddressInfo: '',
|
||||
customerId: 0,
|
||||
id: 0,
|
||||
kinsfolkName: '',
|
||||
kinsfolkPhone: '',
|
||||
kinsfolkRef: '',
|
||||
realName: ''
|
||||
})
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
})
|
||||
}
|
||||
|
||||
const borrowPage = {
|
||||
pageNum: 0,
|
||||
pageSize: 3,
|
||||
}
|
||||
const useEdit = ref(true)
|
||||
const _getBorrowPage = () => {
|
||||
getBorrowPage(borrowPage).then(res => {
|
||||
useEdit.value = res.length === 0;
|
||||
})
|
||||
}
|
||||
|
||||
const beforeRead = (file) => {
|
||||
if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
|
||||
showToast('请上传 jpg 或者 png 格式图片');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const afterReadAvatar = (file) => {
|
||||
// 此时可以自行将文件上传至服务器
|
||||
upLoadImage(file, 'handCardPicture')
|
||||
};
|
||||
const afterReadIdFront = (file) => {
|
||||
upLoadImage(file, 'cardBackPicture')
|
||||
};
|
||||
const afterReadIdBack = (file) => {
|
||||
upLoadImage(file, 'cardFrontPicture')
|
||||
};
|
||||
|
||||
const upLoadImage = (file, flag) => {
|
||||
uploadCommon(file.file).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
userInfo[flag] = res.data.data.url
|
||||
} else {
|
||||
showToast('上传失败');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const saveUserInfoBtn = () => {
|
||||
updateCustomerCard(userInfo).then(res => {
|
||||
console.log('')
|
||||
router.back()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
_getBorrowPage()
|
||||
_getUserInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
background-image: linear-gradient(to bottom, #f9bf3a, #ffffff) !important;
|
||||
padding-bottom: 30px;
|
||||
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.id-card-box {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tt {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background: #f6f9fd;
|
||||
color: #738aa4;
|
||||
padding: 20px 0;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.font-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
</style>
|
||||
261
src/views/my/userInfo2/index.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<j-nav-bar color="#FFF" nav-bar-background="#f9bf3a" :placeholder="false"/>
|
||||
|
||||
<van-form @submit="saveUserInfoBtn">
|
||||
<div class="content">
|
||||
<j-gap height="120" background="#F9BF3A" opacity="0"/>
|
||||
<div class="action">
|
||||
<view class="tt">填写真实有效的信息,审核才会通过哦~</view>
|
||||
<!-- <van-field required v-model="userInfo.realName" type="text" label="姓名" placeholder="请输入真实姓名" :rules="[{ required: true, message: '请输入真实姓名' }]"/>-->
|
||||
<van-field required v-model="userInfo.companyName" type="text" label="单位名称" placeholder="请输入单位名称" :rules="[{ required: true, message: '请输入单位名称' }]"/>
|
||||
<van-field required v-model="userInfo.companyTitle" type="text" label="职位" placeholder="请输入职位" :rules="[{ required: true, message: '请输入职位' }]"/>
|
||||
<van-field required v-model="userInfo.companyPhone" type="tel" label="单位电话" placeholder="号码加区号(非必填)" :rules="[{ required: true, message: '号码加区号' }]"/>
|
||||
<van-field required v-model="userInfo.companyYear" type="number" label="工作年龄" placeholder="请输入工龄" :rules="[{ required: true, message: '请输入工龄' }]"/>
|
||||
<van-field required v-model="userInfo.incomeWan" type="number" label="月薪" placeholder="请输入月薪" :rules="[{ required: true, message: '请输入月薪' }]"/>
|
||||
<van-field
|
||||
v-model="userInfo.companyAddress"
|
||||
is-link
|
||||
required
|
||||
readonly
|
||||
:rules="[{ required: true, message: '请选择省市区' }]"
|
||||
label="单位地址"
|
||||
placeholder="请选择省市区"
|
||||
@click="companyAddressShow = true"
|
||||
/>
|
||||
<van-popup v-model:show="companyAddressShow" round position="bottom">
|
||||
<van-cascader
|
||||
v-model="cascaderValue"
|
||||
title="请选择所在地区"
|
||||
:options="options"
|
||||
@close="companyAddressShow = false"
|
||||
@finish="onFinish"
|
||||
/>
|
||||
</van-popup>
|
||||
<van-field required v-model="userInfo.companyAddressInfo" type="text" label="详细地址" placeholder="请输入详细地址" :rules="[{ required: true, message: '请输入详细地址' }]"/>
|
||||
|
||||
|
||||
<van-field
|
||||
v-model="userInfo.customerAddress"
|
||||
is-link
|
||||
required
|
||||
readonly
|
||||
:rules="[{ required: true, message: '请选择所在地区' }]"
|
||||
label="现居住地址"
|
||||
placeholder="请选择所在地区"
|
||||
@click="customerAddressShow = true"
|
||||
/>
|
||||
<van-popup v-model:show="customerAddressShow" round position="bottom">
|
||||
<van-cascader
|
||||
v-model="customerAddressValue"
|
||||
title="请选择所在地区"
|
||||
:options="options"
|
||||
@close="customerAddressShow = false"
|
||||
@finish="onCustomerAddressFinish"
|
||||
/>
|
||||
</van-popup>
|
||||
<van-field required v-model="userInfo.customerAddressInfo" type="text" label="详细地址" placeholder="例:东北石油大学启智寝室楼2A603" :rules="[{ required: true, message: '请输入详细地址' }]"/>
|
||||
</div>
|
||||
|
||||
<div class="action">
|
||||
<view class="tt">
|
||||
直系亲属联系人
|
||||
</view>
|
||||
|
||||
<van-field required v-model="userInfo.kinsfolkName" type="text" label="姓名" placeholder="请输入真实姓名" :rules="[{ required: true, message: '请输入真实姓名' }]"/>
|
||||
<van-field required v-model="userInfo.kinsfolkPhone" type="tel" label="手机号码" placeholder="请输入手机号码" :rules="[{ required: true, message: '请输入手机号码' }]"/>
|
||||
<van-field
|
||||
v-model="userInfo.kinsfolkRefText"
|
||||
is-link
|
||||
:rules="[{ required: true, message: '请选择关系' }]"
|
||||
required
|
||||
readonly
|
||||
label="关系"
|
||||
placeholder="请选择关系"
|
||||
@click="showPicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showPicker" round position="bottom">
|
||||
<van-picker
|
||||
:columns="columns"
|
||||
@cancel="showPicker = false"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 0 20px 60px">
|
||||
<van-button
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; "
|
||||
native-type="submit"
|
||||
:disabled="!useEdit"
|
||||
>
|
||||
提交
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {getBorrowPage, getUserInfo, updateCustomerCard} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {showToast} from "vant";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useCascaderAreaData} from "@vant/area-data";
|
||||
|
||||
const companyAddressShow = ref(false)
|
||||
const customerAddressShow = ref(false)
|
||||
const cascaderValue = ref('');
|
||||
const customerAddressValue = ref('');
|
||||
const columns = [
|
||||
{ text: '父母', value: '1' },
|
||||
{ text: '配偶', value: '2' },
|
||||
{ text: '子女', value: '3' },
|
||||
{ text: '祖父母', value: '4' },
|
||||
];
|
||||
const userInfo = reactive({
|
||||
backCardNum: '',
|
||||
incomeWan: 0,
|
||||
bankType: '',
|
||||
cardBackPicture: '',
|
||||
cardFrontPicture: '',
|
||||
cardNum: '',
|
||||
companyAddress: '',
|
||||
companyAddressInfo: '',
|
||||
companyName: '',
|
||||
companyPhone: '',
|
||||
companyTitle: '',
|
||||
companyYear: '',
|
||||
customerAddress: '',
|
||||
customerAddressInfo: '',
|
||||
customerId: 0,
|
||||
id: 0,
|
||||
kinsfolkName: '',
|
||||
kinsfolkPhone: '',
|
||||
kinsfolkRef: '',
|
||||
kinsfolkRefText: '',
|
||||
realName: ''
|
||||
})
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
columns.forEach(c => {
|
||||
if (c.value == userInfo.kinsfolkRef) {
|
||||
userInfo.kinsfolkRefText = c.text
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const borrowPage = {
|
||||
pageNum: 0,
|
||||
pageSize: 3,
|
||||
}
|
||||
const useEdit = ref(true)
|
||||
const _getBorrowPage = () => {
|
||||
getBorrowPage(borrowPage).then(res => {
|
||||
useEdit.value = res.length === 0;
|
||||
})
|
||||
}
|
||||
|
||||
const options = useCascaderAreaData();
|
||||
const onFinish = ({ selectedOptions }) => {
|
||||
companyAddressShow.value = false;
|
||||
userInfo.companyAddress = selectedOptions.map((option) => option.text).join('/');
|
||||
};
|
||||
|
||||
|
||||
const onCustomerAddressFinish = ({ selectedOptions }) => {
|
||||
customerAddressShow.value = false;
|
||||
userInfo.customerAddress = selectedOptions.map((option) => option.text).join('/');
|
||||
};
|
||||
|
||||
const showPicker = ref(false);
|
||||
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
showPicker.value = false;
|
||||
userInfo.kinsfolkRef = selectedOptions[0].value;
|
||||
userInfo.kinsfolkRefText = selectedOptions[0].text;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const saveUserInfoBtn = (values) => {
|
||||
updateCustomerCard(userInfo).then(res => {
|
||||
router.back()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
_getUserInfo()
|
||||
_getBorrowPage()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
background-image: linear-gradient(to bottom, #f9bf3a, #ffffff) !important;
|
||||
padding-bottom: 30px;
|
||||
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.id-card-box {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tt {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background: #f6f9fd;
|
||||
color: #738aa4;
|
||||
padding: 20px 0;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.font-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
</style>
|
||||
197
src/views/my/userInfo3/index.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<j-nav-bar :placeholder="false" color="#FFF" nav-bar-background="#f9bf3a"/>
|
||||
|
||||
<van-form @submit="saveUserInfoBtn">
|
||||
|
||||
<div class="content">
|
||||
<j-gap background="#F9BF3A" height="120" opacity="0"/>
|
||||
<div class="action">
|
||||
<view class="tt">填写真实有效的信息,审核才会通过哦~</view>
|
||||
<van-field
|
||||
v-model="userInfo.bankType"
|
||||
:rules="[{ required: true, message: '请选择开户银行' }]"
|
||||
label="开户银行"
|
||||
placeholder="请输入开户银行"
|
||||
:readonly="!useEdit"
|
||||
required
|
||||
/>
|
||||
<!-- <van-popup v-model:show="showPicker" position="bottom" round>
|
||||
<van-picker
|
||||
:columns="columns"
|
||||
@cancel="showPicker = false"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</van-popup>-->
|
||||
<van-field :readonly="!useEdit" @paste="pasteField" @copy="copyField" v-model="userInfo.backCardNum" :rules="[{ required: true, message: '请输入银行卡号' }]" label="银行卡号" placeholder="请输入银行卡号" required
|
||||
type="digit"/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="padding: 0 20px 60px">
|
||||
<van-button
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
native-type="submit"
|
||||
round
|
||||
style="width: 100%; "
|
||||
:disabled="!useEdit"
|
||||
>
|
||||
提交
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {getBankType, getBorrowPage, getUserInfo, updateCustomerCard, uploadCommon} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
import {showToast} from "vant";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
|
||||
const userInfo = reactive({
|
||||
backCardNum: '',
|
||||
bankType: '',
|
||||
cardBackPicture: '',
|
||||
cardFrontPicture: '',
|
||||
cardNum: '',
|
||||
companyAddress: '',
|
||||
companyAddressInfo: '',
|
||||
companyName: '',
|
||||
companyPhone: '',
|
||||
companyTitle: '',
|
||||
companyYear: '',
|
||||
customerAddress: '',
|
||||
customerAddressInfo: '',
|
||||
customerId: 0,
|
||||
id: 0,
|
||||
kinsfolkName: '',
|
||||
kinsfolkPhone: '',
|
||||
kinsfolkRef: '',
|
||||
realName: ''
|
||||
})
|
||||
|
||||
const borrowPage = {
|
||||
pageNum: 0,
|
||||
pageSize: 3,
|
||||
}
|
||||
const useEdit = ref(true)
|
||||
const _getBorrowPage = () => {
|
||||
getBorrowPage(borrowPage).then(res => {
|
||||
useEdit.value = res.length === 0;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const _getUserInfo = () => {
|
||||
getUserInfo().then(res => {
|
||||
resetData(userInfo, res)
|
||||
})
|
||||
}
|
||||
|
||||
const bankTypeList = reactive([])
|
||||
const columns: any[] = reactive([])
|
||||
const _getBankType = () => {
|
||||
getBankType().then(res => {
|
||||
bankTypeList.newPush(res)
|
||||
bankTypeList.forEach(btl => {
|
||||
columns.push({
|
||||
value: btl,
|
||||
text: btl
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
const showPicker = ref(false);
|
||||
const onConfirm = ({selectedOptions}) => {
|
||||
showPicker.value = false;
|
||||
userInfo.bankType = selectedOptions[0].value;
|
||||
};
|
||||
|
||||
const pasteField = (e) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
const copyField = (e) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const saveUserInfoBtn = () => {
|
||||
updateCustomerCard(userInfo).then(res => {
|
||||
console.log('')
|
||||
router.back()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
_getUserInfo()
|
||||
// _getBankType()
|
||||
_getBorrowPage()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
background-image: linear-gradient(to bottom, #f9bf3a, #ffffff) !important;
|
||||
padding-bottom: 30px;
|
||||
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.id-card-box {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tt {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background: #f6f9fd;
|
||||
color: #738aa4;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.font-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
</style>
|
||||
180
src/views/register/index.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<j-nav-bar :placeholder="false" color="#FFF" nav-bar-background="#ffffff00"/>
|
||||
|
||||
<div class="content">
|
||||
<j-gap height="50" background="#F9BF3A" opacity="0"/>
|
||||
<div class="slogan">
|
||||
<div class="slogan-1">注册</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="input-box">
|
||||
<!-- 允许输入数字,调起带符号的纯数字键盘 -->
|
||||
<van-field
|
||||
v-model="loginData.phoneNumber"
|
||||
class="login-btn"
|
||||
label="手机号码"
|
||||
placeholder="请输入手机号码"
|
||||
type="tel"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="loginData.code"
|
||||
class="login-btn"
|
||||
label="验证码"
|
||||
placeholder="请输入验证码"
|
||||
type="number"
|
||||
>
|
||||
<template #button>
|
||||
<div class="password-btn">
|
||||
<van-count-down v-show="countDownFlag" ref="countDown" :auto-start="false" :time="time" @finish="onFinish">
|
||||
<template #default="timeData">
|
||||
<span class="block">{{ timeData.seconds }}秒</span>
|
||||
</template>
|
||||
</van-count-down>
|
||||
<div v-show="!countDownFlag" style="color: #bc7c1c" @click="start">发送验证码</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<!-- 允许输入数字,调起带符号的纯数字键盘 -->
|
||||
<van-field
|
||||
v-model="loginData.password"
|
||||
class="login-btn"
|
||||
label="登录密码"
|
||||
placeholder="请设置6-16位密码"
|
||||
type="text"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<van-button
|
||||
:disabled="!loginData.password || !loginData.phoneNumber"
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; margin: 20px 0"
|
||||
@click.stop="registerBtn"
|
||||
>
|
||||
注册
|
||||
</van-button>
|
||||
|
||||
<view class="op">
|
||||
<view class="register" @click="goLogin">登录</view>
|
||||
</view>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {reactive, ref} from "vue";
|
||||
import {showToast} from "vant";
|
||||
import {useRouter} from "vue-router";
|
||||
import {register, sendSmsRegister} from "@/api/login";
|
||||
import {useUserStore} from "@/store/modules/user";
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loginData = reactive({
|
||||
phone: null,
|
||||
phoneNumber: '',
|
||||
code: null,
|
||||
password: '',
|
||||
loginRole: 5,
|
||||
openId: '',
|
||||
ticket: null,
|
||||
randStr: null
|
||||
})
|
||||
const time = ref(60 * 1000);
|
||||
const countDown = ref();
|
||||
const countDownFlag = ref(false);
|
||||
const start = () => {
|
||||
if (loginData.phoneNumber) {
|
||||
countDown.value.start();
|
||||
countDownFlag.value = true
|
||||
sendSmsRegister({
|
||||
phoneNumber: loginData.phoneNumber
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
loginData.code = res
|
||||
onFinish()
|
||||
})
|
||||
} else {
|
||||
showToast('请输入手机号')
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const registerBtn = () => {
|
||||
register(loginData).then(res => {
|
||||
// goLogin()
|
||||
userStore.login({
|
||||
mobile: loginData.phoneNumber,
|
||||
password: loginData.password
|
||||
}).then(res => {
|
||||
router.replace('/home')
|
||||
}, err => {
|
||||
router.replace('/home')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const goLogin = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
|
||||
|
||||
const onFinish = () => {
|
||||
countDownFlag.value = false
|
||||
countDown.value.reset();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
padding: 0 20px;
|
||||
background-image: linear-gradient(to bottom, #F9BF3A, #ffffff, #ffffff, #ffffff, #ffffff);
|
||||
height: 100vh;
|
||||
|
||||
.slogan {
|
||||
padding-top: 168px;
|
||||
|
||||
.slogan-1 {
|
||||
font-size: 70px;
|
||||
color: #3f3f3f;
|
||||
}
|
||||
|
||||
.slogan-2 {
|
||||
font-weight: 400;
|
||||
font-size: 32px;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.input-box {
|
||||
margin-top: 40px;
|
||||
padding: 20px 0 10px 0;
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 7px -3px #b4b3b3;
|
||||
|
||||
.login-btn {
|
||||
margin-bottom: 40px;
|
||||
background: #12332100;
|
||||
}
|
||||
|
||||
.password-btn {
|
||||
border-left: 1px solid #CCCCCC;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.op {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
221
src/views/uploadPassword/index.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<j-nav-bar :placeholder="false" color="#FFF" nav-bar-background="#F9BF3A00"/>
|
||||
<div class="content">
|
||||
|
||||
<j-gap height="50" background="#F9BF3A" opacity="0"/>
|
||||
<j-gap height="120" opacity="1"/>
|
||||
<view class="action">
|
||||
|
||||
<van-field
|
||||
v-model="loginData.phoneNumber"
|
||||
disabled
|
||||
class="login-btn"
|
||||
label="手机号码"
|
||||
placeholder="请输入手机号码"
|
||||
label-align="top"
|
||||
style="background: #12332100"
|
||||
type="tel"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="loginData.code"
|
||||
class="login-btn"
|
||||
label="验证码"
|
||||
placeholder="请输入验证码"
|
||||
label-align="top"
|
||||
style="background: #12332100"
|
||||
type="number"
|
||||
>
|
||||
<template #button>
|
||||
<div class="password-btn">
|
||||
<van-count-down v-show="countDownFlag" ref="countDown" :auto-start="false" :time="time" @finish="onFinish">
|
||||
<template #default="timeData">
|
||||
<span class="block">{{ timeData.seconds }}秒</span>
|
||||
</template>
|
||||
</van-count-down>
|
||||
<div v-show="!countDownFlag" style="color: #bc7c1c" @click="start">发送验证码</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field
|
||||
v-model="loginData.password"
|
||||
class="login-btn"
|
||||
label="登录密码"
|
||||
label-align="top"
|
||||
placeholder="请设置6-16位密码"
|
||||
style="background: #12332100"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-model="loginData.confirmPassword"
|
||||
class="login-btn"
|
||||
label="确认密码"
|
||||
label-align="top"
|
||||
placeholder="请再次输入密码"
|
||||
style="background: #12332100"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
|
||||
<view style="padding: 20px">
|
||||
|
||||
<van-button
|
||||
:disabled="!loginData.password || !loginData.confirmPassword"
|
||||
color="linear-gradient(to right, #D2A64C, #F9D88D)"
|
||||
round
|
||||
style="width: 100%; margin: 20px 0"
|
||||
@click.stop="updatePwdBtn"
|
||||
>
|
||||
确认修改
|
||||
</van-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {sendSmsForget, updatePwd} from "@/api/login";
|
||||
import {showToast} from "vant";
|
||||
import {getCustomerInfo} from "@/api";
|
||||
import {resetData} from "@/utils/dataUtil";
|
||||
|
||||
const loginData = reactive({
|
||||
phone: null,
|
||||
phoneNumber: '',
|
||||
code: null,
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
checkCode: '',
|
||||
})
|
||||
|
||||
const flag = ref('1')
|
||||
|
||||
|
||||
const time = ref(60 * 1000);
|
||||
const countDown = ref();
|
||||
const countDownFlag = ref(false);
|
||||
const start = () => {
|
||||
if (loginData.phoneNumber) {
|
||||
countDown.value.start();
|
||||
countDownFlag.value = true
|
||||
sendSmsForget({
|
||||
phoneNumber: loginData.phoneNumber
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
loginData.code = res
|
||||
loginData.checkCode = res
|
||||
onFinish()
|
||||
})
|
||||
} else {
|
||||
showToast('请输入手机号')
|
||||
}
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
flag.value = '2'
|
||||
}
|
||||
|
||||
const updatePwdBtn = () => {
|
||||
updatePwd({
|
||||
checkCode: loginData.checkCode,
|
||||
confirmPassword: loginData.confirmPassword,
|
||||
password: loginData.password
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
showToast('修改成功')
|
||||
})
|
||||
}
|
||||
const customerInfo = reactive({
|
||||
"account": 0,
|
||||
"borrowAccount": 0,
|
||||
"id": 0,
|
||||
"lastLoginIp": "",
|
||||
"lastLoginTime": "",
|
||||
"loansFlag": 0,
|
||||
"nickName": "",
|
||||
"phoneNumber": "",
|
||||
"realNameAuth": 0,
|
||||
"repaymentAccount": 0,
|
||||
"status": 0,
|
||||
"updateTime": "",
|
||||
"withdrawFlag": 0
|
||||
})
|
||||
const _getCustomerInfo = () => {
|
||||
getCustomerInfo().then(res => {
|
||||
resetData(customerInfo, res)
|
||||
loginData.phoneNumber = customerInfo.phoneNumber
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const onFinish = () => {
|
||||
countDownFlag.value = false
|
||||
countDown.value.reset();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
_getCustomerInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
padding: 0 10px;
|
||||
background-image: linear-gradient(to bottom, #F9BF3A, #ffffff, #ffffff, #ffffff, #ffffff);
|
||||
height: 100vh;
|
||||
|
||||
.action {
|
||||
margin: 20px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
|
||||
&-item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.id-card-box {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 1vw 0px #e0e0e0;
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tt {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background: #f6f9fd;
|
||||
color: #738aa4;
|
||||
padding: 20px 0;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||