Commit 9b805d52 by 姜雷

Merge branch 'test'

parents cd0bdc1a 57ef7412
{
"name": "wx-school-app",
"version": "1.0.9",
"version": "1.0.10",
"private": true,
"description": "",
"scripts": {
......
......@@ -4,6 +4,7 @@
"description": "",
"appid": "wxf2cac8f84183c8fc",
"setting": {
"checkSiteMap": false,
"urlCheck": true,
"es6": false,
"postcss": false,
......
{
"rules": {
"rules": [
{
"action": "allow",
"page": "pages/Login/Login"
},
{
"action": "disallow",
"page": "*"
}
]
}
......@@ -93,3 +93,90 @@ export const refreshCodeBar = (): Promise<ResponseDataEntity<string>> =>
customerFetch({
url: '/dcxy/wechat/applet/flush/idbar',
});
type ChangeAppPasswordParam = {
customerId: number;
oldPassword: string;
password: string;
};
export const changeAppPassword = (
entity: ChangeAppPasswordParam,
): Promise<ResponseDataEntity<null>> =>
customerFetch({
url: '/app/customer/old/pwd',
method: 'POST',
data: entity,
});
export const changeDevicePassword = (
entity: ChangeAppPasswordParam,
): Promise<ResponseDataEntity<null>> =>
customerFetch({
url: '/app/customer/hardware/pwd',
method: 'POST',
data: entity,
});
type ChangePhoneAccountParams = {
customerId: number;
password: string;
phoneAccount: string;
verification: string;
};
export const changePhoneAccount = (
entity: ChangePhoneAccountParams,
): Promise<ResponseDataEntity<null>> =>
customerFetch({
url: '/app/customer/phone/account/',
method: 'POST',
data: entity,
});
type SwitchAccountLoginStateParams = {
customerId: number;
state: string;
};
export const switchAccountLoginState = (
entity: SwitchAccountLoginStateParams,
): Promise<ResponseDataEntity<null>> =>
customerFetch({
url: `/app/customer/switch/${entity.customerId}/${entity.state}`,
method: 'PUT',
});
type FetchBalanceStateParams = {
customerId: number;
};
export enum BalanceState {
open = 1,
close = 0,
}
export const fetchBalanceState = (
entity: FetchBalanceStateParams,
): Promise<ResponseDataEntity<BalanceState>> =>
customerFetch({
url: '/app/customer/balance/state',
data: entity,
});
type SwitchBalanceStateParams = {
customerId: number;
isEnabled: BalanceState;
};
export const switchBalanceState = (
entity: SwitchBalanceStateParams,
): Promise<ResponseDataEntity<null>> =>
customerFetch({
url: `/app/customer/swich/balance/${entity.customerId}/${entity.isEnabled}`,
method: 'PUT',
});
type FetchAppLaunchCustomerParams = {
secretCode: string;
uuId: string;
serviceId: string;
timeStamp: string;
sign: string;
};
export const fetchAppLaunchCustomer = (entity: FetchAppLaunchCustomerParams) =>
customerFetch({
url: '/dcxy/wechat/anxinpay/app/jump/mini/validatelogin',
data: entity,
});
......@@ -44,7 +44,7 @@ const createFetch = (basePath: string) => {
return data;
}
if (data.code === LogoutCode) {
Taro.redirectTo({
Taro.reLaunch({
url: '/pages/Login/Login',
});
throw data;
......
......@@ -55,15 +55,38 @@ export const startShowerEquipment = (params: StartParams) =>
method: 'POST',
data: params,
});
export enum ShowerUseType {
bluetooth = 1,
appointment = 2,
mix = 3,
}
type ControllerParams = {
customerId: number;
campusId: number;
};
type ControllerResponse = {
type Balance = {
amount: number;
// allowEmptyValue: false
// 服务数量
serviceId: string;
// allowEmptyValue: false
// 服务id -1:授信额度;0:艾米;1:豆
serviceName: string;
// allowEmptyValue: false
// 服务名称
};
export type ControllerResponse = {
appointmentThresholdPrompt: string;
balances: Balance[];
beanAmount: number;
defaultType: ShowerUseType;
money: number;
thresholdPrompt: string;
thresholdValue: number;
useType: ShowerUseType;
};
export const getShowerController = (
......@@ -73,3 +96,184 @@ export const getShowerController = (
url: '/dcxy/api/shower/controllerConfigs',
data: params,
});
type AppointDeviceParam = {
userId: number;
campusId: number;
location?: string;
pageNum?: number;
pageSize?: number;
};
export type AppointDeviceResponse = {
appointable: boolean;
// example: false
// allowEmptyValue: false
// 是否可预约
code: string;
// allowEmptyValue: false
// 设备编号
inPunishment: boolean;
// example: false
// allowEmptyValue: false
// 用户是在惩罚中
location: string;
// allowEmptyValue: false
// 设备位置
operationMode: OperationMode;
// allowEmptyValue: false
// 设备计费模式
};
export const getAppointmentDevice = (
params: AppointDeviceParam,
): Promise<ResponseDataEntity<{ list: AppointDeviceResponse[] }>> =>
showerFetch({
url: '/dcxy/api/shower/appointment/devices',
data: params,
});
type AppointingParams = {
account: string;
// allowEmptyValue: false
// 设备登录账号
deviceCode: string;
// allowEmptyValue: false
// 设备编号
phone: string;
// allowEmptyValue: false
// 手机号
userId: number;
// allowEmptyValue: false
// 用户ID
userName: string;
// allowEmptyValue: false
// 用户名称
};
export const appointingEquipment = (
params: AppointingParams,
): Promise<ResponseDataEntity<number>> =>
showerFetch({
url: '/dcxy/api/shower/appointment/appointing',
method: 'POST',
data: params,
});
type AppointmentRecordsParams = {
pageNum?: number;
// 当前页码
pageSize?: number;
// 页面大小
orderSort?: string;
// 排序
userId: number;
// 用户ID
appointTimeStart?: string;
// 预约时间区间上限
appointTimeEnd?: string;
// 预约时间区间下限
};
export type AppointmentRecordsResponse = {
appointTime: string;
// allowEmptyValue: false
// 预约时间
campusName: string;
// allowEmptyValue: false
// 所属区域
deviceCode: string;
// allowEmptyValue: false
// 设备编号
deviceLocation: string;
// allowEmptyValue: false
// 设备位置
expireTime: string;
// allowEmptyValue: false
// 到期时间
status: number;
// allowEmptyValue: false
// 状态: 0-预约中,1-预约成功,2-使用中,3-完成,4-预约失败,5-失效(逾期未使用)
used: boolean;
// example: false
// allowEmptyValue: false
// 是否使用
};
export const getAppointmentRecords = (
params: AppointmentRecordsParams,
): Promise<ResponseDataEntity<{ list: AppointmentRecordsResponse[] }>> =>
showerFetch({
url: '/dcxy/api/shower/appointment/appointRecords',
data: params,
});
type UserAppointableParams = {
userId: number;
campusId: number;
};
export type UserAppointableData = {
appointMode: number;
// allowEmptyValue: false
// 校验结果:0-校区未启用预约功能,1-校区未设置惩罚时长,2-用户未在惩罚中,3-用户在惩罚中
defaultType: number;
// allowEmptyValue: false
// 混合模式时的默认使用模式
duration: number;
// allowEmptyValue: false
// 预约有效时长
message: string;
// allowEmptyValue: false
// 被禁用时提示消息
punishment: number;
// allowEmptyValue: false
// 系统惩罚时长
useType: number;
// allowEmptyValue: false
// 使用模式: 1-蓝牙,2-预约,3-混合
userPunishment: number;
// allowEmptyValue: false
// 用户剩余惩罚时长
};
export const getUserAppointableData = (
params: UserAppointableParams,
): Promise<ResponseDataEntity<UserAppointableData>> =>
showerFetch({
url: `/dcxy/api/shower/appointment/user/${params.userId}/appointable/${
params.campusId
}`,
});
type AppointResultParams = {
recordId: number;
userId: number;
};
export enum AppointResultRes {
appointting = 0,
appointSuccess = 1,
appointFail = 4,
}
export const fetchAppointResult = (
params: AppointResultParams,
): Promise<ResponseDataEntity<AppointResultRes>> =>
showerFetch({
url: `/dcxy/api/shower/appointment/appointResult/${params.recordId}`,
data: params,
});
......@@ -25,13 +25,14 @@ class App extends Component {
config: Config = {
pages: [
'pages/index/index',
'pages/UserSetting/CustomerSetting',
'pages/Home/Home',
'pages/Order/OrderPay/OrderPay',
'pages/Order/OrderList/OrderList',
'pages/BarCode/BarCode',
'pages/Announcement/Announcement',
'pages/UserSetting/UserSetting',
'pages/ResetPwd/ResetPwd',
'pages/Password/ResetPwd',
'pages/Register/Register',
'pages/Login/Login',
'pages/Feedback/Feedback',
......@@ -40,8 +41,13 @@ class App extends Component {
'pages/Content/Content',
'pages/WebPage/WebPage',
'pages/Shower/Shower',
'pages/Shower/ShowerAppointment',
'pages/WaterDispenser/WaterDispenser',
'pages/Account/Account',
'pages/Password/ChangePwd',
'pages/Password/ChangeHardwareAccount',
'pages/Password/ChangeTelAccount',
'pages/AppLaunch/AppLaunch',
],
window: {
backgroundTextStyle: 'light',
......
.AppBackButton {
position: absolute;
left: 0;
bottom: 160px;
width: 140px;
height: 60px;
line-height: 60px;
font-size: 30px;
}
import { Button } from '@tarojs/components';
import Taro from '@tarojs/taro';
import useAppBackState from '@/hooks/useAppBackState';
import './AppBackButton.scss';
const AppBackButton = () => {
const isShow = useAppBackState();
const goBackAppError = e => {
console.log(e);
Taro.showModal({
title: '提示',
content: '呼起APP失败,请手动回到APP',
});
};
return isShow ? (
<Button
className='AppBackButton'
open-type='launchApp'
app-parameter='wechat'
onError={goBackAppError}>
返回
</Button>
) : null;
};
export default AppBackButton;
......@@ -98,7 +98,7 @@ class Vcode extends Component {
ctx.draw();
}
clickHandle() {
clickHandle = () => {
const { vcode, inputCode } = this.state;
if (vcode && vcode.toString() == inputCode) {
const { cellphone, positionNum } = this.props;
......@@ -126,7 +126,7 @@ class Vcode extends Component {
icon: 'none',
});
}
}
};
countStart() {
this.setState({
......
import Taro, { useEffect, useState } from '@tarojs/taro';
const useAppBackState = (): boolean => {
const [state, setState] = useState(false);
useEffect(() => {
let entity = Taro.getLaunchOptionsSync();
console.log(entity);
if (entity.scene === 1069 || entity.scene === 1036) {
setState(true);
}
}, []);
return state;
};
export default useAppBackState;
import { useState, useCallback } from '@tarojs/taro';
import { BaseEventOrig } from '@tarojs/components/types/common';
function useInputValue(initialValue: string) {
let [value, setValue] = useState(initialValue);
let onChange = useCallback(function(
event: BaseEventOrig<{
value: string;
cursor: number;
keyCode: number;
}>,
) {
setValue(event.detail.value);
}, []);
return {
value,
onChange,
};
}
export default useInputValue;
import { useSelector, useDispatch } from '@tarojs/redux';
import { getShowerController, ShowerUseType } from '@/api/shower';
import { updateShowerControlConfig } from '@/store/rootReducers/shower';
import { Customer } from '@/types/Customer/Customer';
import Taro, { useEffect, useState } from '@tarojs/taro';
const useShowerController = () => {
const [method, setState] = useState('');
const dispatch = useDispatch();
const userinfo = useSelector(
(state: { userinfo: Customer }) => state.userinfo,
);
useEffect(() => {
if (method) {
Taro.showLoading();
getShowerController({
campusId: userinfo.areaId,
customerId: userinfo.customerId,
})
.then(res => {
let data = res.data;
dispatch(updateShowerControlConfig(res.data));
let type =
data.useType === ShowerUseType.mix
? data.defaultType
: data.useType;
let url = '';
if (type === ShowerUseType.bluetooth) {
url = '/pages/Shower/Shower';
} else {
url = '/pages/Shower/ShowerAppointment';
}
Taro.hideLoading();
console.log(url);
if (method === 'redirectTo') {
Taro.redirectTo({ url: url });
} else {
Taro.navigateTo({ url: url });
}
})
.catch(err => {
Taro.hideLoading();
console.log('getShowerController err:', err);
Taro.showToast({
title: err.msg || '网络错误',
icon: 'none',
});
});
}
}, [method]);
return setState;
};
export default useShowerController;
.AppLaunch {
padding: 60px 48px;
.AppLaunch-Message {
margin-bottom: 40px;
}
}
import './AppLaunch.scss';
import { View, Button } from '@tarojs/components';
import Taro, { useEffect, useState } from '@tarojs/taro';
import { fetchAppLaunchCustomer } from '@/api/customer';
import { LogoutCode } from '@/constants';
import { useDispatch } from '@tarojs/redux';
import { updateUserInfo } from '@/store/rootReducers/userinfo';
import AppBackButton from '@/components/AppBackButton/AppBackButton';
import useShowerController from '@/hooks/useShowerController';
function AppLaunch() {
const dispatch = useDispatch();
const setGoMethod = useShowerController();
const goNextPage = (serviceId: string) => {
let url = '';
switch (serviceId) {
case '9':
url = '/pages/WaterDispenser/WaterDispenser';
break;
case '10':
setGoMethod('redirectTo');
return;
case '11':
case '12':
url = `/pages/BarCode/BarCode?serviceId=${serviceId}`;
break;
default:
console.log('goNextPage serviceId: ' + serviceId);
Taro.showToast({
title: '无法识别的服务',
icon: 'none',
});
break;
}
if (url) {
Taro.redirectTo({ url });
}
};
const [showBackButtonState, setShowBackButtonState] = useState(false);
const [errorMsg, setErrorMsg] = useState('');
useEffect(() => {
console.log('AppLaunch', this.$router.params);
let entity = this.$router.params;
Taro.showLoading();
const { uuId, serviceId } = entity;
if (uuId && serviceId) {
fetchAppLaunchCustomer({
...entity,
timeStamp: Math.floor(new Date().getTime() / 1000),
})
.then(res => {
console.log(res);
Taro.hideLoading();
dispatch(updateUserInfo(res.data));
goNextPage(serviceId);
})
.catch(err => {
Taro.hideLoading();
let msg = err.msg || '请求失败';
Taro.showToast({
title: msg,
icon: 'none',
});
if (err.code !== LogoutCode) {
setErrorMsg(msg);
setShowBackButtonState(true);
}
});
} else {
Taro.hideLoading();
console.log('no uuid');
setErrorMsg('进入小程序参数错误');
setShowBackButtonState(true);
}
}, []);
const goBackAppError = e => {
console.log(e);
Taro.showModal({
title: '提示',
content: '呼起APP失败,请手动回到APP',
});
};
return (
<View className='AppLaunch'>
<View className='AppLaunch-Message'>{errorMsg}</View>
{showBackButtonState && (
<Button
open-type='launchApp'
app-parameter='wechat'
onError={goBackAppError}>
返回APP
</Button>
)}
</View>
);
}
export default AppLaunch;
......@@ -17,6 +17,7 @@ import { shareHandle } from '@/common/shareMethod';
import { PayOrderState } from '@/store/rootReducers/orderState';
import WaitPayOrderComponent from '@/components/WaitPayOrder/WaitPayOrder';
import widthWaitPayOrderComponent from '@/components/WaitPayOrder/WaitPayOrder';
import AppBackButton from '@/components/AppBackButton/AppBackButton';
type PageStateProps = {
userinfo: Customer;
......@@ -172,6 +173,7 @@ class BarCode extends Component {
return (
<View className='BarCode'>
<AppBackButton />
<WaitPayOrderComponent />
<View className={payOrderState ? 'blur' : ''}>
{showBackTag && (
......
......@@ -12,7 +12,7 @@
background-color: #fff;
border-radius: 24px;
margin-top: 4px;
height: 176px;
// height: 176px;
font-size: 28px;
text-align: center;
line-height: 88px;
......
......@@ -38,6 +38,12 @@ import MenuIconNormal from '@/components/MenuIcon/normal/MenuIconNormal';
import MenuIconBlock from '@/components/MenuIcon/block/MenuIconBlock';
import MenuIconBig from '@/components/MenuIcon/big/MenuIconBig';
import { updateServiceList } from '@/store/rootReducers/service';
import {
getShowerController,
ShowerUseType,
ControllerResponse,
} from '@/api/shower';
import { updateShowerControlConfig } from '@/store/rootReducers/shower';
type PageStateProps = {
userinfo: Customer;
......@@ -46,6 +52,7 @@ type PageStateProps = {
type PageDispatchProps = {
updateUserInfo: (e: UserState) => void;
updateServiceList: (e: Service[]) => void;
updateShowerControlConfig: (e: ControllerResponse) => void;
};
type BeanAccount = {
......@@ -77,6 +84,9 @@ interface Home {
updateServiceList(entity: Service[]) {
dispatch(updateServiceList(entity));
},
updateShowerControlConfig(e: ControllerResponse) {
dispatch(updateShowerControlConfig(e));
},
}),
)
class Home extends Component {
......@@ -114,6 +124,7 @@ class Home extends Component {
componentDidShow() {
this.getInitData();
}
getInitData() {
this.getServiceList();
this.getAnn();
......@@ -182,11 +193,35 @@ class Home extends Component {
});
}
goShower() {
goShower = () => {
const { userinfo, updateShowerControlConfig } = this.props;
getShowerController({
campusId: userinfo.areaId,
customerId: userinfo.customerId,
})
.then(res => {
let data = res.data;
updateShowerControlConfig(res.data);
let type =
data.useType === ShowerUseType.mix ? data.defaultType : data.useType;
if (type === ShowerUseType.bluetooth) {
Taro.navigateTo({
url: '/pages/Shower/Shower',
});
} else {
Taro.navigateTo({
url: '/pages/Shower/ShowerAppointment',
});
}
})
.catch(err => {
console.log('getShowerController err:', err);
Taro.showToast({
title: err.msg || '网络错误',
icon: 'none',
});
});
};
goDispenser() {
Taro.navigateTo({
......@@ -221,6 +256,13 @@ class Home extends Component {
});
}
goUserSetting() {
this.toggleBarMenu();
Taro.navigateTo({
url: '/pages/UserSetting/CustomerSetting',
});
}
render() {
const { userinfo, serviceList } = this.props;
const { annItem, barMenuVisiable } = this.state;
......@@ -232,6 +274,7 @@ class Home extends Component {
<View className='mask' onClick={this.toggleBarMenu} />
<View className='Home-BarMenu'>
<View className='Home-BarMenu-Content'>
<View onClick={this.goUserSetting}>个人设置</View>
<View onClick={this.goFeedback}>意见反馈</View>
<View onClick={this.logoutHandle}>切换账号</View>
</View>
......@@ -252,7 +295,8 @@ class Home extends Component {
{userinfo.customerName}
</View>
<View className='Home-UserBox-tel'>
{this.formatePhone(userinfo.customerPhone)}
{userinfo.customerPhone &&
this.formatePhone(userinfo.customerPhone)}
</View>
<View className='Home-UserBox-addr'>{userinfo.areaName}</View>
</View>
......
.Login {
height: 100%;
background: linear-gradient(180deg, #486dec, #b3b8fc);
background: linear-gradient(145deg, #486dec, #b3b8fc);
overflow: hidden;
padding: 0 32px;
.Login-title {
......
......@@ -160,7 +160,7 @@ class Login extends Component {
</View>
<Navigator
className='Login-regist-btn'
url='/pages/ResetPwd/ResetPwd'>
url='/pages/Password/ResetPwd'>
忘记密码
</Navigator>
</View>
......
......@@ -17,10 +17,10 @@ class OrderTitle extends Component {
const { price } = this.props;
return (
<View className='OrderTitle'>
<View className='OrderTitle-name'>
{/* <View className='OrderTitle-name'>
<Image className='OrderTitle-icon' src={comLogo} />
<Text>成都多彩任意门科技有限公司</Text>
</View>
</View> */}
<View className='OrderTitle-price'>-{price ? price : ''}</View>
</View>
);
......
.ChangeHardwareAccount {
.ChangePwd-btn {
margin-top: 60px;
}
}
import './Common.scss';
import './ChangeHardwareAccount.scss';
import Taro from '@tarojs/taro';
import usePasswordInput from './usePasswordInput';
import { View, Button, Image, Input } from '@tarojs/components';
import pwdHideIcon from '../../images/login/setting_invisible_icon@2x.png';
import pwdShowIcon from '../../images/login/setting_see_icon@2x.png';
import { changeDevicePassword } from '@/api/customer';
import { useSelector } from '@tarojs/redux';
import { Customer } from '@/types/Customer/Customer';
const ChangeHardwareAccount = () => {
const customerId = useSelector(
(state: { userinfo: Customer }) => state.userinfo.customerId,
);
const { pwd, setPwd, showPwd, setShowPwd } = usePasswordInput();
const {
pwd: oldPwd,
setPwd: setOldPwd,
showPwd: showOldPwd,
setShowPwd: setShowOldPwd,
} = usePasswordInput();
const {
pwd: checkPwd,
setPwd: setCheckPwd,
showPwd: showCheckPwd,
setShowPwd: setShowCheckPwd,
} = usePasswordInput();
const changeAccountHandle = () => {
if (!oldPwd || oldPwd.length < 6) {
return Taro.showToast({
title: '请输入6-20位登录密码',
icon: 'none',
});
}
if (!pwd || pwd.length !== 4) {
return Taro.showToast({
title: '请输入4位新密码',
icon: 'none',
});
}
if (!checkPwd || checkPwd.length !== 4 || pwd !== checkPwd) {
return Taro.showToast({
title: '请确认输入密码和新密码完全一致',
icon: 'none',
});
}
Taro.showLoading();
changeDevicePassword({
password: pwd,
oldPassword: oldPwd,
customerId: customerId,
})
.then(res => {
Taro.hideLoading();
Taro.showToast({
title: '修改成功',
});
setTimeout(() => {
Taro.navigateBack();
}, 2000);
})
.catch(err => {
Taro.hideLoading();
Taro.showToast({
title: err.msg || '修改成功',
icon: 'none',
});
});
};
return (
<View className='ChangeHardwareAccount Password'>
<View className='Password-form'>
<View className='Password-title'>小程序登录密码</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
password={!showOldPwd}
placeholder='请输入当前登录密码'
maxLength={20}
value={oldPwd}
onInput={({ detail: { value } }) => setOldPwd(value)}
/>
<Image
className='registerBox-pwdIcon'
src={showOldPwd ? pwdShowIcon : pwdHideIcon}
onClick={setShowOldPwd}
/>
</View>
<View className='Password-title'>新密码</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
type='number'
password={!showPwd}
placeholder='设备密码由4位数字组成'
maxLength={4}
value={pwd}
onInput={({ detail: { value } }) => setPwd(value)}
/>
<Image
className='registerBox-pwdIcon'
src={showPwd ? pwdShowIcon : pwdHideIcon}
onClick={setShowPwd}
/>
</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
type='number'
password={!showCheckPwd}
placeholder='确认密码'
maxLength={4}
value={checkPwd}
onInput={({ detail: { value } }) => setCheckPwd(value)}
/>
<Image
className='registerBox-pwdIcon'
src={showCheckPwd ? pwdShowIcon : pwdHideIcon}
onClick={setShowCheckPwd}
/>
</View>
<Button className='ChangePwd-btn' onClick={changeAccountHandle}>
确定
</Button>
</View>
</View>
);
};
ChangeHardwareAccount.config = {
navigationBarTitleText: '设备密码修改',
};
export default ChangeHardwareAccount;
.ChangePwd {
.ChangePwd-forget {
margin: 40px auto 60px;
color: #f93d3d;
font-size: 24px;
text-align: right;
}
.ChangePwd-btn {
border-radius: 24px;
}
}
import './Common.scss';
import './ChangePwd.scss';
import { View, Image, Input, Button } from '@tarojs/components';
import Taro from '@tarojs/taro';
import pwdHideIcon from '../../images/login/setting_invisible_icon@2x.png';
import pwdShowIcon from '../../images/login/setting_see_icon@2x.png';
import { changeAppPassword } from '@/api/customer';
import usePasswordInput from './usePasswordInput';
import { useSelector } from '@tarojs/redux';
import { Customer } from '@/types/Customer/Customer';
const ChangePwd = () => {
const customerId = useSelector(
(state: { userinfo: Customer }) => state.userinfo.customerId,
);
const { pwd, setPwd, showPwd, setShowPwd } = usePasswordInput();
const {
pwd: oldPwd,
setPwd: setOldPwd,
showPwd: showOldPwd,
setShowPwd: setShowOldPwd,
} = usePasswordInput();
const {
pwd: checkPwd,
setPwd: setCheckPwd,
showPwd: showCheckPwd,
setShowPwd: setShowCheckPwd,
} = usePasswordInput();
const changePasswordHandle = () => {
if (!oldPwd || oldPwd.length < 6) {
return Taro.showToast({
title: '请输入6-20位旧密码',
icon: 'none',
});
}
if (!pwd || pwd.length < 6) {
return Taro.showToast({
title: '请输入6-20位新密码',
icon: 'none',
});
}
if (!checkPwd || checkPwd.length < 6 || pwd !== checkPwd) {
return Taro.showToast({
title: '请确认输入密码和新密码完全一致',
icon: 'none',
});
}
Taro.showLoading();
changeAppPassword({
password: pwd,
oldPassword: oldPwd,
customerId: customerId,
})
.then(res => {
Taro.hideLoading();
Taro.showToast({
title: '修改成功',
});
setTimeout(() => {
Taro.reLaunch({
url: '/pages/Login/Login',
});
}, 2000);
})
.catch(err => {
Taro.hideLoading();
Taro.showToast({
title: err.msg || '修改成功',
icon: 'none',
});
});
};
const goResetpwd = () => {
Taro.navigateTo({
url: '/pages/Password/ResetPwd',
});
};
return (
<View className='ChangePwd Password'>
<View className='Password-form'>
<View className='Password-title'>旧密码</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
password={!showOldPwd}
placeholder='请输入当前登录密码'
maxLength={20}
value={oldPwd}
onInput={({ detail: { value } }) => setOldPwd(value)}
/>
<Image
className='registerBox-pwdIcon'
src={showOldPwd ? pwdShowIcon : pwdHideIcon}
onClick={setShowOldPwd}
/>
</View>
<View className='Password-title'>设置新密码</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
password={!showPwd}
placeholder='输入6-20位新密码'
maxLength={20}
value={pwd}
onInput={({ detail: { value } }) => setPwd(value)}
/>
<Image
className='registerBox-pwdIcon'
src={showPwd ? pwdShowIcon : pwdHideIcon}
onClick={setShowPwd}
/>
</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
password={!showCheckPwd}
placeholder='确认6-20位新密码'
maxLength={20}
value={checkPwd}
onInput={({ detail: { value } }) => setCheckPwd(value)}
/>
<Image
className='registerBox-pwdIcon'
src={showCheckPwd ? pwdShowIcon : pwdHideIcon}
onClick={setShowCheckPwd}
/>
</View>
<View className='ChangePwd-forget' onClick={goResetpwd}>
忘记密码?
</View>
<Button className='ChangePwd-btn' onClick={changePasswordHandle}>
确定
</Button>
</View>
</View>
);
};
ChangePwd.config = {
navigationBarTitleText: '登陆密码修改',
};
export default ChangePwd;
.ChangeTelAccount {
.ChangeTelAccount-forget {
margin: 40px auto 60px;
color: #f93d3d;
font-size: 24px;
text-align: right;
}
}
import './Common.scss';
import './ChangeTelAccount.scss';
import { View, Button, Image, Input } from '@tarojs/components';
import Taro, { useState } from '@tarojs/taro';
import { useSelector } from '@tarojs/redux';
import { Customer } from '@/types/Customer/Customer';
import usePasswordInput from './usePasswordInput';
import pwdHideIcon from '../../images/login/setting_invisible_icon@2x.png';
import pwdShowIcon from '../../images/login/setting_see_icon@2x.png';
import useInputValue from '@/hooks/useInputValue';
import Vcode from '@/components/Vcode/Vcode';
import { changePhoneAccount } from '@/api/customer';
const ChangeTelAccount = () => {
const customerId = useSelector(
(state: { userinfo: Customer }) => state.userinfo.customerId,
);
const [vcode, setVcode] = useState('');
const { pwd, setPwd, showPwd, setShowPwd } = usePasswordInput();
const { value: phone, onChange } = useInputValue('');
const goResetpwd = () => {
Taro.navigateTo({
url: '/pages/Password/ResetPwd',
});
};
const changeAccountHandle = () => {
if (!pwd || pwd.length < 6) {
return Taro.showToast({
title: '请输入6-20位新密码',
icon: 'none',
});
}
if (!phone || phone.length !== 11) {
return Taro.showToast({
title: '请输入手机号码',
icon: 'none',
});
}
if (!vcode || vcode.length !== 6) {
return Taro.showToast({
title: '请输入6位验证码',
icon: 'none',
});
}
Taro.showLoading();
changePhoneAccount({
customerId: customerId,
password: pwd,
phoneAccount: phone,
verification: vcode,
})
.then(res => {
Taro.hideLoading();
Taro.showToast({
title: '修改成功',
});
setTimeout(() => {
Taro.reLaunch({
url: '/pages/Login/Login',
});
}, 2000);
})
.catch(err => {
Taro.hideLoading();
Taro.showToast({
title: err.msg || '修改成功',
icon: 'none',
});
});
};
return (
<View className='ChangeTelAccount Password'>
<View className='Password-form'>
<View className='Password-title'>小程序登录密码</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
password={!showPwd}
placeholder='请输入当前登录密码'
maxLength={20}
value={pwd}
onInput={({ detail: { value } }) => setPwd(value)}
/>
<Image
className='registerBox-pwdIcon'
src={showPwd ? pwdShowIcon : pwdHideIcon}
onClick={setShowPwd}
/>
</View>
<View className='Password-title'>更换所需手机号</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
placeholder='请输入手机号码'
maxLength={11}
value={phone}
onInput={onChange}
/>
</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
placeholderClass='loginBox-placeholder'
placeholder='请输入验证码'
maxLength={6}
value={vcode}
onInput={({ detail: { value } }) => setVcode(value)}
/>
<Vcode
vcode-classname='registerBox-getVcode'
positionNum='4'
ref='Vcode'
cellphone={phone}
/>
</View>
<View className='ChangeTelAccount-forget' onClick={goResetpwd}>
忘记密码?
</View>
<Button className='ChangePwd-btn' onClick={changeAccountHandle}>
确定
</Button>
</View>
</View>
);
};
ChangeTelAccount.config = {
navigationBarTitleText: '更换手机账户',
};
export default ChangeTelAccount;
.ResetPwd {
.ResetPwd-form {
.Password {
.Password-form {
padding: 0 32px;
}
.ResetPwd-title {
.Password-title {
margin-top: 52px;
margin-bottom: 32px;
font-size: 32px;
}
.ResetPwd-btn {
.Password-btn {
margin: 40px 16px 0;
border-radius: 24px;
}
......
.ResetPwd {
.ResetPwd-btn {
margin: 40px 16px 0;
border-radius: 24px;
}
}
......@@ -6,6 +6,7 @@ import pwdHideIcon from '../../images/login/setting_invisible_icon@2x.png';
import pwdShowIcon from '../../images/login/setting_see_icon@2x.png';
import ToastBox from '../../components/ToastBox/ToastBox';
import './Common.scss';
import './ResetPwd.scss';
import { changePwdByCellphone } from '../../api/customer';
import { replaceIllegalPwd } from '../../utils/pwd';
......@@ -113,7 +114,7 @@ class ResetPwd extends Component {
this.refs.ToastBox.showToast('修改成功');
console.log('回到登陆');
setTimeout(() => {
Taro.redirectTo({
Taro.reLaunch({
url: '/pages/Login/Login',
});
}, 2000);
......@@ -131,10 +132,10 @@ class ResetPwd extends Component {
checkPwd,
} = this.state;
return (
<View className='ResetPwd'>
<View className='ResetPwd Password'>
<ToastBox ref='ToastBox' />
<View className='ResetPwd-form'>
<View className='ResetPwd-title'>请输入您的登陆手机号</View>
<View className='Password-form'>
<View className='Password-title'>请输入您的登陆手机号</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
......@@ -172,7 +173,7 @@ class ResetPwd extends Component {
cellphone={cellphone}
/>
</View>
<View className='ResetPwd-title'>设置新密码</View>
<View className='Password-title'>设置新密码</View>
<View className='registerBox-item'>
<Input
className='loginBox-input'
......
import Taro, { useState, useCallback } from '@tarojs/taro';
import { switchAccountLoginState } from '@/api/customer';
const useAccountLogin = (
customerId: number,
initState: string,
): [boolean, (state: boolean) => void] => {
const [state, setState] = useState(initState === '1' ? true : false);
const setStateHandle = useCallback(
(state: boolean) => {
setState(state);
Taro.showLoading();
switchAccountLoginState({
customerId,
state: state ? '1' : '0',
})
.then(res => {
Taro.hideLoading();
console.log(res);
Taro.showToast({
title: '修改成功',
});
})
.catch(err => {
Taro.hideLoading();
console.log(err);
setState(!state);
Taro.showToast({
title: err.msg || '修改失败',
icon: 'none',
});
});
},
[customerId],
);
return [state, setStateHandle];
};
export default useAccountLogin;
import Taro, { useEffect, useState, useCallback } from '@tarojs/taro';
import {
fetchBalanceState,
switchBalanceState,
BalanceState,
} from '@/api/customer';
const useBalanceState = (
customerId: number,
): [boolean, (state: boolean) => void] => {
const [state, setState] = useState(false);
const getBoolValue = (e: BalanceState): boolean =>
e === BalanceState.open ? true : false;
useEffect(() => {
fetchBalanceState({
customerId,
})
.then(res => {
setState(getBoolValue(res.data));
})
.catch(err => {
console.log(err);
});
}, []);
const switchBalanceStateHandle = (state: BalanceState) => {
Taro.showLoading();
return switchBalanceState({
customerId,
isEnabled: state,
})
.then(res => {
Taro.hideLoading();
console.log(res);
setState(getBoolValue(state));
Taro.showToast({
title: '修改成功',
});
})
.catch(err => {
Taro.hideLoading();
console.log(err);
Taro.showToast({
title: err.msg || '修改失败',
icon: 'none',
});
});
};
const setStateHandle = useCallback(
(state: boolean) => {
setState(state);
if (state) {
Taro.showModal({
title: '提示',
content: '您将切换使用预充值模式,消费时请保证账号又余额',
}).then(res => {
if (res.confirm) {
switchBalanceStateHandle(BalanceState.open).catch(() => {
setState(false);
});
} else {
setState(false);
}
});
} else {
Taro.showModal({
title: '提示',
content: '关闭后将切换至订单单笔支付模式',
}).then(res => {
if (res.confirm) {
switchBalanceStateHandle(BalanceState.close).catch(() => {
setState(true);
});
} else {
setState(true);
}
});
}
},
[customerId],
);
return [state, setStateHandle];
};
export default useBalanceState;
import { useState, useCallback } from '@tarojs/taro';
import { ITouchEvent } from '@tarojs/components/types/common';
type PasswordInput = {
pwd: string;
setPwd: (e: string) => void;
showPwd: boolean;
setShowPwd: (e: ITouchEvent) => void;
};
const usePasswordInput = (): PasswordInput => {
const [pwd, setPwd] = useState('');
const [showPwd, setShowPwd] = useState(false);
const setShowPwdHandle = useCallback(() => {
setShowPwd(showPwd ? false : true);
}, [showPwd]);
return { pwd, setPwd, showPwd, setShowPwd: setShowPwdHandle };
};
export default usePasswordInput;
.Shower {
padding-top: 126px;
.showAppoint {
position: absolute;
box-sizing: border-box;
text-align: right;
right: 0;
top: 30px;
width: 60px;
height: 160px;
color: #fff;
background-color: #474747;
padding: 20px 15px 0;
line-height: 30px;
border-radius: 16px 0 0 16px;
}
.equipment-info-box {
width: 630px;
height: 572px;
......
......@@ -14,6 +14,8 @@ import {
fetchUsingShowerInfo,
getShowerController,
OperationMode,
ControllerResponse,
ShowerUseType,
} from '@/api/shower';
import { connect, useSelector } from '@tarojs/redux';
import { Customer } from '@/types/Customer/Customer';
......@@ -25,6 +27,7 @@ import { updateBluetoothDevice } from './actions';
import { ab2str, str2ab } from '@/utils/arrayBuffer';
import WaitPayOrderComponent from '@/components/WaitPayOrder/WaitPayOrder';
import { PayOrderState } from '@/store/rootReducers/orderState';
import AppBackButton from '@/components/AppBackButton/AppBackButton';
enum BlueToothError {
BlueToothNotOpen = 'BlueToothNotOpen',
......@@ -43,6 +46,7 @@ type DeviceInfo = {
type PageStateProps = {
userinfo: Customer;
bluetoothDevice: BluetoothDevice;
showerControlConfig: ControllerResponse;
};
type PageDispatchProps = {
updateBluetoothDevice: (entity: BluetoothDevice) => void;
......@@ -56,6 +60,7 @@ type PageState = {
sockedDone: boolean;
deviceDone: boolean;
showerState: boolean;
showAppoint: boolean;
};
const StopCode = 1000;
......@@ -68,9 +73,10 @@ let timer: NodeJS.Timeout | null = null;
let reConnectting: boolean = false;
let socketTask: Taro.SocketTask | null = null;
@connect(
({ userinfo, Shower }) => ({
({ userinfo, Shower, showerState }) => ({
userinfo,
bluetoothDevice: Shower,
showerControlConfig: showerState.controllerConfigs,
}),
dispatch => ({
updateBluetoothDevice(data: BluetoothDevice) {
......@@ -99,15 +105,23 @@ class Shower extends Component {
writeId: '',
readId: '',
},
showAppoint: false,
};
}
onShareAppMessage = shareHandle;
componentWillMount() {
// useType;
this.openBluetooth();
this.connectDeviceSocket();
this.checkUsingDevice();
const { showerControlConfig } = this.props;
if (showerControlConfig.useType === ShowerUseType.mix) {
this.setState({
showAppoint: true,
});
}
}
componentWillUnmount() {
......@@ -659,7 +673,7 @@ class Shower extends Component {
});
}
checkUserMonry() {
checkUserMoney() {
const { userinfo } = this.props;
return getShowerController({
customerId: userinfo.customerId,
......@@ -727,7 +741,7 @@ class Shower extends Component {
if (!sockedDone) {
this.connectDeviceSocket();
}
this.checkUserMonry()
this.checkUserMoney()
.then(() => this.startDevicesDiscovery())
.then((deviceId: string) => this.createConnection(deviceId))
.then(this.getDeviceServices)
......@@ -798,12 +812,17 @@ class Shower extends Component {
this.closeBluetoothConnection();
});
}
goAppointShower() {
Taro.redirectTo({
url: '/pages/Shower/ShowerAppointment',
});
}
render() {
const {
bluetoothDevice: { code, position },
} = this.props;
const { showerState } = this.state;
const { showerState, showAppoint } = this.state;
const payOrderState = useSelector(
(state: { orderState: PayOrderState }) =>
state.orderState.waitPayOrderState,
......@@ -811,8 +830,13 @@ class Shower extends Component {
return (
<View className='Shower'>
<AppBackButton />
<WaitPayOrderComponent />
{showAppoint && (
<View className='showAppoint' onClick={this.goAppointShower}>
预约洗浴
</View>
)}
<View className={payOrderState ? 'blur' : ''}>
<View className='equipment-info-box'>
<View className='equipment-info'>
......
.ShowerAppointment {
display: flex;
flex-direction: column;
height: 100%;
background: linear-gradient(180deg, #486dec, #b3b8fc);
.ShowerAppointment-Searchbox {
height: 100px;
margin: 0 40px 28px;
background-color: #fff;
border-radius: 16px;
display: flex;
align-items: center;
input {
padding-left: 40px;
font-size: 24px;
flex: 1;
}
.ShowerAppointment-Searchbox-btn {
font-size: 24px;
margin: 0 40px;
color: #8c95fa;
}
}
.ShowerAppointment-Account {
margin: 0 40px 34px;
color: #fff;
font-size: 28px;
display: flex;
.ShowerAppointment-Account-item {
display: flex;
height: 34px;
padding: 0 54px;
.name {
margin-right: 20px;
}
&:first-child {
padding-left: 0;
}
&:last-child {
border-left: 1px solid #7791f1;
}
}
}
.ShowerAppointment-Content {
box-sizing: border-box;
padding: 60px 40px;
background-color: #fff;
flex: 1;
overflow-y: scroll;
.ShowerAppointment-Equipment-title {
font-size: 32px;
color: #333;
font-weight: bolder;
}
.ShowerAppointment-Equipment-info {
margin: 8px 0 22px;
font-size: 24px;
color: #6180f4;
}
}
.ShowerAppointment-AppointmentList {
box-sizing: border-box;
flex: 1;
overflow-y: scroll;
padding: 10px 40px 20px;
}
.ShowerAppointment-Footer {
display: flex;
background-color: #fff;
height: 100px;
line-height: 100px;
justify-content: space-around;
font-size: 32px;
color: #999;
box-shadow: 0 -5px 5px -7px #bcbcbc;
.ShowerAppointment-Footer-item {
height: 100%;
&.actived {
position: relative;
color: #456beb;
}
&.actived::before {
content: '';
position: absolute;
left: 50%;
top: 0;
height: 4px;
width: 44px;
background-color: #ffd506;
transform: translate(-50%, 0);
}
}
}
.ShowerAppointment-ToggleBtn {
position: absolute;
box-sizing: border-box;
text-align: right;
right: 0;
top: 120px;
width: 60px;
height: 160px;
color: #fff;
background-color: #474747;
padding: 20px 15px 0;
line-height: 30px;
border-radius: 16px 0 0 16px;
}
}
import './ShowerAppointment.scss';
import { View, Input, Text, ScrollView } from '@tarojs/components';
import Taro, { useState, useEffect } from '@tarojs/taro';
import AppointermentCard from './components/AppointermentCard/AppointermentCard';
import useInputValue from '@/hooks/useInputValue';
import {
appointingEquipment,
ShowerUseType,
OperationMode,
fetchAppointResult,
AppointResultRes,
} from '@/api/shower';
import { useSelector } from '@tarojs/redux';
import { Customer } from '@/types/Customer/Customer';
import useDeviceList from './hooks/useDeviceList';
import AppointermentEquipment from './components/AppointermentEquipment/AppointermentEquipment';
import useAppointRecordsList from './hooks/useAppointRecordsList';
import useCheckAppointable from './hooks/useCheckAppointable';
import { ShowerState } from '@/store/rootReducers/shower';
import AppBackButton from '@/components/AppBackButton/AppBackButton';
import WaitPayOrderComponent from '@/components/WaitPayOrder/WaitPayOrder';
function ShowerAppointment() {
const userInfo = useSelector(
(state: { userinfo: Customer }) => state.userinfo,
);
const showerControlConfig = useSelector(
(state: { showerState: ShowerState }) =>
state.showerState.controllerConfigs,
);
const [showState, setShowState] = useState(1);
const [recordId, setRecordId] = useState(0);
const [loopResultState, setLoopResultState] = useState(false);
const search = useInputValue('');
const [searchString, setSearchString] = useState('');
const appointableData = useCheckAppointable(
userInfo.customerId,
userInfo.areaId,
);
const list = useDeviceList(
userInfo.areaId,
userInfo.customerId,
searchString,
);
const [records, resetRecordList, fetchMoreRecordList] = useAppointRecordsList(
userInfo.customerId,
);
const searchEquipment = () => {
setSearchString(search.value);
};
const appointingEquipmentHandle = (deviceCode: string) => {
Taro.showLoading({
title: '',
mask: true,
});
return appointingEquipment({
account: userInfo.hardwareAccount,
phone: userInfo.customerPhone,
userId: userInfo.customerId,
userName: userInfo.customerName,
deviceCode: deviceCode,
})
.then(res => {
setRecordId(res.data);
})
.catch(err => {
Taro.hideLoading();
Taro.showToast({
title: err.msg || '网络错误',
icon: 'none',
duration: 2000,
});
console.log(err);
});
};
const startLoopFetchResult = () => {
if (loopResultState) {
let intervalTime = 1000;
fetchAppointResult({
userId: userInfo.customerId,
recordId,
})
.then(res => {
if (res.data === AppointResultRes.appointting) {
setTimeout(() => {
startLoopFetchResult();
}, intervalTime);
} else {
setLoopResultState(false);
if (res.data === AppointResultRes.appointFail) {
Taro.showToast({
title: res.msg || '预约失败',
icon: 'none',
duration: 2000,
});
} else if (res.data === AppointResultRes.appointSuccess) {
Taro.showToast({
title: res.msg || '预约成功',
duration: 2000,
});
}
}
})
.catch(err => {
console.log(err);
setTimeout(() => {
startLoopFetchResult();
}, intervalTime);
});
} else {
Taro.showToast({
title: '请求超时',
icon: 'none',
duration: 2000,
});
}
};
useEffect(() => {
if (recordId && showState === 2) {
resetRecordList();
setRecordId(0);
}
}, [recordId, showState]);
useEffect(() => {
let timer: NodeJS.Timeout | null = null;
if (recordId) {
setLoopResultState(true);
timer = setTimeout(() => {
setLoopResultState(false);
}, 30000);
}
return () => {
timer && clearTimeout(timer);
};
}, [recordId]);
useEffect(() => {
if (loopResultState) {
startLoopFetchResult();
}
}, [loopResultState]);
const appointingStart = (
deviceCode: string,
operationMode: number,
location: string,
) => {
const thresholdValue = showerControlConfig.thresholdValue;
if (showerControlConfig.balances.length) {
let aimiItem = showerControlConfig.balances.find(
item => item.serviceId === '0',
);
let aimi = aimiItem ? aimiItem.amount : 0;
let beanItem = showerControlConfig.balances.find(
item => item.serviceId === '1',
);
let bean = beanItem ? beanItem.amount : 0;
let money = operationMode === OperationMode.onlyAimi ? aimi : aimi + bean;
if (money < thresholdValue) {
let i = 0;
let text = showerControlConfig.appointmentThresholdPrompt.replace(
/\{\}/g,
() => {
return i++ == 0 ? aimi.toFixed(2) : bean.toFixed(2);
},
);
Taro.showModal({
title: '提示',
content: text,
}).then(res => {
if (res.confirm) {
beforeStartCheckHandle(deviceCode, location);
}
});
return;
}
}
beforeStartCheckHandle(deviceCode, location);
};
const goBluetoothShower = () => {
Taro.redirectTo({
url: '/pages/Shower/Shower',
});
};
const beforeStartCheckHandle = (deviceCode: string, location: string) => {
let minData = appointableData.duration / 60;
let hourData = appointableData.punishment / 3600;
Taro.showModal({
title: '提示',
content: `确认是否预约${deviceCode}(${location})预约后请在${minData}分钟内激活设备使用,否则将会在${hourData}小时内无法预约设备,`,
}).then(res => {
if (res.confirm) {
appointingEquipmentHandle(deviceCode);
}
});
};
return (
<View className='ShowerAppointment'>
<AppBackButton />
<WaitPayOrderComponent />
{showState == 1 && (
<View className='ShowerAppointment-Searchbox'>
<Input
placeholder='请输入要筛选的设备位置'
value={search.value}
onInput={e => search.onChange(e)}
/>
<Text
className='ShowerAppointment-Searchbox-btn'
onClick={searchEquipment}>
开始筛选
</Text>
</View>
)}
{showState == 1 && showerControlConfig.balances.length && (
<View className='ShowerAppointment-Account'>
{showerControlConfig.balances.map(item => (
<View
key={item.serviceId}
className='ShowerAppointment-Account-item'>
<Text className='name'>{item.serviceName}</Text>
<Text>{item.amount.toFixed(2)}</Text>
</View>
))}
</View>
)}
{showState == 1 && (
<View className='ShowerAppointment-Content'>
<View className='ShowerAppointment-Equipment-title'>设备列表</View>
<View className='ShowerAppointment-Equipment-info'>
{appointableData.message}
</View>
{list.map(item => (
<AppointermentEquipment
key={item.code}
data={item}
disabled={appointableData.appointMode === 3}
appointingStart={appointingStart}
/>
))}
</View>
)}
{showState == 1 && showerControlConfig.useType === ShowerUseType.mix && (
<View
className='ShowerAppointment-ToggleBtn'
onClick={goBluetoothShower}>
自助洗浴
</View>
)}
{showState == 2 && (
<ScrollView
className='ShowerAppointment-AppointmentList'
scrollY
scrollWithAnimation
lowerThreshold={50}
onScrollToLower={fetchMoreRecordList}>
{records.map(item => (
<AppointermentCard key={item.deviceCode} data={item} />
))}
</ScrollView>
)}
<View className='ShowerAppointment-Footer'>
<View
className={`ShowerAppointment-Footer-item ${
showState == 1 ? 'actived' : ''
}`}
onClick={() => setShowState(1)}>
洗浴预约
</View>
<View
className={`ShowerAppointment-Footer-item ${
showState == 2 ? 'actived' : ''
}`}
onClick={() => setShowState(2)}>
预约记录
</View>
</View>
</View>
);
}
ShowerAppointment.onPullDownRefresh = () => {
console.log('in onPullDownRefresh');
};
ShowerAppointment.config = {
navigationBarBackgroundColor: '#486dec',
navigationBarTitleText: '',
backgroundColor: '#486dec',
};
export default ShowerAppointment;
.AppointermentCard {
position: relative;
box-sizing: border-box;
height: 370px;
background-color: #fff;
margin-bottom: 20px;
border-radius: 16px;
padding: 50px 48px 0;
overflow: hidden;
box-shadow: 0 0 5px 0 #eee;
&.disabled {
background-color: #e7e7e7;
.AppointermentCard-Time {
background-color: #e2e2e6;
}
}
.AppointermentCard-Dis-Icon {
position: absolute;
width: 262px;
height: 246px;
right: 0;
bottom: 0;
}
.AppointermentCard-Title {
line-height: 40px;
font-size: 32px;
font-weight: bold;
margin-bottom: 20px;
}
.AppointermentCard-row {
display: flex;
line-height: 36px;
font-size: 24px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 20px;
}
text:first-child {
margin-right: 16px;
}
.AppointermentCard-row-location {
width: 440px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.AppointermentCard-Time {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 96px;
line-height: 96px;
text-align: center;
color: #6180f4;
background-color: #fafafe;
border-radius: 0 0 16px 16px;
text:last-child {
display: inline-block;
margin-left: 20px;
width: 300px;
text-align: left;
}
}
}
import './AppointermentCard.scss';
import { View, Text, Image } from '@tarojs/components';
import DisabledIcon from '../../../../images/shower/img_shixiao@2x.png';
import { AppointmentRecordsResponse } from '@/api/shower';
type PageOwnProps = {
data: AppointmentRecordsResponse;
};
const AppointermentCard = (props: PageOwnProps) => {
const { data } = props;
return (
<View
className={`AppointermentCard ${
data.status === 5 || data.status === 8 ? 'disabled' : ''
}`}>
<View className='AppointermentCard-Title'>我的预约</View>
<View className='AppointermentCard-row'>
<Text>设备编码:</Text>
<Text>{data.deviceCode}</Text>
</View>
<View className='AppointermentCard-row'>
<Text>设备位置:</Text>
<Text className='AppointermentCard-row-location'>
{data.deviceLocation}
</Text>
</View>
<View className='AppointermentCard-row'>
<Text>预约生效时间:</Text>
<Text>{data.appointTime ? data.appointTime : ''}</Text>
</View>
<View className='AppointermentCard-Time'>
<Text>预约失效时间:</Text>
<Text>{data.expireTime ? data.expireTime : ''}</Text>
{/* <Text>{data.expireTime ? data.expireTime : ''}</Text> */}
</View>
{(data.status === 5 || data.status === 8) && (
<Image className='AppointermentCard-Dis-Icon' src={DisabledIcon} />
)}
</View>
);
};
export default AppointermentCard;
.ShowerAppointment-Equipment {
position: relative;
font-size: 28px;
color: #333;
height: 158px;
background-color: #f7f8fe;
border-radius: 16px;
padding: 14px 0 0 32px;
margin-bottom: 20px;
&.disabled {
color: #999;
}
.ShowerAppointment-Equipment-row {
display: flex;
margin-bottom: 6px;
line-height: 44px;
.ShowerAppointment-Equipment-name {
margin-right: 32px;
}
.ShowerAppointment-Equipment-value {
width: 312px;
&.location {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
}
.ShowerAppointment-Equipment-appoint {
position: absolute;
top: 50px;
right: 0;
width: 136px;
height: 68px;
text-align: center;
line-height: 68px;
background-color: #e4f3da;
color: #6bb11a;
font-size: 32px;
font-weight: bold;
border-radius: 16px 0 0 16px;
}
.ShowerAppointment-Equipment-disabled {
position: absolute;
width: 40px;
height: 40px;
right: 46px;
top: 62px;
}
}
import './AppointermentEquipment.scss';
import { View, Text, Image } from '@tarojs/components';
import { AppointDeviceResponse } from '@/api/shower';
import DisabledIcon from '@/images/shower/ic_jinyuyue@2x.png';
type PageOwnProps = {
disabled: boolean;
data: AppointDeviceResponse;
appointingStart: (
deviceCode: string,
operationMode: number,
location: string,
) => void;
};
const AppointermentEquipment = (props: PageOwnProps) => {
const { data, appointingStart, disabled } = props;
const clickHandle = () => {
appointingStart &&
appointingStart(data.code, data.operationMode, data.location);
};
return (
<View
className={`ShowerAppointment-Equipment ${
data.appointable && !data.inPunishment && !disabled ? '' : 'disabled'
}`}>
<View className='ShowerAppointment-Equipment-row'>
<Text className='ShowerAppointment-Equipment-name'>设备编码</Text>
<Text className='ShowerAppointment-Equipment-value'>{data.code}</Text>
</View>
<View className='ShowerAppointment-Equipment-row'>
<Text className='ShowerAppointment-Equipment-name'>设备位置</Text>
<Text className='ShowerAppointment-Equipment-value location'>
{data.location}
</Text>
</View>
{data.appointable && !disabled ? (
<View
className='ShowerAppointment-Equipment-appoint'
onClick={clickHandle}>
可预约
</View>
) : (
<Image
className='ShowerAppointment-Equipment-disabled'
src={DisabledIcon}
/>
)}
</View>
);
};
export default AppointermentEquipment;
import Taro, { useEffect, useState, useReducer } from '@tarojs/taro';
import {
getAppointmentRecords,
AppointmentRecordsResponse,
} from '@/api/shower';
import Actions from '@/types/Store/Actions';
const reducer = (state: AppointmentRecordsResponse[], action: Actions) => {
switch (action.type) {
case 'getRecordList':
return action.payload;
case 'appendRecordList':
return [...state, ...action.payload];
default:
return state;
}
};
const useAppointRecordsList = (
userId: number,
): [AppointmentRecordsResponse[], () => void, () => void] => {
const [pageNum, setPageNum] = useState(1);
const [list, dispatch] = useReducer(reducer, []);
const fetchList = (pageNum: number) => {
return getAppointmentRecords({
userId,
pageSize: 10,
pageNum,
});
};
const fetchMoreList = () => {
Taro.showLoading({
title: '',
mask: true,
});
fetchList(pageNum + 1)
.then(res => {
Taro.hideLoading();
console.log(res);
let list = res.data.list;
if (list.length) {
setPageNum(pageNum + 1);
dispatch({ type: 'appendRecordList', payload: res.data.list });
}
})
.catch(err => {
console.log(err);
Taro.showToast({
title: err.msg || '网络错误',
icon: 'none',
});
});
};
const fetchNewList = () => {
Taro.showLoading();
fetchList(1)
.then(res => {
Taro.hideLoading();
console.log(res);
dispatch({ type: 'getRecordList', payload: res.data.list });
})
.catch(err => {
console.log(err);
Taro.showToast({
title: err.msg || '网络错误',
icon: 'none',
});
});
};
useEffect(() => {
fetchNewList();
}, [userId]);
return [list, fetchNewList, fetchMoreList];
};
export default useAppointRecordsList;
import { useEffect, useReducer } from '@tarojs/taro';
import { getUserAppointableData, UserAppointableData } from '@/api/shower';
import Actions from '@/types/Store/Actions';
const reducer = (state: UserAppointableData, action: Actions) => {
switch (action.type) {
case 'getAppointData':
return action.payload;
default:
return state;
}
};
const initState: UserAppointableData = {
appointMode: 0,
defaultType: 0,
duration: 0,
message: '',
punishment: 0,
useType: 0,
userPunishment: 0,
};
const useCheckAppointable = (
userId: number,
campusId: number,
): UserAppointableData => {
const [data, dispatch] = useReducer(reducer, initState);
useEffect(() => {
getUserAppointableData({
userId,
campusId,
})
.then(res => {
console.log(res);
dispatch({ type: 'getAppointData', payload: res.data });
})
.catch(err => {
console.log(err);
});
}, [userId, campusId]);
return data;
};
export default useCheckAppointable;
import Taro, { useReducer, useEffect } from '@tarojs/taro';
import Actions from '@/types/Store/Actions';
import { getAppointmentDevice, AppointDeviceResponse } from '@/api/shower';
const reducer = (state: AppointDeviceResponse[], action: Actions) => {
switch (action.type) {
case 'updateDeviceList':
return action.payload;
default:
return state;
}
};
const useDeviceList = (
campusId: number,
userId: number,
location: string,
): AppointDeviceResponse[] => {
const [list, dispatch] = useReducer(reducer, []);
// const fetchList = (pageNum: number) => {
// getAppointmentDevice({ location, campusId, userId, pageNum })
// .then(res => {
// Taro.hideLoading();
// dispatch({ type: 'updateDeviceList', payload: res.data.list });
// })
// .catch(err => {
// Taro.showToast({
// title: err.msg || '网络错误',
// icon: 'none',
// });
// });
// };
// const fetchNewList = () => {};
// const fetchMoreList = () => {};
useEffect(() => {
Taro.showLoading();
getAppointmentDevice(
location ? { location, campusId, userId } : { campusId, userId },
)
.then(res => {
Taro.hideLoading();
dispatch({ type: 'updateDeviceList', payload: res.data.list });
})
.catch(err => {
Taro.showToast({
title: err.msg || '网络错误',
icon: 'none',
});
});
}, [campusId, userId, location]);
return list;
};
export default useDeviceList;
.CustomerSetting {
padding: 10px 32px;
.CustomerSettingItem {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 28px;
line-height: 80px;
color: #333;
.CustomerSetting-icon {
width: 15px;
color: #999;
font-family: serif;
font-weight: bolder;
}
}
.CustomerSettingItem-switch {
zoom: 0.7;
}
.CustomerSetting-buttom {
padding-top: 40px;
}
}
import './CustomerSetting.scss';
import Taro from '@tarojs/taro';
import { View, Text, Switch } from '@tarojs/components';
import { useSelector } from '@tarojs/redux';
import { Customer } from '@/types/Customer/Customer';
import useAccountLogin from '../Password/useAccountLogin';
import useBalanceState from '../Password/useBalanceState';
const CustomerSetting = () => {
const userinfo = useSelector(
(state: { userinfo: Customer }) => state.userinfo,
);
const goChangePwd = () => {
Taro.navigateTo({
url: '/pages/Password/ChangePwd',
});
};
const goChangeAccount = () => {
Taro.navigateTo({
url: '/pages/Password/ChangeHardwareAccount',
});
};
const goChangePhoneAccount = () => {
Taro.navigateTo({
url: '/pages/Password/ChangeTelAccount',
});
};
const [accountLoginState, setAccountLoginState] = useAccountLogin(
userinfo.customerId,
userinfo.hardwareState,
);
const [balanceState, setBalanceState] = useBalanceState(userinfo.customerId);
return (
<View className='CustomerSetting'>
<View className='CustomerSettingItem' onClick={goChangePwd}>
<Text className='CustomerSettingItem-label'>登录密码修改</Text>
<Text className='CustomerSetting-icon'>></Text>
</View>
<View className='CustomerSettingItem' onClick={goChangeAccount}>
<Text className='CustomerSettingItem-label'>设备密码修改</Text>
<Text className='CustomerSetting-icon'>></Text>
</View>
<View className='CustomerSettingItem' onClick={goChangePhoneAccount}>
<Text className='CustomerSettingItem-label'>更换手机账户</Text>
<Text className='CustomerSetting-icon'>></Text>
</View>
<View className='CustomerSetting-buttom'>
<View className='CustomerSettingItem'>
<Text className='CustomerSettingItem-label'>开启设备登录密码</Text>
<Switch
className='CustomerSettingItem-switch'
color='#6180f4'
checked={accountLoginState}
onChange={e => setAccountLoginState(e.detail.value)}
/>
</View>
<View className='CustomerSettingItem'>
<Text className='CustomerSettingItem-label'>开启系统自动扣费</Text>
<Switch
className='CustomerSettingItem-switch'
color='#6180f4'
checked={balanceState}
onChange={e => setBalanceState(e.detail.value)}
/>
</View>
</View>
</View>
);
};
CustomerSetting.config = {
navigationBarTitleText: '个人设置',
};
export default CustomerSetting;
......@@ -4,6 +4,7 @@ import OrderList from '../pages/Order/OrderList/store';
import ShowerReducer from '@/pages/Shower/store';
import serviceList from './rootReducers/service';
import orderState from './rootReducers/orderState';
import showerState from './rootReducers/shower';
export default combineReducers({
userinfo,
......@@ -11,4 +12,5 @@ export default combineReducers({
Shower: ShowerReducer,
serviceList,
orderState,
showerState,
});
import Actions from '@/types/Store/Actions';
import { ControllerResponse } from '@/api/shower';
export type ShowerState = {
controllerConfigs: ControllerResponse;
};
const INITIAL_STATE = {
controllerConfigs: {
appointmentThresholdPrompt: '',
balances: [],
beanAmount: 0,
defaultType: 0,
money: 0,
thresholdPrompt: '',
thresholdValue: 0,
useType: 0,
},
};
export const updateShowerControlConfig = (
entity: ControllerResponse,
): Actions => ({
type: 'UPDATE_SHOWER_CONFIG',
payload: entity,
});
export default function shower(
state: ShowerState = INITIAL_STATE,
actions: Actions,
): ShowerState {
switch (actions.type) {
case 'UPDATE_SHOWER_CONFIG':
return {
...state,
controllerConfigs: actions.payload,
};
default:
return state;
}
}
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