mirror of
https://github.com/haiwen/seahub.git
synced 2025-07-15 16:04:01 +00:00
525 lines
18 KiB
JavaScript
525 lines
18 KiB
JavaScript
import React, { Component, Fragment } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import toaster from './toast';
|
|
import { Modal, ModalHeader, ModalBody, ModalFooter, InputGroup, InputGroupAddon, InputGroupText, Input, Button } from 'reactstrap';
|
|
import { gettext, serviceURL } from '../utils/constants';
|
|
import { Utils } from '../utils/utils';
|
|
import { subscriptionAPI } from '../utils/subscription-api';
|
|
import Loading from './loading';
|
|
|
|
import '../css/layout.css';
|
|
import '../css/subscription.css';
|
|
|
|
const {
|
|
isOrgContext,
|
|
} = window.app.pageOptions;
|
|
|
|
|
|
const PlansPropTypes = {
|
|
plans: PropTypes.array.isRequired,
|
|
onPay: PropTypes.func.isRequired,
|
|
paymentType: PropTypes.string.isRequired,
|
|
handleContentScroll: PropTypes.func.isRequired,
|
|
};
|
|
|
|
class Plans extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
currentPlan: props.plans[0],
|
|
assetQuotaUnitCount: 1,
|
|
count: 1,
|
|
};
|
|
}
|
|
|
|
togglePlan = (plan) => {
|
|
this.setState({ currentPlan: plan }, () => {
|
|
});
|
|
};
|
|
|
|
onPay = () => {
|
|
let { paymentType } = this.props;
|
|
let { currentPlan, assetQuotaUnitCount, count } = this.state;
|
|
let totalAmount; let assetQuota; let newUserCount;
|
|
|
|
// parse
|
|
if (paymentType === 'paid') {
|
|
newUserCount = currentPlan.count;
|
|
totalAmount = currentPlan.total_amount;
|
|
} else if (paymentType === 'extend_time') {
|
|
newUserCount = currentPlan.count;
|
|
assetQuota = currentPlan.asset_quota;
|
|
totalAmount = currentPlan.total_amount;
|
|
} else if (paymentType === 'add_user') {
|
|
newUserCount = count;
|
|
totalAmount = count * currentPlan.price_per_user;
|
|
} else if (paymentType === 'buy_quota') {
|
|
assetQuota = (assetQuotaUnitCount) * currentPlan.asset_quota_unit;
|
|
totalAmount = assetQuotaUnitCount * currentPlan.price_per_asset_quota_unit;
|
|
} else {
|
|
toaster.danger(gettext('Internal Server Error.'));
|
|
return;
|
|
}
|
|
|
|
this.props.onPay(currentPlan.plan_id, newUserCount, assetQuota, totalAmount);
|
|
};
|
|
|
|
onCountInputChange = (e) => {
|
|
let { currentPlan } = this.state;
|
|
if (!currentPlan.can_custom_count) {
|
|
return;
|
|
}
|
|
let count = e.target.value.replace(/^(0+)|[^\d]+/g, '');
|
|
if (count < 1) {
|
|
count = 1;
|
|
} else if (count > 9999) {
|
|
count = 9999;
|
|
}
|
|
this.setState({ count: count });
|
|
};
|
|
|
|
onAssetQuotaUnitCountInputChange = (e) => {
|
|
let { currentPlan } = this.state;
|
|
if (!currentPlan.can_custom_asset_quota) {
|
|
return;
|
|
}
|
|
let count = e.target.value.replace(/^(0+)|[^\d]+/g, '');
|
|
if (count < 1) {
|
|
count = 1;
|
|
} else if (count > 9999) {
|
|
count = 9999;
|
|
}
|
|
this.setState({ assetQuotaUnitCount: count });
|
|
};
|
|
|
|
renderPaidOrExtendTime = () => {
|
|
let { plans, paymentType } = this.props;
|
|
let { currentPlan } = this.state;
|
|
let boughtQuota = 0;
|
|
if (paymentType === 'extend_time') {
|
|
boughtQuota = currentPlan.asset_quota - 100;
|
|
}
|
|
let totalAmount = currentPlan.total_amount;
|
|
let originalTotalAmount = totalAmount;
|
|
return (
|
|
<div className='d-flex flex-column subscription-container'>
|
|
<span className="subscription-subtitle">{'选择方案'}</span>
|
|
<dl className='items-dl'>
|
|
{plans.map((item, index) => {
|
|
let selectedCss = item.plan_id === currentPlan.plan_id ? 'plan-selected' : '';
|
|
let countDescription = '¥' + item.price_per_user;
|
|
if (isOrgContext) {
|
|
countDescription += '/每用户';
|
|
}
|
|
return (
|
|
<dd key={index} className={`plan-description-item ${selectedCss}`} onClick={this.togglePlan.bind(this, item)}>
|
|
<span className='plan-name'>{item.name}</span>
|
|
<span className='plan-description'>{countDescription}</span>
|
|
</dd>
|
|
);
|
|
})}
|
|
</dl>
|
|
|
|
{paymentType === 'extend_time' && boughtQuota > 0 &&
|
|
<Fragment>
|
|
<span className="subscription-subtitle">{'增加空间'}</span>
|
|
<dl className='items-dl'>
|
|
<dd className='order-item order-item-top order-item-bottom subscription-list'>
|
|
<span className='order-into'>{currentPlan.asset_quota_unit + 'GB x ' + (boughtQuota / currentPlan.asset_quota_unit)}</span>
|
|
{/* 续费时候需要减去附赠的100GB */}
|
|
<span className='order-value'>{'¥' + (boughtQuota / currentPlan.asset_quota_unit) * currentPlan.price_per_asset_quota_unit}</span>
|
|
</dd>
|
|
</dl>
|
|
</Fragment>
|
|
}
|
|
|
|
<span className="subscription-subtitle">{'方案汇总'}</span>
|
|
<dl className='items-dl'>
|
|
<div>
|
|
<dd className='order-item order-item-top'>
|
|
<span className='order-into'>{'所选方案'}</span>
|
|
<span className='order-value'>{currentPlan.name}</span>
|
|
</dd>
|
|
{isOrgContext &&
|
|
<dd className='order-item'>
|
|
<span className='order-into'>{'成员人数'}</span>
|
|
<span className='order-value'>{currentPlan.count + '人'}</span>
|
|
</dd>
|
|
}
|
|
<dd className='order-item'>
|
|
<span className='order-into'>{'可用空间'}</span>
|
|
<span className='order-value'>{'100GB(附赠)' + (boughtQuota > 0 ? '+' + boughtQuota + 'GB(扩充)' : '')}</span>
|
|
</dd>
|
|
<dd className='order-item order-item-bottom rounded-0'>
|
|
<span className='order-into'>{'到期时间'}</span>
|
|
<span className='order-value'>{currentPlan.new_term_end}</span>
|
|
</dd>
|
|
<dd className='order-item order-item-bottom subscription-list'>
|
|
<span className='order-into'>{'实际支付金额'}</span>
|
|
<span className='order-price'>
|
|
{originalTotalAmount !== totalAmount &&
|
|
<span style={{ fontSize: 'small', textDecoration: 'line-through', color: '#9a9a9a' }}>{'¥' + originalTotalAmount}</span>
|
|
}
|
|
<span>{'¥' + totalAmount + ' '}</span>
|
|
</span>
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
<Button className='subscription-submit' color="primary" onClick={this.onPay}>{'提交订单'}</Button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
renderAddUser = () => {
|
|
let { currentPlan, count } = this.state;
|
|
let operationIntro = '新增用户';
|
|
let originalTotalAmount = count * currentPlan.price_per_user;
|
|
let totalAmount = originalTotalAmount;
|
|
return (
|
|
<div className='d-flex flex-column subscription-container price-version-container-header subscription-add-user'>
|
|
<div className="price-version-container-top"></div>
|
|
<h3 className='user-quota-plan-name py-5'>{currentPlan.name}</h3>
|
|
<span className='py-2 mb-0 text-orange font-500 text-center'>
|
|
{'¥ '}<span className="price-version-plan-price">{currentPlan.price}</span>{' ' + currentPlan.description}
|
|
</span>
|
|
<InputGroup style={{ marginBottom: '5px' }} className='user-numbers'>
|
|
<InputGroupAddon addonType="prepend">
|
|
<InputGroupText>{operationIntro}</InputGroupText>
|
|
</InputGroupAddon>
|
|
<Input
|
|
className="py-2"
|
|
placeholder={operationIntro}
|
|
title={operationIntro}
|
|
type="number"
|
|
value={count || 1}
|
|
min="1"
|
|
max="9999"
|
|
disabled={!currentPlan.can_custom_count}
|
|
onChange={this.onCountInputChange}
|
|
/>
|
|
</InputGroup>
|
|
<span className='py-2 text-orange mb-0 font-500 price-version-plan-whole-price text-center'>
|
|
{'总价 ¥ ' + totalAmount}
|
|
{originalTotalAmount !== totalAmount &&
|
|
<span style={{ fontSize: 'small', textDecoration: 'line-through', color: '#9a9a9a' }}>{' ¥' + originalTotalAmount}</span>
|
|
}
|
|
</span>
|
|
<span className='py-2 mb-0 text-lg-size font-500 price-version-plan-valid-day text-center'>{'有效期至 ' + currentPlan.new_term_end}</span>
|
|
<span className='subscription-notice text-center py-5'>{'注:当有效期剩余天数少于计划中的时候,增加用户的价格按天来计算'}</span>
|
|
<Button className='subscription-submit' onClick={this.onPay} color="primary">{'立即购买'}</Button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
renderBuyQuota = () => {
|
|
let { currentPlan, assetQuotaUnitCount } = this.state;
|
|
let operationIntro = '新增空间';
|
|
let originalTotalAmount = assetQuotaUnitCount * currentPlan.price_per_asset_quota_unit;
|
|
let totalAmount = originalTotalAmount;
|
|
return (
|
|
<div className='d-flex flex-column subscription-container price-version-container-header subscription-add-space'>
|
|
<div className="price-version-container-top"></div>
|
|
<h3 className='user-quota-plan-name py-5'>{currentPlan.name}</h3>
|
|
<span className='py-2 mb-0 text-orange font-500 text-center'>
|
|
{'¥ '}<span className="price-version-plan-price">{currentPlan.asset_quota_price}</span>{' ' + currentPlan.asset_quota_description}
|
|
</span>
|
|
<InputGroup style={{ marginBottom: '5px' }} className='space-quota'>
|
|
<InputGroupAddon addonType="prepend">
|
|
<InputGroupText><span className="font-500">{operationIntro}</span></InputGroupText>
|
|
</InputGroupAddon>
|
|
<Input
|
|
className="py-2"
|
|
placeholder={operationIntro}
|
|
title={operationIntro}
|
|
type="number"
|
|
value={assetQuotaUnitCount || 1}
|
|
min="1"
|
|
max="9999"
|
|
disabled={!currentPlan.can_custom_asset_quota}
|
|
onChange={this.onAssetQuotaUnitCountInputChange}
|
|
/>
|
|
<InputGroupAddon addonType='append'>
|
|
<InputGroupText><span className="font-500">{' x ' + currentPlan.asset_quota_unit + 'GB'}</span></InputGroupText>
|
|
</InputGroupAddon>
|
|
</InputGroup>
|
|
<span className='py-4 text-orange mb-0 font-500 price-version-plan-whole-price text-center'>
|
|
{'总价 ¥ ' + totalAmount}
|
|
{originalTotalAmount !== totalAmount &&
|
|
<span style={{ fontSize: 'small', textDecoration: 'line-through', color: '#9a9a9a' }}>{' ¥' + originalTotalAmount}</span>
|
|
}
|
|
</span>
|
|
<span className='py-2 mb-0 text-lg-size font-500 price-version-plan-valid-day text-center'>{'有效期至 ' + currentPlan.new_term_end}</span>
|
|
<span className='subscription-notice text-center py-5'>{'注:当有效期剩余天数少于计划中的时候,增加空间的价格按天来计算'}</span>
|
|
<Button className='subscription-submit' onClick={this.onPay} color="primary">{'立即购买'}</Button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
let { paymentType } = this.props;
|
|
if (paymentType === 'paid' || paymentType === 'extend_time') {
|
|
return this.renderPaidOrExtendTime();
|
|
} else if (paymentType === 'add_user') {
|
|
return this.renderAddUser();
|
|
} else if (paymentType === 'buy_quota') {
|
|
return this.renderBuyQuota();
|
|
} else {
|
|
toaster.danger(gettext('Internal Server Error.'));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Plans.propTypes = PlansPropTypes;
|
|
|
|
const PlansDialogPropTypes = {
|
|
isOrgContext: PropTypes.bool.isRequired,
|
|
paymentType: PropTypes.string.isRequired,
|
|
paymentTypeTrans: PropTypes.string.isRequired,
|
|
toggleDialog: PropTypes.func.isRequired,
|
|
};
|
|
|
|
class PlansDialog extends Component {
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
isLoading: true,
|
|
isWaiting: false,
|
|
planList: [],
|
|
paymentSourceList: [],
|
|
};
|
|
}
|
|
|
|
getPlans = () => {
|
|
subscriptionAPI.getSubscriptionPlans(this.props.paymentType).then((res) => {
|
|
this.setState({
|
|
planList: res.data.plan_list,
|
|
paymentSourceList: res.data.payment_source_list,
|
|
isLoading: false,
|
|
});
|
|
}).catch(error => {
|
|
let errorMsg = Utils.getErrorMsg(error);
|
|
this.setState({
|
|
isLoading: false,
|
|
errorMsg: errorMsg,
|
|
});
|
|
});
|
|
};
|
|
|
|
onPay = (planID, count, asset_quota, totalAmount) => {
|
|
this.setState({ isWaiting: true });
|
|
let payUrl = serviceURL + '/subscription/pay/?payment_source=' + this.state.paymentSourceList[0] +
|
|
'&payment_type=' + this.props.paymentType + '&plan_id=' + planID +
|
|
'&total_amount=' + totalAmount;
|
|
if (count) {
|
|
payUrl += '&count=' + count;
|
|
}
|
|
if (asset_quota) {
|
|
payUrl += '&asset_quota=' + asset_quota;
|
|
}
|
|
window.open(payUrl);
|
|
};
|
|
|
|
onReload = () => {
|
|
window.location.reload();
|
|
};
|
|
|
|
componentDidMount() {
|
|
this.getPlans();
|
|
}
|
|
|
|
render() {
|
|
const { isLoading, isWaiting, planList } = this.state;
|
|
const { toggleDialog, paymentTypeTrans, paymentType } = this.props;
|
|
const modalStyle = (paymentType === 'paid' || paymentType === 'extend_time') ?
|
|
{ width: '560px', maxWidth: '560px' } : { width: '560px' };
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Modal isOpen={true} toggle={toggleDialog}>
|
|
<ModalHeader toggle={toggleDialog}>{paymentTypeTrans}</ModalHeader>
|
|
<ModalBody>
|
|
<Loading />
|
|
</ModalBody>
|
|
</Modal>
|
|
);
|
|
}
|
|
if (isWaiting) {
|
|
return (
|
|
<Modal isOpen={true} toggle={this.onReload}>
|
|
<ModalHeader toggle={this.onReload}>{paymentTypeTrans}</ModalHeader>
|
|
<ModalBody>
|
|
<div>{'是否完成付款?'}</div>
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<button className="btn btn-outline-primary" onClick={this.onReload}>{'是'}</button>
|
|
</ModalFooter>
|
|
</Modal>
|
|
);
|
|
}
|
|
return (
|
|
<Modal isOpen={true} toggle={toggleDialog} style={modalStyle}>
|
|
<ModalHeader toggle={toggleDialog}>{paymentTypeTrans}</ModalHeader>
|
|
<ModalBody>
|
|
<div className="d-flex justify-content-between">
|
|
<Plans
|
|
plans={planList}
|
|
onPay={this.onPay}
|
|
paymentType={this.props.paymentType}
|
|
/>
|
|
</div>
|
|
</ModalBody>
|
|
</Modal>
|
|
);
|
|
}
|
|
}
|
|
|
|
PlansDialog.propTypes = PlansDialogPropTypes;
|
|
|
|
const propTypes = {
|
|
isOrgContext: PropTypes.bool.isRequired,
|
|
handleContentScroll: PropTypes.func,
|
|
};
|
|
|
|
class Subscription extends Component {
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.paymentTypeTransMap = {
|
|
paid: '立即购买',
|
|
extend_time: '立即续费',
|
|
add_user: '增加用户',
|
|
buy_quota: '增加空间',
|
|
};
|
|
this.state = {
|
|
isLoading: true,
|
|
errorMsg: '',
|
|
isDialogOpen: false,
|
|
planName: this.props.isOrgContext ? '团队版' : '个人版',
|
|
userLimit: 20,
|
|
assetQuota: 1,
|
|
termEnd: '长期',
|
|
subscription: null,
|
|
paymentTypeList: [],
|
|
currentPaymentType: '',
|
|
errorMsgCode: ''
|
|
};
|
|
}
|
|
|
|
getSubscription = () => {
|
|
subscriptionAPI.getSubscription().then((res) => {
|
|
const subscription = res.data.subscription;
|
|
const paymentTypeList = res.data.payment_type_list;
|
|
if (!subscription) {
|
|
this.setState({
|
|
isLoading: false,
|
|
paymentTypeList: paymentTypeList,
|
|
});
|
|
} else {
|
|
let isActive = subscription.is_active;
|
|
let plan = subscription.plan;
|
|
this.setState({
|
|
isLoading: false,
|
|
subscription,
|
|
planName: plan.name,
|
|
userLimit: subscription.user_limit,
|
|
assetQuota: isActive ? subscription.asset_quota : plan.asset_quota,
|
|
termEnd: isActive ? subscription.term_end : '已过期',
|
|
paymentTypeList: paymentTypeList,
|
|
});
|
|
}
|
|
}).catch(error => {
|
|
let errorMsg = Utils.getErrorMsg(error);
|
|
this.setState({
|
|
isLoading: false,
|
|
errorMsg: errorMsg,
|
|
});
|
|
});
|
|
};
|
|
|
|
toggleDialog = () => {
|
|
this.setState({ isDialogOpen: !this.state.isDialogOpen });
|
|
};
|
|
|
|
togglePaymentType = (paymentType) => {
|
|
this.setState({ currentPaymentType: paymentType });
|
|
this.toggleDialog();
|
|
};
|
|
|
|
componentDidMount() {
|
|
this.getSubscription();
|
|
}
|
|
|
|
render() {
|
|
const { isLoading, errorMsg, planName, userLimit, assetQuota, termEnd,
|
|
isDialogOpen, paymentTypeList, currentPaymentType } = this.state;
|
|
if (isLoading) {
|
|
return <Loading />;
|
|
}
|
|
if (errorMsg) {
|
|
return <p className="text-center mt-8 error">{errorMsg}</p>;
|
|
}
|
|
return (
|
|
<Fragment>
|
|
<div className="content position-relative" onScroll={this.props.handleContentScroll}>
|
|
<div id="current-plan" className="subscription-info">
|
|
<h3 className="subscription-info-heading">{'当前版本'}</h3>
|
|
<p className="mb-2">{planName}</p>
|
|
</div>
|
|
{this.props.isOrgContext &&
|
|
<div id="user-limit" className="subscription-info">
|
|
<h3 className="subscription-info-heading">{'用户数限制'}</h3>
|
|
<p className="mb-2">{userLimit}</p>
|
|
</div>
|
|
}
|
|
<div id="asset-quota" className="subscription-info">
|
|
<h3 className="subscription-info-heading">{'空间'}</h3>
|
|
<p className="mb-2">{assetQuota ? assetQuota + 'GB' : '1GB'}</p>
|
|
</div>
|
|
<div id="current-subscription-period" className="subscription-info">
|
|
<h3 className="subscription-info-heading">{'订阅有效期'}</h3>
|
|
<p className="mb-2">{termEnd}</p>
|
|
</div>
|
|
<div id="product-price" className="subscription-info">
|
|
<h3 className="subscription-info-heading">{'云服务付费方案'}</h3>
|
|
<p className="mb-2">
|
|
<a rel="noopener noreferrer" target="_blank" href="https://www.seafile.com/seafile-docs/home/">{'查看详情'}</a>
|
|
</p>
|
|
</div>
|
|
{paymentTypeList.map((item, index) => {
|
|
let name = this.paymentTypeTransMap[item];
|
|
return (
|
|
<button
|
|
key={index}
|
|
className="btn btn-outline-primary mr-4"
|
|
onClick={this.togglePaymentType.bind(this, item)}
|
|
>{name}
|
|
</button>
|
|
);
|
|
})}
|
|
{!this.state.subscription &&
|
|
<div id="sales-consultant" className="subscription-info mt-6">
|
|
<h3 className="subscription-info-heading">{'销售咨询'}</h3>
|
|
<img className="mb-2" src="/media/img/qr-sale.png" alt="" width="112"></img>
|
|
<p className="mb-2">{'微信扫码联系销售'}</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
{isDialogOpen &&
|
|
<PlansDialog
|
|
paymentType={currentPaymentType}
|
|
paymentTypeTrans={this.paymentTypeTransMap[currentPaymentType]}
|
|
isOrgContext={this.props.isOrgContext}
|
|
toggleDialog={this.toggleDialog}
|
|
/>
|
|
}
|
|
</Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
Subscription.propTypes = propTypes;
|
|
|
|
export default Subscription;
|