Commit e0526383 by 姜雷

添加页面逻辑

parent 282f85f1
......@@ -31,30 +31,31 @@
"@tarojs/taro-swan": "1.2.13",
"@tarojs/taro-tt": "1.2.13",
"@tarojs/taro-weapp": "1.2.13",
"nervjs": "^1.3.9",
"crypto-js": "^3.1.9-1",
"nerv-devtools": "^1.3.9",
"nervjs": "^1.3.9",
"redux": "^4.0.0",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"@types/react": "^16.4.8",
"@types/webpack-env": "^1.13.6",
"@tarojs/plugin-babel": "1.2.13",
"@tarojs/plugin-csso": "1.2.13",
"@tarojs/plugin-sass": "1.2.13",
"@tarojs/plugin-uglifyjs": "1.2.13",
"@tarojs/webpack-runner": "1.2.13",
"@types/react": "^16.4.8",
"@types/webpack-env": "^1.13.6",
"babel-eslint": "^8.2.3",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-jsx-stylesheet": "^0.6.5",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-eslint": "^8.2.3",
"eslint": "^4.19.1",
"eslint-config-taro": "1.2.13",
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-taro": "1.2.13",
"eslint-plugin-typescript": "^0.12.0",
"typescript": "^3.0.1"
......
......@@ -2,7 +2,7 @@
"miniprogramRoot": "./dist",
"projectname": "wx-school-app-public",
"description": "",
"appid": "touristappid",
"appid": "wx99b75b4bdb31b3c3",
"setting": {
"urlCheck": true,
"es6": false,
......
import {
ADD,
MINUS
} from '../constants/counter'
export const add = () => {
return {
type: ADD
}
}
export const minus = () => {
return {
type: MINUS
}
}
// 异步的action
export function asyncAdd () {
return dispatch => {
setTimeout(() => {
dispatch(add())
}, 2000)
}
}
import { baseFetch, ResponseDataEntity } from '..';
type Params = {
equipmentNum: string;
// 设备编码
equipmentPos: string;
// 设备位置
payType: string;
// 支付方式
serviceId: number;
// 服务id
smaproCustomerId: number;
// 会员id
smaproPrepayConfigId: number;
// 预付配置id
};
type ResponseData = {
outTradeNo: string;
// 商户交易订单号
payStr: string;
// 微信小程序端支付信息
};
export const getPayOrder = (
entity: Params,
): Promise<ResponseDataEntity<ResponseData>> =>
baseFetch({
url: '/smapro/prepay/order/wx/apply/pay',
method: 'POST',
data: entity,
});
import { ResponseDataEntity, baseFetch } from '..';
type Params = {
orderId: string;
};
export const paySuccess = (entity: Params): Promise<ResponseDataEntity<null>> =>
baseFetch({
url: '/smapro/prepay/order/paySuccess',
method: 'POST',
data: entity,
});
import { baseFetch, ResponseDataEntity } from '.';
type Params = {
/** 登录code */
code: string;
/** 用户昵称 */
userName?: string;
};
export const registerAndLogin = (
entity: Params,
): Promise<ResponseDataEntity<null>> =>
baseFetch({
url: '/smapro/customer/wxmini/registerandlogin',
method: 'POST',
data: entity,
});
import { baseFetch, ResponseDataEntity } from '.';
type Params = {
customerId: number;
equipmentNum: string;
serviceId: number;
};
type DeviceInfoResponse = {
code: string;
// 设备编码
isCurrentUserUsed: number;
// 是否当前人使用 1:是,0:否
isOnline: number;
// 是否上线 1:是,0:否
isUsed: number;
// 是否被使用 1:是,0:否
operatorId: number;
// 运营商Id
operatorName: string;
// 运营商名称
position: string;
// 位置名称
positionId: string;
// 位置id
rates: RateInfoResponse[];
};
type RateInfoResponse = {
mark: string;
// 费率描述
name: string;
// 费率名称
};
type SmaproPrepayConfig = {
createAt: string;
// 创建时间
id: number;
// 会员id
isOpen: number;
// 是否开启
operateId: number;
// 运营商id
operateName: string;
// 运营商名称
prepayMoney: number;
// 预付金额
serviceId: number;
// 服务类型id
serviceName: string;
// 服务名称
sortId: number;
// 序号
updateAt: string;
// 更新时间
};
type ResponseData = {
deviceInfoResponse: DeviceInfoResponse;
prepayConfigs: SmaproPrepayConfig[];
};
export const getDeviceConfig = (
entity: Params,
): Promise<ResponseDataEntity<ResponseData>> =>
baseFetch({
url: '/smapro/prepay/config/wxmini',
method: 'POST',
data: entity,
});
import Taro, { request } from '@tarojs/taro';
import store from '../store/index';
import { BASE_SERVER_URL, LogoutCode, SuccessCode } from '../constants/index';
export type ResponseDataEntity<T> = {
code: number;
msg: string;
data: T;
[propName: string]: any;
};
type ResponseEntity = {
data: ResponseDataEntity<any>;
[propName: string]: any;
};
const createFetch = (basePath: string) => {
return (entity: request.Param) => {
const token = store.getState().userinfo.token;
return Taro.request({
...entity,
header: token
? {
token: token,
reqSource: 'wxmini',
}
: {
reqSource: 'wxmini',
},
url: basePath + entity.url,
}).then(({ data }: ResponseEntity) => {
if (data.code === SuccessCode) {
return data;
}
if (data.code === LogoutCode) {
Taro.redirectTo({
url: '/pages/Login/Login',
});
throw data;
}
Taro.showToast({
title: data.msg || '网络错误',
icon: 'none',
});
throw data;
});
};
};
export const baseFetch = createFetch(BASE_SERVER_URL);
export default createFetch;
......@@ -4,7 +4,7 @@ import { Provider } from '@tarojs/redux';
import Index from './pages/index';
import configStore from './store';
import store from './store';
import './app.scss';
......@@ -14,8 +14,6 @@ import './app.scss';
// require('nerv-devtools')
// }
const store = configStore();
class App extends Component {
/**
* 指定config的类型声明为: Taro.Config
......@@ -26,8 +24,8 @@ class App extends Component {
*/
config: Config = {
pages: [
'pages/pay/pay',
'pages/index/index',
'pages/pay/pay',
],
window: {
backgroundTextStyle: 'light',
......
export const ADD = 'ADD'
export const MINUS = 'MINUS'
export const SuccessCode = 1000;
export const LogoutCode = -2;
export const NotRegisterCode = 1005;
export const BASE_SERVER_URL = 'https://ex-dev-dcxy-smapro-app.168cad.top';
.index {
.scan-icon {
position: relative;
padding: 0;
background-color: #fff;
border-color: #fff;
width: 460px;
height: 460px;
margin: 238px auto 168px;
image {
position: absolute;
background-color: #fff;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 2;
}
}
.scan-text {
......
import { ComponentClass } from 'react';
import Taro, { Component, Config } from '@tarojs/taro';
import { View, Image, Text } from '@tarojs/components';
import { connect } from '@tarojs/redux';
import { View, Image } from '@tarojs/components';
import { add, minus, asyncAdd } from '../../actions/counter';
import scanIcon from '../../images/icon_s@2x.png';
import './index.scss';
import { registerAndLogin } from '../../api/customer';
import { getDeviceConfig } from '../../api/device';
import { UserState, updateUserInfo } from '../../store/rootReducers/userinfo';
import { connect } from '@tarojs/redux';
import { Customer } from '../../types/Customer/Customer';
import { DeviceState, updateDeviceData } from '../../store/rootReducers/device';
import PrepayConfig from '../../types/Order/Order';
import { updatePayData } from '../../store/rootReducers/prepayConfig';
// #region 书写注意
//
// 目前 typescript 版本还无法在装饰器模式下将 Props 注入到 Taro.Component 中的 props 属性
// 需要显示声明 connect 的参数类型并通过 interface 的方式指定 Taro.Component 子类的 props
// 这样才能完成类型检查和 IDE 的自动提示
// 使用函数模式则无此限制
// ref: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20796
//
// #endregion
type DeviceEntity = {
serviceId: number;
equipmentNum: string;
};
type PageStateProps = {
counter: {
num: number;
};
userinfo: Customer;
};
type PageDispatchProps = {
add: () => void;
dec: () => void;
asyncAdd: () => any;
updateUserInfo: (e: UserState) => void;
updateDeviceData: (e: DeviceState) => void;
updatePayData: (e: PrepayConfig[]) => void;
};
type PageOwnProps = {};
......@@ -41,42 +40,155 @@ interface Index {
}
@connect(
({ counter }) => ({
counter,
({ userinfo }) => ({
userinfo,
}),
dispatch => ({
add() {
dispatch(add());
updateUserInfo(data: UserState) {
dispatch(updateUserInfo(data));
},
dec() {
dispatch(minus());
updateDeviceData(data: DeviceState) {
dispatch(updateDeviceData(data));
},
asyncAdd() {
dispatch(asyncAdd());
updatePayData(data: PrepayConfig[]) {
dispatch(updatePayData(data));
},
}),
)
class Index extends Component {
/**
* 指定config的类型声明为: Taro.Config
*
* 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型
* 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string
* 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型
*/
config: Config = {
navigationBarTitleText: '多彩自助服务',
};
clickHandle() {
Taro.navigateTo({
url: '/pages/pay/pay',
componentWillMount() {
this.loginHandle().then(() => {
const { userinfo } = this.props;
let queryArr = Object.keys(this.$router.params);
let scene: string = queryArr.length > 0 ? queryArr[0] : '';
console.log('scene:', scene);
if (scene) {
let entity = this.getDeviceEntity(scene);
if (entity) {
getDeviceConfig({
customerId: userinfo.customerId,
...entity,
})
.then(res => {
console.log(res);
})
.catch(err => {
console.error(err);
Taro.showToast({
title: err.msg || '设备码有误',
icon: 'none',
});
});
}
}
});
}
getDeviceEntity(paramStr: string): DeviceEntity {
let serviceId = Number(paramStr.slice(0, 2));
let equipmentNum = paramStr.slice(2);
return {
serviceId,
equipmentNum,
};
}
loginHandle() {
const { updateUserInfo } = this.props;
return Taro.login().then(res => {
const { code } = res;
return registerAndLogin({
code,
// userName: '',
}).then(res => {
const { token, customerId } = res;
updateUserInfo({
token,
customerId,
});
});
});
}
scanHandle() {
const { userinfo, updateDeviceData, updatePayData } = this.props;
Taro.scanCode({
onlyFromCamera: true,
scanType: ['qrCode'],
})
.then(res => {
const { path, result } = res;
if (path) {
let queryArr = path.split('?');
let queryStr = queryArr.length >= 2 ? queryArr[1] : '';
console.log(result, queryArr, queryStr);
const { serviceId, equipmentNum } = this.getDeviceEntity(queryStr);
getDeviceConfig({
customerId: userinfo.customerId,
equipmentNum: equipmentNum,
serviceId: serviceId,
})
.then(res => {
console.log(res);
const { deviceInfoResponse, prepayConfigs } = res.data;
if (deviceInfoResponse.isUsed) {
console.log('设备使用中');
return;
}
updateDeviceData(deviceInfoResponse);
updatePayData(prepayConfigs);
Taro.navigateTo({
url: '/pages/pay/pay',
});
})
.catch(err => {
console.error(err);
Taro.showToast({
title: err.msg || '请扫描正确的设备码',
icon: 'none',
});
});
} else {
Taro.showToast({
title: '请扫描正确的设备码',
icon: 'none',
});
}
})
.catch(err => {
console.error(err);
});
}
getUserInfoHandle(res) {
console.log(res);
const { detail } = res;
if (detail.userInfo) {
// this.scanHandle(detail.userInfo);
}
}
clickHandle() {
this.scanHandle();
}
render() {
return (
<View className='index'>
{/* <Button
className='scan-icon'
onGetUserInfo={this.getUserInfoHandle}
open-type='getUserInfo'>
<Image src={scanIcon} />
</Button> */}
<View className='scan-icon' onClick={this.clickHandle}>
<Image src={scanIcon} />
</View>
......@@ -87,11 +199,4 @@ class Index extends Component {
}
}
// #region 导出注意
//
// 经过上面的声明后需要将导出的 Taro.Component 子类修改为子类本身的 props 属性
// 这样在使用这个子类时 Ts 才不会提示缺少 JSX 类型参数错误
//
// #endregion
export default Index as ComponentClass<PageOwnProps, PageState>;
import { Component, Config } from '@tarojs/taro';
import Taro, { Component, Config } from '@tarojs/taro';
import { ComponentClass } from 'react';
import { View, Text, Button, Input } from '@tarojs/components';
import { View, Text, Button } from '@tarojs/components';
import { getPayOrder } from '../../api/Order/pay';
import { connect } from '@tarojs/redux';
import { Device } from '../../types/Device/Device';
import PrepayConfig from '../../types/Order/Order';
import { Customer } from '../../types/Customer/Customer';
import AES from 'crypto-js/aes';
import Utf8 from 'crypto-js/enc-utf8';
import ECBmode from 'crypto-js/mode-ecb';
import PaddingPkcs7 from 'crypto-js/pad-pkcs7';
import { paySuccess } from '../../api/Order/paySuccess';
type PageStateProps = {
device: Device;
prepayConfig: PrepayConfig[];
userinfo: Customer;
};
type PageDispatchProps = {};
type PageState = {
payId: undefined | number;
};
type Iprop = PageStateProps & PageDispatchProps;
interface Pay {
props: Iprop;
state: PageState;
}
@connect(({ device, prepayConfig, userinfo }) => ({
device,
prepayConfig,
userinfo,
}))
class Pay extends Component {
config: Config = {
navigationBarTitleText: '支付金额',
};
constructor(props) {
super(props);
this.state = {
payId: undefined,
};
}
selectPayConfig(id: number) {
this.setState({
payId: id,
});
}
payHandle() {
const {
device,
userinfo: { customerId },
} = this.props;
const { payId } = this.state;
if (payId) {
getPayOrder({
equipmentNum: device.code,
equipmentPos: device.position,
payType: '',
serviceId: 0,
smaproCustomerId: customerId,
smaproPrepayConfigId: payId,
})
.then(res => {
const { payStr, outTradeNo } = res;
const key = customerId.toString().padEnd(16, '0');
const payData = JSON.parse(
AES.decrypt(payStr, Utf8.parse(key), {
mode: ECBmode,
padding: PaddingPkcs7,
}).toString(Utf8),
);
console.log(payData);
Taro.requestPayment({
timeStamp: payData.msg.timeStamp.toString(),
nonceStr: payData.msg.nonceStr,
package: payData.msg.package,
signType: payData.msg.signType,
paySign: payData.msg.paySign,
}).then(res => {
console.log(res);
paySuccess({ orderId: outTradeNo });
Taro.showLoading({
title: '设备连接中',
mask: true,
});
});
})
.catch(err => {
Taro.showToast({
title: '发起支付失败!',
icon: 'none',
});
console.error(err);
});
} else {
Taro.showToast({
title: '请选择预付金额',
icon: 'none',
});
}
}
render() {
const { device, prepayConfig } = this.props;
const { payId } = this.state;
return (
<View className='Pay'>
<View className='Pay-info'>
<View className='Pay-info-item'>
<Text>服务类型</Text>
<Text>饮水</Text>
<Text>
{prepayConfig && prepayConfig.length
? prepayConfig[0].serviceName
: ''}
</Text>
</View>
<View className='Pay-info-item'>
<Text>设备编号</Text>
<Text>SN100089078</Text>
<Text>{device.code}</Text>
</View>
<View className='Pay-info-item'>
<Text>设备位置</Text>
<Text>天信苑A幢1栋2302门口</Text>
<Text>{device.position}</Text>
</View>
<View className='Pay-info-item'>
<Text>适用费率</Text>
<View className='Pay-info-rate'>
<View>
{device.rates.map(item => (
<View key={item.name}>
<Text>{item.name}</Text>
<Text>{item.mark}</Text>
</View>
))}
{/* <View>
<Text>冷水</Text>
<Text>0.02元/100ml</Text>
</View>
<View>
<Text>热水</Text>
<Text>0.02元/100ml</Text>
</View>
</View> */}
</View>
</View>
</View>
......@@ -40,16 +153,27 @@ class Pay extends Component {
温馨提示:如本次预付款未全部使用,系统将在使用结束后半小时内自动退还剩余金额,请留意查收!
</View>
<View className='Pay-money'>
<View className='Pay-money-item seleted'>0.50元</View>
<View className='Pay-money-item'>1.00元</View>
{prepayConfig.map(payConfig => (
<View
key={payConfig.id}
className={`Pay-money-item ${
payConfig.id == payId ? 'seleted' : ''
}`}
onClick={() => this.selectPayConfig(payConfig.id)}>
{payConfig.prepayMoney}
</View>
))}
{/* <View className='Pay-money-item'>1.00元</View>
<View className='Pay-money-item'>2.00元</View>
<View className='Pay-money-item'>5.00元</View>
<Input className='Pay-money-item' placeholder='输入>0.5数额' />
<Input className='Pay-money-item' placeholder='输入>0.5数额' /> */}
</View>
<Button className='Pay-btn'>确定支付</Button>
<Button className='Pay-btn' onClick={this.payHandle}>
确定支付
</Button>
</View>
);
}
}
export default Pay as ComponentClass;
export default Pay as ComponentClass<Iprop, PageState>;
import { ADD, MINUS } from '../constants/counter'
const INITIAL_STATE = {
num: 0
}
export default function counter (state = INITIAL_STATE, action) {
switch (action.type) {
case ADD:
return {
...state,
num: state.num + 1
}
case MINUS:
return {
...state,
num: state.num - 1
}
default:
return state
}
}
import { combineReducers } from 'redux'
import counter from './counter'
export default combineReducers({
counter
})
import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from '../reducers'
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers';
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
})
: compose;
const middlewares = [
thunkMiddleware
]
const middlewares = [thunkMiddleware];
if (process.env.NODE_ENV === 'development') {
middlewares.push(require('redux-logger').createLogger())
middlewares.push(require('redux-logger').createLogger());
}
const enhancer = composeEnhancers(
applyMiddleware(...middlewares),
// other store enhancers if any
)
);
export default function configStore () {
const store = createStore(rootReducer, enhancer)
return store
}
const store = createStore(rootReducer, enhancer);
export default store;
import { combineReducers } from 'redux';
import userinfo from './rootReducers/userinfo';
import device from './rootReducers/device';
import prepayConfig from './rootReducers/prepayConfig';
export default combineReducers({
userinfo,
device,
prepayConfig,
});
type ConstantState = {
APP_ID: string;
};
const INITIAL_STATE = {
APP_ID: 'wxf5912b79bba23663',
};
export default function counter(state: ConstantState = INITIAL_STATE) {
return state;
}
import Action from '../../types/Store/Actions';
import { Device } from '../..//types/Device/Device';
export type DeviceState = StoreState<Device>;
export const INITIAL_STATE = {
code: '',
isCurrentUserUsed: 0,
isOnline: 0,
isUsed: 0,
operatorId: 0,
operatorName: '',
position: '',
positionId: '',
rates: [],
};
export const updateDeviceData = (entity: DeviceState): Action => ({
type: 'UPDATE_DEVICE_DATA',
payload: entity,
});
export default function device(
state: Device = INITIAL_STATE,
actions: Action,
): Device {
switch (actions.type) {
case 'UPDATE_DEVICE_DATA':
return {
...state,
...actions.payload,
};
default:
return state;
}
}
import Action from '../../types/Store/Actions';
import { PrepayConfig } from '../../types/Order/Order';
export const INITIAL_STATE = [];
export const updatePayData = (entity: PrepayConfig[]): Action => ({
type: 'UPDATE_DEVICE_DATA',
payload: entity,
});
export default function prepayConfig(
state: PrepayConfig[] = INITIAL_STATE,
actions: Action,
): PrepayConfig[] {
switch (actions.type) {
case 'UPDATE_DEVICE_DATA':
return {
...state,
...actions.payload,
};
default:
return state;
}
}
import Action from '../../types/Store/Actions';
import { Customer } from '../../types/Customer/Customer';
export type UserState = StoreState<Customer & { code: string }>;
export const INITIAL_STATE = {
token: '',
customerId: 0,
};
export const updateUserInfo = (entity: UserState): Action => ({
type: 'UPDATE_USERINFO',
payload: entity,
});
export default function userinfo(
state: Customer = INITIAL_STATE,
actions: Action,
): Customer {
switch (actions.type) {
case 'UPDATE_USERINFO':
return {
...state,
...actions.payload,
};
default:
return state;
}
}
export type Customer = {
token: string;
customerId: number;
};
export type WechatUserInfo = {
avatarUrl: string;
city: string;
country: string;
gender: number;
language: string;
nickName: string;
province: string;
};
export type Rate = {
mark: string;
name: string;
};
export type Device = {
code: string;
isCurrentUserUsed: number;
isOnline: number;
isUsed: number;
operatorId: number;
operatorName: string;
position: string;
positionId: string;
rates: Rate[];
};
export type PrepayConfig = {
createAt: string;
// 创建时间
id: number;
// 会员id
isOpen: number;
// 是否开启
operateId: number;
// 运营商id
operateName: string;
// 运营商名称
prepayMoney: number;
// 预付金额
serviceId: number;
// 服务类型id
serviceName: string;
// 服务名称
sortId: number;
// 序号
updateAt: string;
// 更新时间
};
export default PrepayConfig;
type Actions = {
type: String;
payload?: any;
};
export default Actions;
type StoreState<T> = { [P in keyof T]?: T[P] };
......@@ -1874,6 +1874,10 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
crypto-js@^3.1.9-1:
version "3.1.9-1"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8"
css-loader@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.0.tgz#9f46aaa5ca41dbe31860e3b62b8e23c42916bf56"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment