Переглянути джерело

- 完成 项目迁移 从documentFrontEndWeb 到 AlipayFrontendWeb

feat
JinxChen 2 роки тому
джерело
коміт
d93363838a
21 змінених файлів з 1528 додано та 189 видалено
  1. +38
    -20
      README.md
  2. +52
    -0
      src/api/alipay.js
  3. BIN
      src/assets/alipay.png
  4. BIN
      src/assets/antpay.png
  5. BIN
      src/assets/banner_03.jpg
  6. +281
    -0
      src/assets/reset.scss
  7. +0
    -122
      src/components/HelloWorld.vue
  8. +13
    -0
      src/config/models.js
  9. +21
    -0
      src/http/request.js
  10. +56
    -0
      src/main.js
  11. +23
    -15
      src/router/index.js
  12. +57
    -9
      src/store/index.js
  13. +9
    -0
      src/store/prefix.js
  14. +23
    -0
      src/utils/index.js
  15. +0
    -5
      src/views/About.vue
  16. +238
    -0
      src/views/AliPayForm.vue
  17. +480
    -0
      src/views/AliPayIndex.vue
  18. +36
    -0
      src/views/AliPayRedirect.vue
  19. +169
    -0
      src/views/AliPayResult.vue
  20. +0
    -18
      src/views/Home.vue
  21. +32
    -0
      src/views/page-not-found/index.vue

+ 38
- 20
README.md Переглянути файл

@@ -1,34 +1,52 @@
<!--
* @Date: 2022-01-19 10:08:58
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-25 11:43:03
* @FilePath: \AntpayFrontEnd\README.md
* @description: readme说明文档
-->
# alipay-scan-code-front-end

## Project setup
## 项目说明
## 这是一个通过支付宝二维码进入的h5
## 项目依赖安装
```
npm install
```

### Compiles and hot-reloads for development
### 项目运行
```
npm run serve
npm run dev
```

### Compiles and minifies for production
### 项目打包
```
npm run build
```

### Run your tests
```
npm run test
```

### Lints and fixes files
```
npm run lint
```
### 项目代码编写规范(暂时想到这些,后面根据项目情况补充说明)
- css类名: 小写驼峰 中间用 - 隔开
- js函数方法: 开头小写后面开头大写驼峰
- .vue 文件命名: 统一大写驼峰

### Run your unit tests
```
npm run test:unit
```
### git 提交规范
- feature 新增一个功能
- bugfix 修复一个
- Bugdocs 文档变更
- style 代码格式(不影响功能,例如空格、分号等格式修正)
- refactor 代码重构
- perf 改善性能
- test 测试
- build 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等)
- ci 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等
- chore 变更构建流程或辅助工具
- revert 代码回退

### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
### 版本控制以及版本迭代说明
### v1.0.0
`2022.02.23`
build
- 初版发布
- 完成 项目搭建
- 完成 项目迁移 从documentFrontEndWeb 到 AlipayFrontendWeb
- 增加 docker部署脚本

+ 52
- 0
src/api/alipay.js Переглянути файл

@@ -0,0 +1,52 @@
/*
* @Date: 2022-02-14 15:18:50
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-14 15:21:05
* @FilePath: \alipay-scan-code-front-end\src\api\goods.js
* @description:
*/
import request from '../http/request';

export const APIAlipay = {
getGoodsDetails, //首页获取商品数据
getAlipayForm, // 调起支付宝收银台
getAlipayResult, //获取支付结果
};
export default APIAlipay;

// post请求
function getGoodsDetails(goodsNo) {
return request({
url: '/api/get/goods',
method: 'get',
params: {
goodsNo
},
});
}

function getAlipayForm(params) {
return request({
url: '/webpage/alipay',
method: 'post',
data: params,
});
}

function getAlipayResult(params) {
return request({
url: '/api/order/detail/callback',
method: 'post',
data: params,
});
}

// get请求 示例
/* function getPolygonFence(serialNo) {
return request({
url: '/api/Geofence/GetPolygonFence',
method: 'get',
headers: { AuthKey: 'key1' },
params: { serialNo },
})
} */

BIN
src/assets/alipay.png Переглянути файл

Before After
Width: 600  |  Height: 600  |  Size: 14KB

BIN
src/assets/antpay.png Переглянути файл

Before After
Width: 600  |  Height: 600  |  Size: 16KB

BIN
src/assets/banner_03.jpg Переглянути файл

Before After
Width: 480  |  Height: 420  |  Size: 206KB

+ 281
- 0
src/assets/reset.scss Переглянути файл

