Commit c29c6d6f authored by 陈锐涵's avatar 陈锐涵
Browse files

Merge branch 'release' into 'master'

chore: 发布 v1.7.0

See merge request op/xinghuo-business/xinghuo-frontend/c-end-mweb/xh-contract-online-operation!39
parents 601ddae6 8b43813b
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /home/www/;
index index.html index.htm;
if ($request_filename ~* index\.html$) { add_header Cache-Control "no-store, no-cache";}
try_files $uri $uri/ /index.html;
}
# CSS, Javascript and other static files
location ~* \.(?:css|js|png|jpg|eot|svg|ttf|woff|woff2|map)$ {
root /home/www/;
expires 1h;
access_log off;
}
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /home/www/;
index index.html index.htm;
if ($request_filename ~* index\.html$) { add_header Cache-Control "no-store, no-cache";}
try_files $uri $uri/ /index.html;
}
# CSS, Javascript and other static files
location ~* \.(?:css|js|png|jpg|eot|svg|ttf|woff|woff2|map)$ {
root /home/www/;
expires 1h;
access_log off;
}
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /home/www/;
index index.html index.htm;
if ($request_filename ~* index\.html$) { add_header Cache-Control "no-store, no-cache";}
try_files $uri $uri/ /index.html;
}
# CSS, Javascript and other static files
location ~* \.(?:css|js|png|jpg|eot|svg|ttf|woff|woff2|map)$ {
root /home/www/;
expires 1h;
access_log off;
}
}
# static
PUBLIC_URL=/contract-online-operation/
PUBLIC_URL=/contract-online-operation/xh/
# route base
REACT_APP_BASE_NAME=contract-online-operation
......
......@@ -5,18 +5,25 @@ LABEL maintainer="tanweijiu@xiao100.com"
ARG env=prd
ENV env=${env:-prd}
WORKDIR /home/repo/contract-online-operation
WORKDIR /home/repo/xh-contract-online-operation
COPY package*.json ./
COPY yarn*.lock ./
RUN yarn
RUN yarn install
COPY . .
RUN yarn run build:${env}
RUN yarn run build:${env} -d
# 简化镜像大小
FROM registry-vpc.cn-shenzhen.aliyuncs.com/xiaodevops/base-openresty:alpine-log
COPY --from=builder /home/repo/contract-online-operation/build /home/repo/contract-online-operation/build
ARG env=prd
ENV env=${env:-prd}
ENV NGINX_CONF_PATH="/etc/nginx/conf.d"
# 复制nginx配置
COPY .docker/nginx/${env}.conf ${NGINX_CONF_PATH}/default.conf
COPY --from=builder /home/repo/xh-contract-online-operation/build /home/www
......@@ -42,3 +42,12 @@ You don’t have to ever use `eject`. The curated feature set is suitable for sm
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Link Refs
1. 晓教育[用户协议+隐私政策](https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-all.html)
2. 晓教育[用户协议](https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-user.html)
3. 晓教育[隐私政策](https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-privacy.html)
{
"name": "contract-online-operation",
"version": "1.6.0",
"name": "xh-contract-online-operation",
"version": "1.7.0",
"private": true,
"dependencies": {
"@army-knife/idcard-validation": "git+ssh://git@gitlab.xinghuolive.com:op/frontend-common/fancy-utils/idcard-validation.git#semver:^1.0",
......
import React, { useEffect, useMemo, useState } from 'react';
import React, { ChangeEvent, useEffect, useMemo, useState } from 'react';
import XiaoTag from '@shared/components/XiaoTag';
import SvgIcon from '@shared/components/SvgIcon';
import { useHistory, useParams } from 'react-router-dom';
import { getInfoCheckingPath, getOrderDownloadPath, getPdfPreviewPath, getSignSuccessPath } from '@shared/services/routePath';
import OrderUsecase from '@shared/domain/order';
import { ConfirmStatus, IOrder, PaidStatus } from '@shared/domain/order/typings';
import { ActivityIndicator, Loading, Modal } from 'zarm';
import { ActivityIndicator, Checkbox, Loading, Modal, Toast } from 'zarm';
import { formatPrice } from '@shared/utils/price';
import { DEFAULT_TEXT, KEY_CONTRACT_CONFIRM_TYPE, KEY_OPERATION_MODE } from '@shared/constants';
import { BrandName, getBrandStorage, getLocalStorageWithBrand } from '@shared/services/storage';
......@@ -21,10 +21,11 @@ import ContractUsecase from '@shared/domain/contract';
const ContractOrderList = () => {
const history = useHistory();
const { batchId } = useParams<{batchId: string}>();
const [orderList, setOrderList] = useState<IOrder[]>([])
const [isPaying, setIsPaying] = useState<boolean>(false)
const [orderList, setOrderList] = useState<IOrder[]>([]);
const [isPaying, setIsPaying] = useState<boolean>(false);
const [isCheckPolicy, setCheckPolicy] = useState(true);
/** 合同操作类型 */
const operationMode = useMemo(() => {
return getLocalStorageWithBrand(KEY_OPERATION_MODE) as OperationMode
......@@ -133,10 +134,29 @@ const ContractOrderList = () => {
history.push(getInfoCheckingPath(batchId))
}
const onPolicyCheckChange = (e?: ChangeEvent<HTMLInputElement>) => {
setCheckPolicy(!isCheckPolicy);
};
/**
* 判断当前报名须知是否勾选了
* 确认+付款的引流合同才需要报名须知
*/
const checkNoticeRegNeedAndHasCheck = () => {
return !(
shouldShowBtnConfirm
&& contractConfirmType === ContractConfirmType.YINLIU_CONTRACT
) || isCheckPolicy;
}
/**
* 点击确认订单信息-批量确认
*/
const confirmContractSubmit = () => {
if (!checkNoticeRegNeedAndHasCheck()) {
Toast.show('请勾选用户须知!');
return;
}
Loading.show({
content: <ActivityIndicator size="lg" />,
});
......@@ -202,8 +222,23 @@ const ContractOrderList = () => {
})
}
const PolicyCheck = () => {
return (
contractConfirmType === ContractConfirmType.YINLIU_CONTRACT ? (
<>
<p className={s.policyArea}>
<Checkbox checked={isCheckPolicy} onChange={onPolicyCheckChange}></Checkbox>
<span className={s.policyText}>已阅读并同意
<a href='http://xiao100.cn/AuDG'>《星火教育体验课程与教学服务报名须知》</a>
</span>
</p>
</>
) : null
);
};
return (
<div>
<div className={s.contractOrderListWrap}>
{orderList.map(order => (
<div className={s.orderCard} key={order.contractId}>
<h4 className={s.orderCardHeader}>
......@@ -359,14 +394,18 @@ const ContractOrderList = () => {
{shouldShowBtnConfirm ? (
<>
{
contractConfirmType === ContractConfirmType.ECS_CONTRACT ? (
<button onClick={confirmContractSubmit}>确认订单信息</button>
) : (
<button onClick={goToSigningPage}>去签合同</button>
)
contractConfirmType === ContractConfirmType.ECS_CONTRACT || contractConfirmType === ContractConfirmType.YINLIU_CONTRACT
? (
<>
<PolicyCheck></PolicyCheck>
<button onClick={confirmContractSubmit}>确认订单信息</button>
</>
) : (
<button onClick={goToSigningPage}>去签合同</button>
)
}
</>
) : shouldShowPay && (
) : shouldShowPay && (
<>
{orderList[0].internetPayPaidStatus === PaidStatus.Invalid ? (
<button disabled>无法支付</button>
......
......@@ -58,6 +58,10 @@
}
}
.contractOrderListWrap{
padding-bottom: 60px;
}
.orderCard {
padding: 0 16px;
margin-top: 12px;
......@@ -200,7 +204,29 @@
// 底部按钮区域
.btnArea {
@include bottomFixedArea();
padding: 12px 0;
box-sizing: border-box;
button {
@include themeButton();
margin: 0 12px;
width: calc(100% - 24px);
}
//协议区域
.policyArea {
font-size: 3.47vw;
padding: 14px 2px 16px 2px;
display: flex;
align-items: center;
justify-content: center;
a {
color: #668DF3;
}
.policyText {
margin-left: 2.2vw;
}
}
}
\ No newline at end of file
......@@ -44,7 +44,7 @@ const SignSuccess = () => {
}, [batchId])
useEffect(() => {
if (contractConfirmType === ContractConfirmType.ECS_CONTRACT) {
if (contractConfirmType === ContractConfirmType.ECS_CONTRACT || contractConfirmType === ContractConfirmType.YINLIU_CONTRACT) {
if (document) document.title = '签合同';
}
}, [contractConfirmType]);
......@@ -81,6 +81,7 @@ const SignSuccess = () => {
<SvgIcon id='icon-chenggong' />
{
contractConfirmType === ContractConfirmType.ECS_CONTRACT
|| contractConfirmType === ContractConfirmType.YINLIU_CONTRACT
? (
<>
<h2>订单信息确认成功</h2>
......@@ -98,7 +99,7 @@ const SignSuccess = () => {
<p>待付金额:¥<strong>{payableAmount}</strong></p>
)}
<button onClick={goToOrderList}>{
payableAmount > 0 ? '去付款' : '订单列表'
payableAmount > 0 ? '去付款' : '回订单列表'
}</button>
</div>
</div>
......
......@@ -8,7 +8,7 @@ import VerifyUsecase from '@shared/domain/verify';
import { ConfirmChannelEnum, LoginMethod } from '@shared/domain/verify/typings';
import { setTokenHeader } from '@shared/services/fetch';
import { getLocalStorageWithBrand, setLocalStorageWithBrand } from '@shared/services/storage';
import { KEY_CONTRACT_CONFIRM_TYPE, KEY_OPERATION_MODE, KEY_TOKEN } from '@shared/constants';
import { KEY_CONTRACT_CONFIRM_TYPE, KEY_HAS_CHECK_POLICY, KEY_OPERATION_MODE, KEY_TOKEN } from '@shared/constants';
import { handleError } from '@shared/services/errorHandler';
import s from './verify-code.module.scss';
import { useDomMounted } from '@shared/hooks/useDomMounted';
......@@ -83,14 +83,17 @@ const VerifyCode = () => {
}, []);
const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value
// console.log("input change ===>", val)
setVerifyCode(val)
if (val.length === 6) {
login(val)
}
const val = e.target.value;
if (isLogining || val.length>6) return;
setVerifyCode(val);
}
useEffect(() => {
if (verifyCode && verifyCode.length === 6) {
login(verifyCode);
}
}, [verifyCode]);
const sendCode = () => {
if (isSending) return true
setIsSending(true)
......@@ -138,6 +141,8 @@ const VerifyCode = () => {
// 保存 token 到 localStorage 和 request header
setTokenHeader(res);
setLocalStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`, `true`);
if (confirmChannel === ConfirmChannelEnum.History) {
history.replace(getHistOrderListPath(batchId));
} else {
......@@ -156,14 +161,20 @@ const VerifyCode = () => {
Loading.hide()
})
.catch(e => {
setIsLogining(false)
setVerifyCode('') // 清空输入
if (e.message) {
Modal.alert({
content: '验证码输入错误,请重新输入',
cancelText: '我知道了',
})
onCancel: () => {
setIsLogining(false)
return true
},
});
}else{
setIsLogining(false)
}
setVerifyCode('') // 清空输入
console.log("登录失败 ==>", e.message || e)
Loading.hide()
......
import React, { useEffect, useMemo, useState } from 'react'
import React, { ChangeEvent, useEffect, useMemo, useState } from 'react';
import { getOSSImage } from '@shared/services/image'
import { getHiddenPhoneNum } from '@shared/utils'
import { checkMobile } from '@shared/utils/check'
import { Toast } from 'zarm'
import { Checkbox, Toast } from 'zarm';
import s from './verify-index.module.scss'
import { useHistory, useParams, useRouteMatch } from 'react-router-dom'
import VerifyUsecase from '@shared/domain/verify'
import { IContractUserInfo } from '@shared/domain/verify/typings'
import { stringify } from 'querystring'
import { handleError } from '@shared/services/errorHandler'
import { RES_BATCH_NOT_FOUND_CODE } from '@shared/constants'
import { getInvalidPath } from '@shared/services/routePath'
import { KEY_HAS_CHECK_POLICY, PolicyPrivacyLink, PolicyUserLink, RES_BATCH_NOT_FOUND_CODE } from '@shared/constants';
import { getInvalidPath } from '@shared/services/routePath';
import { BrandName, getBrandStorage, getLocalStorageWithBrand, getSessionStorageWithBrand, setLocalStorageWithBrand, setSessionStorageWithBrand } from '@shared/services/storage';
const VerifyIndex = () => {
const history = useHistory()
const { url } = useRouteMatch()
const { batchId } = useParams<{batchId: string}>()
const [isCheckPolicy, setCheckPolicy] = useState(false);
const [isInSS, setInSS] = useState(false);
const [phoneNum, setPhoneNum] = useState<string>('')
const [userInfo, setUserInfo] = useState<IContractUserInfo>(
{} as IContractUserInfo
......@@ -52,11 +54,35 @@ const VerifyIndex = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// 处理过已经勾选过同意协议的情况
useEffect(() => {
const hasCheckPolicy = 'true' === getSessionStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`)
|| 'true' === getLocalStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`);
const isInSS = getBrandStorage() === BrandName.SS;
setInSS(isInSS);
setCheckPolicy(hasCheckPolicy || isInSS);
}, [phoneNum]);
const onPolicyCheckChange = (e?: ChangeEvent<HTMLInputElement>) => {
if (isCheckPolicy) {
// 登录成功才默认勾选
setLocalStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`, 'false');
}
setSessionStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`, `${!isCheckPolicy}`)
setCheckPolicy(!isCheckPolicy);
};
const checkAndLogin = () => {
if (!checkMobile(phoneNum)) {
Toast.show("合同手机格式不正确,请联系校区");
return
}
if (!isCheckPolicy) {
Toast.show('请您先阅读并同意《用户协议》及《隐私政策》');
return;
}
history.push(`${url}/code?${stringify({ ...userInfo })}`)
}
......@@ -74,9 +100,24 @@ const VerifyIndex = () => {
<span>{phoneStr}</span>
</div>
<button
className={s.btnLogin}
className={isCheckPolicy ? s.btnLogin : s.btnLoginDisabled}
onClick={checkAndLogin}
>登录</button>
{
!isInSS ? (
<p className={s.policyArea}>
<Checkbox id='policy-cb' checked={isCheckPolicy} onChange={onPolicyCheckChange}></Checkbox>
<label htmlFor='policy-cb'>
<span className={s.policyText}>已阅读并同意
<a href={PolicyUserLink}>《用户协议》</a>
<a href={PolicyPrivacyLink}>《隐私政策》</a>
</span>
</label>
</p>
) : null
}
</div>
)
}
......
......@@ -39,4 +39,27 @@
.btnLogin {
margin-top: 20px;
@include themeButton();
}
.btnLoginDisabled{
margin-top: 20px;
@include themeButton();
background: $disabled-color;
}
//协议区域
.policyArea {
font-size: 3.73vw;
padding: 20px 0;
display: flex;
align-items: center;
justify-content: center;
a {
color: #668DF3;
}
.policyText {
margin-left: 2.2vw;
}
}
\ No newline at end of file
......@@ -5,7 +5,7 @@ import { Loading, ActivityIndicator, Modal, Toast } from 'zarm';
import { useQuery } from '@shared/hooks/useQuery';
import { getHiddenPhoneNum } from '@shared/utils';
import { useDomMounted } from '@shared/hooks/useDomMounted';
import { KEY_TOKEN } from '@shared/constants';
import { KEY_HAS_CHECK_POLICY, KEY_TOKEN } from '@shared/constants';
import { handleError } from '@shared/services/errorHandler';
import { updateTokenHeader } from '@shared/services/fetch/cEndRequest';
import { setLocalStorageWithBrand } from '@shared/services/storage';
......@@ -83,6 +83,8 @@ const VerifyCode = () => {
validCode: code
});
setLocalStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`, `true`);
if (accessToken) {
// setLocalStorageWithBrand(KEY_TOKEN, accessToken);
// 保存 token 到 localStorage 和 request header
......
import React, { useEffect, useMemo, useState } from 'react';
import React, { ChangeEvent, useEffect, useMemo, useState } from 'react';
import { useParams, useRouteMatch } from 'react-router-dom';
import { Toast, Loading, ActivityIndicator } from 'zarm';
import { Toast, Loading, ActivityIndicator, Checkbox } from 'zarm';
import { stringify } from 'querystring';
import { getOSSImage } from '@shared/services/image';
import { getHiddenPhoneNum } from '@shared/utils';
......@@ -13,11 +13,13 @@ import { ICustBindingVerifInfo } from '@shared/domain/student/typings';
import { useRedirection } from '../../../hooks/userRedirection';
import s from './verify-index.module.scss';
import { KEY_HAS_CHECK_POLICY, PolicyPrivacyLink, PolicyUserLink } from '@shared/constants';
import { getLocalStorageWithBrand, getSessionStorageWithBrand, setLocalStorageWithBrand, setSessionStorageWithBrand } from '@shared/services/storage';
const VerifyIndex = () => {
const { url } = useRouteMatch();
const { recordId } = useParams<{ recordId: string }>();
const [isCheckPolicy, setCheckPolicy] = useState(false);
const [isInit, setIsInit] = useState(false);
const [loading, setLoading] = useState(true);
......@@ -76,6 +78,22 @@ const VerifyIndex = () => {
}
}, [recordId]);
// 处理过已经勾选过同意协议的情况
useEffect(() => {
const hasCheckPolicy = 'true' === getSessionStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`)
|| 'true' === getLocalStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`);
setCheckPolicy(hasCheckPolicy);
}, [phoneNum]);
const onPolicyCheckChange = (e?: ChangeEvent<HTMLInputElement>) => {
if (isCheckPolicy) {
// 登录成功才默认勾选
setLocalStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`, 'false');
}
setSessionStorageWithBrand(`${KEY_HAS_CHECK_POLICY}-${phoneNum}`, `${!isCheckPolicy}`)
setCheckPolicy(!isCheckPolicy);
};
/**
* 登录操作
*/
......@@ -85,6 +103,11 @@ const VerifyIndex = () => {
return;
}
if (!isCheckPolicy) {
Toast.show('请您先阅读并同意《用户协议》及《隐私政策》');
return;
}
nextToCode();
};
......@@ -101,9 +124,20 @@ const VerifyIndex = () => {
<span className={s.separator}></span>
<span>{phoneStr}</span>
</div>
<button className={s.btnLogin} onClick={checkAndLogin}>
<button className={isCheckPolicy ? s.btnLogin : s.btnLoginDisabled} onClick={checkAndLogin}>
登录
</button>
<p className={s.policyArea}>
<Checkbox id='policy-cb' checked={isCheckPolicy} onChange={onPolicyCheckChange}></Checkbox>
<label htmlFor="policy-cb">
<span className={s.policyText}>已阅读并同意
<a href={PolicyUserLink}>《用户政策》</a>
<a href={PolicyPrivacyLink}>《隐私协议》</a>
</span>
</label>
</p>
<Loading visible={loading} mask content={<ActivityIndicator size="lg" />} />
</div>
);
......
......@@ -39,4 +39,27 @@
.btnLogin {
margin-top: 20px;
@include themeButton();
}
.btnLoginDisabled{
margin-top: 20px;
@include themeButton();
background: $disabled-color;
}
//协议区域
.policyArea {
font-size: 3.73vw;
padding: 20px 0;
display: flex;
align-items: center;
justify-content: center;
a {
color: #668DF3;
}
.policyText {
margin-left: 2.2vw;
}
}
\ No newline at end of file
......@@ -44,3 +44,19 @@ export const PDF_PREVIEW_URL = process.env.REACT_APP_PDF_PREVIEW_URL as string;
// sentry
export const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN as string;
/**
* 链接: 用户协议+隐私政策
* @type {"https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-all.html"}
*/
export const PolicyAllLink='https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-all.html'
/**
* 链接: 用户协议
* @type {"https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-user.html"}
*/
export const PolicyUserLink='https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-user.html'
/**
* 链接: 隐私政策
* @type {"https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-privacy.html"}
*/
export const PolicyPrivacyLink='https://xiao-zh-cdn.xiao100.com/xhboss/xh-mobile/c-end/html/policy-privacy.html'
......@@ -17,7 +17,13 @@ export const THEME_COLOR = '#a764fe'
export const BASE_NAME = process.env.REACT_APP_BASE_NAME as string;
/** 默认缺省文本 */
export const DEFAULT_TEXT = '-'
export const DEFAULT_TEXT = '-';
/**
* 《用户协议》及《隐私政策》勾选状态
* @type {string}
*/
export const KEY_HAS_CHECK_POLICY="has_check_policy"
/** localStorage */
// with brand 有品牌前缀的
......
export * from './typings'
export * from './localStorage'
export * from './localStorageWithBrand'
\ No newline at end of file
export * from './localStorageWithBrand'
export * from './sessionStorageWithBrand'
\ No newline at end of file
import { generateKey } from "./utils"
export const setSessionStorageWithBrand = (key: string, val: string) => {
sessionStorage.setItem(generateKey(key), val)
}
export const getSessionStorageWithBrand = (key: string) => {
return sessionStorage.getItem(generateKey(key))
}
export const removeSessionStorageWithBrand = (key: string) => {
return sessionStorage.removeItem(generateKey(key))
}
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