@@ -0,0 +1,281 @@
/* 初始化 */
a,
abbr,
acronym,
address,
applet,
area,
article,
aside,
audio,
b,
base,
basefont,
bdi,
bdo,
big,
blockquote,
body,
br,
button,
canvas,
caption,
center,
cite,
code,
col,
colgroup,
datalist,
dd,
del,
details,
dir,
div,
dfn,
dialog,
dl,
dt,
em,
embed,
fieldset,
figcaption,
figure,
font,
footer,
form,
frame,
frameset,
h1,
h2,
h3,
h4,
h5,
h6,
head,
header,
hr,
html,
i,
iframe,
img,
input,
ins,
isindex,
kbd,
keygen,
label,
legend,
li,
link,
map,
mark,
menu,
menuitem,
meta,
meter,
nav,
noscript,
object,
ol,
optgroup,
option,
output,
p,
param,
pre,
progress,
q,
rp,
rt,
ruby,
s,
samp,
script,
section,
select,
small,
source,
span,
strike,
strong,
style,
sub,
summary,
sup,
table,
tbody,
td,
textarea,
tfoot,
th,
thead,
time,
title,
tr,
track,
tt,
u,
ul,
var,
video,
wbr,
xmp {
box-sizing: border-box;
margin: 0;
padding: 0;
}
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font: 14px/1 "PingFang SC", "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img {
display: block;
border: none;
}
dl,
li,
menu,
ol,
ul {
list-style: none;
}
button,
input,
select,
textarea {
outline: none;
}
a,
a:link,
a:visited,
a:hover,
a:active {
text-decoration: none;
}
/* 浮动方式 */
.fl {
float: left;
}
.fr {
float: right;
}
.clear {
overflow: hidden;
clear: both;
height: 0;
font-size: 0;
}
.clearfix::after {
display: block;
visibility: hidden;
clear: both;
height: 0;
font-size: 0;
content: "";
}
/* 定位方式 */
.pr {
position: relative;
}
.pa {
position: absolute;
}
.pf {
position: fixed;
}
.center {
margin: 0 auto;
}
/* 对齐方式 */
.tal {
text-align: left;
}
.tac {
text-align: center;
}
.tar {
text-align: right;
}
.taj {
text-align: justify;
}
/* 居中定位 */
.abs-ct {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.abs-cx {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.abs-cy {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
/* 弹性布局 */
.flex-ct-x {
display: flex;
justify-content: center;
align-items: center;
}
.flex-ct-y {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.flex-fs {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
}
/* 动画模式 */
.td-camera {
perspective: 1000;
}
.td-space {
transform-style: preserve-3d;
}
.td-box {
backface-visibility: hidden;
}
.gpu-speed {
transform: translate3d(0, 0, 0);
}
/* 其他 */
.fullscreen {
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.page-at {
overflow: auto;
width: 100%;
height: 100%;
}
.page-fs {
overflow: hidden;
width: 100%;
height: 100%;
}
.round {
border-radius: 100%;
}

+ 0
- 122
src/components/HelloWorld.vue Переглянути файл

@@ -1,122 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target="_blank"
rel="noopener"
>babel</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest"
target="_blank"
rel="noopener"
>unit-jest</a
>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
>Forum</a
>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
>Community Chat</a
>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
>Twitter</a
>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
>vue-router</a
>
</li>
<li>
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-devtools#vue-devtools"
target="_blank"
rel="noopener"
>vue-devtools</a
>
</li>
<li>
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
>vue-loader</a
>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>awesome-vue</a
>
</li>
</ul>
</div>
</template>

<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

+ 13
- 0
src/config/models.js Переглянути файл

@@ -0,0 +1,13 @@
/*
* @Date: 2021-11-20 10:26:39
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-23 17:36:43
* @FilePath: \AlipayFrontEnd\src\config\models.js
* @description:
*/
export const VersionModel = '1.0.0';
export const ImageUrl = {
production: 'http://zfb.ssjlai.com/web/',
test: 'http://zfb.ssjlai.com/web/',
development: 'http://zfb.ssjlai.com/web/'
};

+ 21
- 0
src/http/request.js Переглянути файл

@@ -0,0 +1,21 @@
/*
* @Date: 2022-01-19 16:42:02
* @LastEditors: JinxChen
* @LastEditTime: 2022-01-19 16:43:19
* @FilePath: \alipay-scan-code-front-end\src\http\request.js
* @description: 封装axios
*/

import axios from 'axios'
const httpRequestUrl = process.env.VUE_APP_BASE_API;

// create an axios instance axios.create创建一个实例
const service = axios.create({
baseURL: httpRequestUrl,
/* baseURL: '172.16.255.35:8989', */
// timeout: 5000 // request timeout
});



export default service;

+ 56
- 0
src/main.js Переглянути файл

@@ -1,7 +1,63 @@
/*
* @Date: 2022-01-19 10:08:26
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-15 17:22:45
* @FilePath: \alipay-scan-code-front-end\src\main.js
* @description:
*/
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import './assets/reset.scss'; //配置全局清除样式
import {
Button,
Tab,
Tabs,
Image as VanImage,
Cell,
CellGroup,
RadioGroup,
Radio,
DropdownMenu,
DropdownItem,
Checkbox,
CheckboxGroup,
Col,
Row,
Dialog,
Card,
Form,
Field,
Sku,
Empty,
Notify,
Loading,
} from 'vant';

Vue
.use(Button)
.use(Tab)
.use(VanImage)
.use(Cell)
.use(CellGroup)
.use(RadioGroup)
.use(Radio)
.use(DropdownMenu)
.use(DropdownItem)
.use(Checkbox)
.use(CheckboxGroup)
.use(Col)
.use(Row)
.use(Dialog)
.use(Card)
.use(Form)
.use(Field)
.use(Sku)
.use(Empty)
.use(Notify)
.use(Loading)
.use(Tabs);

Vue.config.productionTip = false;



+ 23
- 15
src/router/index.js Переглянути файл

@@ -1,24 +1,25 @@
/*
* @Date: 2022-01-19 10:08:26
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-23 17:36:30
* @FilePath: \AlipayFrontEnd\src\router\index.js
* @description:
*/
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import Nprogress from "nprogress";
import "nprogress/nprogress.css";

Vue.use(VueRouter);

const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
{ path: '/', redirect: 'index' },
{ path: '/index', name: 'index', component: resolve => require(['@/views/AliPayIndex'], resolve) },
{ path: '/form', name: 'form', component: resolve => require(['@/views/AliPayForm'], resolve) },
{ path: '/redirect', name: 'redirect', component: resolve => require(['@/views/AliPayRedirect'], resolve) },
{ path: '/result', name: 'result', component: resolve => require(['@/views/AliPayResult'], resolve) },
{ path: '/404', name: 'page-not-found', component: resolve => require(['@/views/page-not-found/index'], resolve) },

];

const router = new VueRouter({
@@ -26,5 +27,12 @@ const router = new VueRouter({
base: process.env.BASE_URL,
routes,
});
router.beforeEach((to, form, next) => {
Nprogress.start();
next();
});
router.afterEach(() => {
Nprogress.done();
});

export default router;

+ 57
- 9
src/store/index.js Переглянути файл

@@ -1,11 +1,59 @@
import Vue from "vue";
import Vuex from "vuex";

/*
* @Date: 2022-01-19 10:08:26
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-14 15:29:30
* @FilePath: \alipay-scan-code-front-end\src\store\index.js
* @description: 配置store
*/
import Vue from 'vue';
import Vuex from 'vuex';
/* import prefix from '@/store/prefix'; */
import { isNotNull } from "../utils/index"
Vue.use(Vuex);

export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {},
});
state: {
goodsNo: '',
userId: '',
price: '',
count: '',
},
mutations: {
goodsNo(state, goodsNo) {
state.goodsNo = goodsNo;
window.localStorage[ 'goodsNo' ] = goodsNo;
},
userId(state, userId) {
state.userId = userId;
window.localStorage[ 'userId' ] = userId;
},
price(state, price) {
state.price = price;
window.localStorage[ 'price' ] = price;
},
count(state, count) {
state.count = count;
window.localStorage[ 'count' ] = count;
},
},
getters: {
goodsNo: state => {
if (isNotNull(state.goodsNo)) return state.goodsNo;
else return window.localStorage[ 'goodsNo' ] == null ? '' : window.localStorage[ 'goodsNo' ];
},
userId: state => {
if (isNotNull(state.userId)) return state.userId;
else return window.localStorage[ 'userId' ] == null ? '' : window.localStorage[ 'userId' ];
},
price: state => {
if (isNotNull(state.price)) return state.price;
else return window.localStorage[ 'price' ] == null ? '' : window.localStorage[ 'price' ];
},
count: state => {
if (isNotNull(state.count)) return state.count;
else return window.localStorage[ 'count' ] == null ? '' : window.localStorage[ 'count' ];
},
},

actions: {},
modules: {}
})

+ 9
- 0
src/store/prefix.js Переглянути файл

@@ -0,0 +1,9 @@
/*
* @Date: 2022-01-19 16:37:44
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-23 17:36:21
* @FilePath: \AlipayFrontEnd\src\store\prefix.js
* @description:
*/
const prefix = 'alipay_front_end_web_';
export default prefix;

+ 23
- 0
src/utils/index.js Переглянути файл

@@ -0,0 +1,23 @@


/**
* 判断是否为空
*/
export function isNull(o) {
if (o === null || o === undefined || o === '') {
return true;
// eslint-disable-next-line
} else if (Array.prototype.isPrototypeOf(o) && o.length === 0) {
return true;
// eslint-disable-next-line
} else if (Object.prototype.isPrototypeOf(o) && Object.keys(o).length === 0) {
return true;
}
return false;
}
/**
* 判断是否为非空
*/
export function isNotNull(o) {
return !isNull(o);
}

+ 0
- 5
src/views/About.vue Переглянути файл

@@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

+ 238
- 0
src/views/AliPayForm.vue Переглянути файл

@@ -0,0 +1,238 @@
<!--
* @Date: 2022-01-19 16:53:16
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-16 10:47:59
* @FilePath: \alipay-scan-code-front-end\src\views\AliPayForm.vue
* @description:
-->
<template>
<div class="form-container">
<!-- 头部 -->
<div class="form-header">
<div class="img-left">
<van-image :src="queryGoodsData.imageUrl" height="120"></van-image>
</div>
<div class="content-right">
<p><strong>套餐名字:</strong></p>
<p>{{queryGoodsData.goodName}}</p>
<p><strong>月套餐费:</strong></p>
<p><span>¥{{ (queryGoodsData.price/queryGoodsData.payCount).toFixed(2) }}</span> x <span>{{queryGoodsData.payCount}}期</span><span v-show="queryGoodsData.isAmountShow">=¥{{queryGoodsData.price}}</span></p>
</div>
</div>
<!-- 中部表单 -->
<div class="form-body">
<van-form @submit="onSubmit" @failed="onFailed">
<div class="form-box">
<van-field
v-model="form.phoneNumber"
name="phoneNumber"
label="电话"
type="tel"
placeholder="必填"
maxlength="11"
required
clearable
:rules="[{ required: true, message: '请填写电话' }]"
/>
<van-field
v-model="form.schoolName"
name="schoolName"
label="学校"
placeholder="必填"
maxlength="30"
required
clearable
:rules="[{ required: true, message: '请填写学校' }]"
/>
<van-field
v-model="form.className"
name="className"
label="班级"
placeholder="必填"
maxlength="10"
required
clearable
:rules="[{ required: true, message: '请填写班级' }]"
/>
<van-field
v-model="form.userName"
name="userName"
label="学生"
placeholder="必填"
maxlength="10"
required
clearable
:rules="[{ required: true, message: '请填写学生' }]"
/>
</div>
<!-- '请知悉' 提示, -->
<div class="know-tips">
<h5>请知悉!</h5>
<p>点击以下按钮,启动支付宝页面"立即支付"</p>
<p>成功后,将分期每月从你的余额扣{{ (queryGoodsData.price/queryGoodsData.payCount).toFixed(2) }}元</p>
</div>
<div class="form-footer">
<van-button type="info" block :disabled="false" native-type="onSubmit">办理支付宝分期业务</van-button>
</div>
</van-form>
</div>
<!-- 底部 -->
<!-- <div class="form-footer">
<van-button type="info" block :disabled="false" native-type="onSubmit">办理支付宝分期业务</van-button>
</div> -->
</div>
</template>

<script>
import { APIAlipay } from "../api/alipay";
export default {
name:'alipay-form',
data(){
return {
queryGoodsData: {
imageUrl: '',
price: '',
payCount: '',
isAmountShow: '',
goodName: '',

}, //接收从首页传过来的数据
form: {
phoneNumber: '',
schoolName: '',
className: '',
userName: '',
deviceImei: ''
}, //输入的表单数据
alipayForm: '', //接收支付宝接口返回的表单
}
},
mounted() {
this.getQueryParams();
},
methods: {
//获取并拼接从首页传过来的数据
getQueryParams() {
let queryData = this.$route.query;
console.log("queryData", queryData);
this.queryGoodsData.price = Number(queryData.price);
this.queryGoodsData.payCount = Number(queryData.payCount);
this.queryGoodsData.imageUrl = queryData.imageUrl;
this.queryGoodsData.isAmountShow = JSON.parse(queryData.isAmountShow);
this.queryGoodsData.goodName = queryData.goodName;
},
// 办理支付宝分期业务表单校验通过后
onSubmit(values) {
let reg = /^1[3456789]\d{9}$/;
if(!reg.test(values.phoneNumber)) {
this.$notify({
message: '请您输入正确的手机号码!',
type: 'warning',
duration: 1500
})
} else {
let reqBody = {
username: this.form.userName,
deviceImei: this.form.deviceImei === '' ? '123' : this.form.deviceImei,
schoolName: this.form.schoolName,
className: this.form.className,
phoneNumber: this.form.phoneNumber,
periodizationNum: Number(this.queryGoodsData.payCount),
goodsNo: this.$store.getters.goodsNo,
alipayStateType: Number(this.queryGoodsData.payType),
userId: this.$store.getters.userId,
}
APIAlipay.getAlipayForm(reqBody)
.then(res => {
this.alipayForm = res.data;
this.$store.commit('price', Number(this.queryGoodsData.price));
this.$store.commit('count', Number(this.queryGoodsData.payCount));
// 跳转到一个中转页 form,再通过这个中转页来调起支付宝的收银台
this.$router.push({path: 'redirect', query: {alipayForm: this.alipayForm}});
})
.catch(error => {
console.log(error);
})
}
},
// 表单不通过时
onFailed() {
console.log("表单输入不完整!");
}
}
}
</script>

<style scoped lang="scss">
.form-container {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
overflow: hidden;
/* padding: 5px;
border-radius: 20px; */
background-color: #f7f8fa;
.form-header {
height: 25vh;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
background-color: rgba(235, 233, 229, 0.726);
.img-left {
width: 120px;
}
.content-right {
flex: 1;
p {
font-size: 14px;
text-align: left;
padding: 5px 10px;
strong {
text-align: left;
}
}
}
}
.form-body {
flex: 1;
position: relative;
overflow: scroll;
/* padding: 10px; */
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
.know-tips {
height: 120px;
width: 100%;
padding: 10px;
h5 {
font-size: 16px;
text-align: left;
padding: 5px;
color: red;
}
p {
font-size: 14px;
padding: 5px;
text-align: left;
}
}

}
.form-footer {
height: 10vh;
width: 100%;
position: absolute;
padding: 10px;
bottom: 10px;
left: 0;
display: flex;
justify-content: center;
align-items: center;
.van-button {
box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
}
}
}
</style>

+ 480
- 0
src/views/AliPayIndex.vue Переглянути файл

@@ -0,0 +1,480 @@
<!--
* @Date: 2022-01-19 16:52:21
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-16 11:05:34
* @FilePath: \alipay-scan-code-front-end\src\views\AliPayIndex.vue
* @description:
-->
<template>
<div class="index-container">
<!-- 头部banner -->
<div class="index-banner">
<img class="img-contanier" :src="goodsData.imgPath"/>
</div>
<!-- 中间内容 -->
<div class="index-body">
<p class="set-meal-title">月套餐费: <span class="set-meal-price">¥{{ (goodsData.price/goodsData.goodCount).toFixed(2) }}</span> x <span>{{goodsData.goodCount}}期</span> <span v-show='isAmountShow'> = ¥{{goodsData.price}}</span></p>
<p class="index-tips">可切换支付方式:</p>
<!-- 花呗or支付宝 -->
<van-radio-group class="radio-group" v-model="radio" @change="onRadioChange">
<van-cell-group>
<!-- 支付宝 -->
<van-cell title="" clickable @click="radio = '1'" v-show="isAlipayShow">
<template #icon>
<img :src="alipayIconPath" alt="" class="pay-icon">
</template>
<template #right-icon>
<span class="radio-group-span">支付宝</span>
<van-radio name="1" />
</template>
</van-cell>
<!-- 花呗 -->
<van-cell title="" clickable @click="radio = '2'">
<template #icon>
<img :src="antpayIconpath" alt="" class="pay-icon">
</template>
<template #right-icon>
<span class="radio-group-span">花呗<label>(推荐)</label></span>
<van-radio name="2" />
</template>
</van-cell>
<!-- 花呗分期 -->
<div class="antpay-container" v-show="isAntpay">
<van-row>
<van-col span="6" class="antpay-row-col-6"><p>花呗分期:</p></van-col>
<van-col span="18" class="antpay-row-col-18">
<van-radio-group v-model="antPayRadio" @change="onChange">
<van-button type="default" size="mini" v-for="(item) in countList" :key="item.value">
<van-radio :name="item.value">
<p>分{{item.value}}期</p>
<p>每期{{ (goodsData.price/item.value).toFixed(2) }}元</p>
</van-radio>
</van-button>
</van-radio-group>
</van-col>
</van-row>
</div>
</van-cell-group>
</van-radio-group>
<!-- 我要开通花呗 -->
<p class="open-antpay" @click="onOpenAntpay">我要开通花呗</p>
<!-- 协议 -->
<p class="agreement" @click="onOpenAgreement">{{agreement.agreementTitle}}</p>
<!-- 协议内容 -->
<van-dialog
class="agreement-container"
v-model="agreement.isAgreementShow"
:title="agreement.agreementTitle"
confirm-button-color="#1989fa"
:show-confirm-button="false"
>
<div class="agreement-content">
<h5>一.开通需知及承诺</h5>
<p v-show="isNewCustom">1.感谢用户使用支付宝花呗分期功能。</p>
<p v-show="isNewCustom">2.用户自愿开通4G电子学生证资费套餐服务({{goodsData.goodCount}}<!-- 24 -->期)每月自动通过支付宝花呗分期支付资费(话费+流量)套餐费{{(goodsData.price/goodsData.goodCount).toFixed(2)}}元/月。</p>
<p v-show="isNewCustom">3.用户需连续使用该资费套餐24期,可据此领取4G电子学生证1台、电话卡1张。</p>
<p style="white-space: pre-wrap"><span v-show="isNewCustom">4.</span>{{goodsData.goodDescription}}</p>
<p v-show="isOldCustom">5.协议期内,电子学生证非人为损坏或进水按“三包”政策进行免费维修或更换。如丢失或人为损坏,按优惠价重新购买。</p>
<p v-show="isOldCustom">6.北京随手精灵科技有限公司接受电子学生证运营销售商委托采用支付宝花呗分期付代收业务货款。</p>
<h5>二.用户承诺</h5>
<p>1.用户已充分了解支付宝花呗付款条款(含支付宝花呗相关规则),充分阅读及理解本协议。</p>
<p>2.用户已经签署业务订购回执,自愿购买并同意签署本协议。</p>
<h5>三.法律适用与管辖</h5>
<p>本协议之效力、解释、变更、执行与争议解决均适用中华人民共和国法律。因本协议产生的争议,均应依照中华人民共和国法律予以处理。</p>
<p v-show="!agreement.isButtonShow">{{agreement.countDown}}秒后显示按钮</p>
<div class="agreement-button" v-show="agreement.isButtonShow">
<van-button type="warning" @click="onDisAgree" round>不同意</van-button>
<van-button type="info" @click="onAgree" round>同意</van-button>
</div>
</div>
</van-dialog>
</div>
<!-- 底部 -->
<div class="index-footer-out">
<div class="index-footer">
<div class="checkbox-container">
<van-checkbox v-model="checked" shape="square" @change="onCheckChange"><strong>已阅读协议并确认</strong></van-checkbox>
</div>
<div class="footer-button">
<van-button :class="['van-button', {notSubmit: !checked}]" block @click="onSubmit">一键下单</van-button>
</div>
</div>
</div>
</div>
</template>

<script>
import { isNotNull } from "../utils/index";
import { APIAlipay } from "../api/alipay";
import { ImageUrl} from '../config/models';
export default {
name:'alipay-index',
data(){
return {
// 商品数据
goodsData: {
imgPath: null /* || require('../assets/banner_03.jpg') */, //首页图片路径
price: null, //价格
goodCount: null, //分期数
goodDefCount: null, //默认分期数
amount: 480, //总数
isOld: null, //是否是老用户
goodDescription: null, //商品描述
defGoodDescription: '电子学生证套餐两年内(24个月)内含每月200分钟通话和1.5G流量,超出通话或流量部分,由用户自行交费。', //默认协议内容
payType: null, //支付类型, 2是花呗, 1 是支付宝
goodName: null, //商品名称
},
radio: '2', //单选框的值,默认是 '2', 2是花呗, 1支付宝
antPayRadio: '', //花呗分期,默认24期
antpayIconpath: require('../assets/antpay.png'), //花呗支付图片
alipayIconPath: require('../assets/alipay.png'), //支付宝图片
checked: false, //是否已经勾选协议
isAlipayShow: false, //是否显示支付宝选项
goodsNo: '', //商品id
countList: null, //分期数数组
isAmountShow: true, //是否显示总数, 默认显示,部分客户不需要显示则false
isAntpay: true, //单选是否是花呗, 默认是花呗,否则是支付宝
agreement: {
agreementTitle: '4G电子学生证资费套餐开通服务协议', //协议标题
isAgreementShow: false, //是否显示协议内容,默认不显示
isButtonShow: false, //是否显示按钮
timer: '', //定义一个倒计时
countDown: 3, //同意/不同意按钮显示倒计时
},
isNewCustom: true, //是否是新用户
isOldCustom: false, //是否是新用户
checkCount: 0, //首次点击同意协议单选框

}
},
mounted() {
this.storeQueryParams();
this.getGoodsDetails();
},
methods: {

// 缓存通过扫码得到的商品id
storeQueryParams() {
let params = this.$route.query;
this.initDealContent(params.goodsNo);
console.log("扫码传过来的参数", params);
if(isNotNull(params.goodsNo)) {
this.goodsNo = params.goodsNo;
this.$store.commit('goodsNo', params.goodsNo);
this.$store.commit('userId', params.userId);
} else {
this.$router.push({path: '/404'})
}
},
// 初始化协议内容,根据不同客户的商品id显示不同的协议内容
initDealContent(goodsNo) {
if(goodsNo === '1452515524181975040') {
this.isAlipayShow = true;
} else if(goodsNo === '1462964210433548288' || goodsNo === '1472882092902715392' || goodsNo === '1479390914305277952') {
this.isAmountShow = false;
} else if (goodsNo === '1483687825375969280') { //翼校云定制的商品id
this.agreement.agreementTitle = '电子学生证AI套餐开通服务协议';
this.isNewCustom = false;
this.isAmountShow = true;
}else {
this.isAmountShow = true;
}
},
// 根据商品id获取商品详情
getGoodsDetails() {
APIAlipay.getGoodsDetails(this.goodsNo)
.then(res => {
if(res.data.code === 20000) {
let good = res.data.data.goods;
console.log("good", good);
// 图片路径
this.goodsData.imgPath = ImageUrl[ process.env.NODE_ENV ] + good.goodsImg;
// 价格
this.goodsData.price = good.price;
// 是否是老用户
this.isOld = good.descriptionText === '' ? true : false;
// 商品名称
this.goodsData.goodName = good.mainTitle;
//商品描述 根据\n换行符来把数据进行换行
this.goodsData.goodDescription = good.descriptionText === '' ? this.goodsData.defGoodDescription : good.descriptionText.replace(/\\n/g,"<br/>");
// 分期数
let goodCount = good.periodizationJson.substring(1, good.periodizationJson.length - 1).split(',');
this.goodsData.goodCount = Math.max(...goodCount);
this.goodsData.goodDefCount = Math.max(...goodCount).toString();
this.antPayRadio = Math.max(...goodCount).toString();
this.countList = goodCount.map(g => {
return {
value: g,
label: g
}
}).reverse();
console.log("this.countList", this.countList);
} else {
this.$notify({
message: '系统出现错误,请联系管理员!',
type: 'warning',
duration: 1000
});
}
})
},
// 单选支付宝花呗发生变化时
onRadioChange(values) {
this.isAntpay = values === '2' ? true : false;
this.radio = values;
this.goodsData.payType = Number(values);
},
// 选择分期数改变时
onChange(values) {
console.log("分期数改变", values);
this.goodsData.goodCount = values;
},
// 我要开通花呗弹窗
onOpenAntpay() {
this.$dialog.confirm({
title: '如何开通花呗?',
message: `
<p>进入支付宝APP-右下角【我的】-【花呗】,点击后按页面提示操作开通即可。</p>
`,
showCancelButton: false,
messageAlign: 'center',
confirmButtonColor: '#1989fa'
})
},
// 打开协议
onOpenAgreement() {
this.agreement.isAgreementShow = true;
this.checkCount ++;
if(this.checkCount <= 1) {
this.agreement.timer = setInterval(() =>{
this.agreement.countDown --;
if(this.agreement.countDown === 0) {
this.agreement.isButtonShow = true;
clearInterval(this.agreement.timer);
this.agreement.countDown = 3;
}
},1000)
} else if(this.checkCount >= 2){
this.agreement.isButtonShow = true;
}
},
// 复选框绑定值变化时
onCheckChange() {
if(this.checkCount < 1) {
// 首次选择复选框自动打开协议
this.onOpenAgreement();
this.checkCount ++;
} else if (this.checkCount >= 2) {
this.agreement.isButtonShow = true;
}
},

// 不同意
onDisAgree() {
this.checked = false;
this.resetCountDown();
},
// 同意
onAgree() {
this.checked = true;
this.resetCountDown();
},
// 重置倒计时和关闭弹窗
resetCountDown() {
this.agreement.isAgreementShow = false;
this.agreement.isButtonShow = false;
clearInterval(this.agreement.timer);
this.agreement.countDown = 3;
},

// 一键下单提交按钮
onSubmit() {
if(this.checked === false) {
this.$notify({
message: `请阅读并勾选同意${this.agreement.agreementTitle}`,
type: 'warning',
duration: 1500
})
} else {
this.$router.push({
path: 'form' ,
query: {
payType: Number(this.radio),
price: this.goodsData.price,
payCount: this.goodsData.goodCount,
goodName: this.goodsData.goodName,
isAmountShow: this.isAmountShow,
imageUrl: this.goodsData.imgPath
}
})
}
}
}
}
</script>

<style scoped lang="scss">
.index-container {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: rgba(241, 241, 245, 0.932) 0px 30px 60px -12px inset, rgba(239, 242, 243, 0.884) 0px 18px 36px -18px inset;
.index-banner{
height: 40vh;
width: 100vw;
padding: 5px 5px 0 5px;
.img-contanier {
height: calc(40vh - 5px);
width: calc(100vw - 10px);
}
}
.index-body {
flex: 1;
margin: 0 5px 5px 5px;
padding: 0 10px 10px 10px;
overflow: scroll;
box-shadow: rgba(0, 0, 0, 0.09) 0px 2px 1px, rgba(0, 0, 0, 0.09) 0px 4px 2px, rgba(0, 0, 0, 0.09) 0px 8px 4px, rgba(0, 0, 0, 0.09) 0px 16px 8px, rgba(0, 0, 0, 0.09) 0px 32px 16px;
.set-meal-title {
font-size: 16px;
text-align: left;
padding: 5px 0 5px 0;
.set-meal-price {
font-size: 24px;
color: red;
}
}
.index-tips {
font-size: 16px;
text-align: left;
margin: 5px 0;
}
.van-cell {
height: 45px;
line-height: 45px;
padding: 0;
border: 0.5px solid #dcdee2;
border-radius: 10px;
margin-top: 10px;
}
.radio-group {
.radio-group-span {
padding-right: 5px;
label {
color: red;
}
}
.antpay-container {
height: 50px;
line-height: 50px;
/* padding: 5px 0; */
background-color: white;
}
.van-row, .antpay-row-col-18{
height: 50px;
line-height: 50px;
.antpay-row-col-6 {
p {
text-align: left;
font-size: 16px;
}
}
.antpay-row-col-18 {
display: flex;
/* justify-content: center;
align-items: center; */
line-height: 40px;
.van-radio-group {
overflow-x: auto;
white-space: nowrap;
}
.van-radio-group::-webkit-scrollbar {
display: none;
}
.van-button {
height: 40px;
width: 120px;
display: inline-block;
border-radius: 5px;
}
}
}
}
.open-antpay, .agreement {
height: 20px;
line-height: 20px;
text-decoration:underline;
text-align: left;
font-size: 14px;
margin: 5px 0px;
}
.open-antpay {
color: red;
}
.agreement {
margin: 5px 0px;
}
.agreement-container {
.agreement-content {
height: 500px;
padding: 5px 10px;
border-top: .5px soild;
border-bottom: .5px soild;
text-align: left;
overflow: scroll;
h5 {
padding: 5px;
}
p {
padding: 5px;
font-size: 14px;
line-height: 20px;

}
.agreement-button {
padding: 10px;
display: flex;
justify-content: space-around;
align-items: center;
.van-button {
height: 30px;
width: 120px;
border-radius: 10px;
}
}
}
}
.pay-icon {
height: 30px;
width: 30px;
margin: 5px;
}
}
.index-footer-out, .index-footer {
height: 15vh;
width: 100vw;
padding: 10px 10px ;
.index-footer {
position: absolute;
left: 0;
bottom: 0;
z-index: 999;
.checkbox-container {
margin: 5px 0;
}
.footer-button {
/* box-shadow: rgb(217, 218, 219) 0 20px 30px -15px; */
.van-button {
background-color: #1989fa;
color: white;
border-radius: 5px;
&.notSubmit {
background-color: rgba(150, 150, 146, 0.904);
}
}
}
}
}
}
</style>

+ 36
- 0
src/views/AliPayRedirect.vue Переглянути файл

@@ -0,0 +1,36 @@
<!--
* @Date: 2022-01-19 16:54:08
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-15 16:05:25
* @FilePath: \alipay-scan-code-front-end\src\views\AliPayRedirect.vue
* @description:
-->
<template>
<div></div>
</template>

<script>
export default {
name:'alipay-redirect',
data(){
return {
queryAlipayForm: this.$route.query.alipayForm, //接收上一个页面传过来的支付宝表单文件
}
},
mounted() {
this.createAlipayPage();
},
methods: {
createAlipayPage() {
const div = document.createElement('div'); //在当前页面创建一个div的节点
div.innerHTML = this.queryAlipayForm; //将支付宝的form表单数据添加到div里面
document.body.appendChild(div); //把这个节点添加到页面的body里面去
document.forms[0].submit(); //自动调用form表单的submit方法正式调起支付宝的收银台界面
}
}
}
</script>

<style scoped lang="scss">

</style>

+ 169
- 0
src/views/AliPayResult.vue Переглянути файл

@@ -0,0 +1,169 @@
<!--
* @Date: 2022-01-19 16:55:03
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-16 10:46:46
* @FilePath: \alipay-scan-code-front-end\src\views\AliPayResult.vue
* @description:
-->
<template>
<div class="pay-result-container">
<!-- 接口轮询时加载动画以及空状态 -->
<van-empty v-show="isResultBack" image="https://img01.yzcdn.cn/vant/custom-empty-image.png" />
<van-loading v-show="isResultBack" type="spinner" >数据加载中...</van-loading>
<!-- 接口轮询完成后 -->
<div class="pay-result-content" v-show="!isResultBack">
<div class="result-header">
<h5 v-if="isPaySuccess">{{ checkSuccess.tips }}</h5>
<h5 v-if="isPaySuccess">{{ checkSuccess.title }}</h5>
<h5 v-if="!isPaySuccess">{{checkFail.tips}}</h5>
<p v-if="isPaySuccess">接下来将按分期每月从你的支付宝余额扣{{(price/count).toFixed(2)}}元。</p>
<p>{{isPaySuccess ? checkSuccess.content : checkFail.content}}</p>
</div>
<div class="result-footer">
<p>商家订单号: </p>
<p>{{alipayOrder.outradeNo}}</p>
<p>请保存好该订单号截图,方便售后服务。</p>

</div>
</div>

</div>
</template>

<script>
import { isNotNull } from "../utils/index";
import { APIAlipay } from "../api/alipay";
export default {
name:'alipay-result',
data(){
return {
alipayOrder: {
outradeNo: '',
tradeNo: '',
}, //支付宝返回的订单号
timer: "", //计时器
num: 1, //轮询次数
price: this.$store.getters.price,
count: this.$store.getters.count,
isPaySuccess: null, //是否支付成功
isResultBack: true, //是否有结果返回
checkSuccess: {
tips: "恭喜你!",
title: '支付宝分期业务办理成功!',
content: "可进入支付宝-->我的-->花呗-->我的账单,查询应还、或提前还款。"
}, //查询支付结果成功时
checkFail: {
tips: "查询交易信息失败!",
content: "请进入支付宝-->我的-->花呗-->我的账单,查询应还、或提前还款。"
}, //查询支付结果失败时

}
},
mounted() {
this.getParamsData();
this.closeTime();
},
methods: {
// 获取url拼接的params
getParamsData() {
let params = this.$route.query;
console.log("支付宝返回的信息", params);
if (isNotNull(params)) {
this.alipayOrder.outradeNo = params.out_trade_no;
this.alipayOrder.tradeNo = params.trade_no;
this.getPayResult();
}
},
// 根据订单号轮询获取订单结果和状态
getPayResult() {
let that = this;
let reqBody = {
outTradeNo: this.alipayOrder.outradeNo,
tradeNo: this.alipayOrder.tradeNo
};
if(that.num > 30 ) {
if(that.timer){
clearInterval(that.timer);
}
} else {
that.timer = setInterval(() => {
that.num ++;
APIAlipay.getAlipayResult(reqBody)
.then(res => {
if(res.data.code === 20000) {
this.isResultBack = false;
this.isPaySuccess = true;
if(that.timer) {
clearInterval(that.timer)
}
}
}).catch(e => {
console.log(e)
})
}, 2000)
}
},
// 30秒后关闭轮询
closeTime() {
setTimeout(() => {
clearInterval(this.timer);
console.log("即将关闭轮询");
if(this.isPaySuccess === null && this.isResultBack === true) {
this.isResultBack = false;
this.isPaySuccess = false;
}
}, 30000);
},
}
}
</script>

<style scoped lang="scss">
.pay-result-container {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
/* background-color: whitesmoke; */
.pay-result-content {
height: 100vh;
width: 100vw;
.result-header, .result-footer {
display: flex;
justify-content: center;
/* align-items: center; */
flex-direction: column;
padding: 30px;
text-align: left;
}
.result-header {
height: 50vh;
background-color: #f2f2f2;
h5 {
color: red;
font-size: 26px;
padding: 10px;
font-weight: bold;
}
p {
font-size: 16px;
padding: 10px;
}
}
.result-footer {
height: 50vh;
p {
font-size: 16px;
padding: 10px;
}
p:nth-child(2) {
padding: 20px;
color: red;
background-color: antiquewhite;
}
}
}
}
</style>

+ 0
- 18
src/views/Home.vue Переглянути файл

@@ -1,18 +0,0 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";

export default {
name: "Home",
components: {
HelloWorld,
},
};
</script>

+ 32
- 0
src/views/page-not-found/index.vue Переглянути файл

@@ -0,0 +1,32 @@
<!--
* @Date: 2022-02-14 15:34:10
* @LastEditors: JinxChen
* @LastEditTime: 2022-02-14 15:39:24
* @FilePath: \alipay-scan-code-front-end\src\views\page-not-found\index.vue
* @description: 404
-->
<template>
<div class="page-not-found">
<van-empty description="扫码出现错误!请您重新扫码!" />
</div>
</template>

<script>
export default {
name:'page-not-found',
data(){
return {

}
}
}
</script>

<style scoped lang="scss">
.page-not-found {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>

Завантаження…
Відмінити
Зберегти