Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
<second-menu>
<menu-item href="." code="index">套餐列表</menu-item>
<span class="item disabled">|</span>
<menu-item href=".create" code="create">[添加套餐]</menu-item>
</second-menu>

View File

@@ -0,0 +1,9 @@
.feature-boxes .feature-box {
margin-bottom: 1em;
width: 24em;
float: left;
}
.feature-boxes .feature-box:hover label {
font-weight: bold;
}
/*# sourceMappingURL=create.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["create.less"],"names":[],"mappings":"AAAA,cACC;EACC,kBAAA;EACA,WAAA;EACA,WAAA;;AAJF,cAOC,aAAY,MACX;EACC,iBAAA","file":"create.css"}

View File

@@ -0,0 +1,141 @@
{$layout "layout"}
{$template "menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table selectable definition">
<tr>
<td class="title">套餐名称 *</td>
<td>
<input type="text" name="name" ref="focus" maxlength="100"/>
</td>
</tr>
<tr>
<td>套餐简介</td>
<td>
<input type="text" name="description" maxlength="100"/>
<p class="comment">为用户介绍当前套餐的应用场景。</p>
</td>
</tr>
<tr>
<td>关联集群 *</td>
<td>
<cluster-selector></cluster-selector>
<p class="comment">使用当前套餐的网站都将会部署在此集群上。</p>
</td>
</tr>
<tr>
<td>价格设置 *</td>
<td>
<plan-price-config-box></plan-price-config-box>
</td>
</tr>
</table>
<table class="ui table selectable definition">
<tr>
<td class="title">流量限制</td>
<td>
<traffic-limit-config-box></traffic-limit-config-box>
</td>
</tr>
<tr>
<td>带宽限制</td>
<td>
<bandwidth-size-capacity-box :v-name="'bandwidthLimitPerNodeJSON'" :v-unit="'mb'"></bandwidth-size-capacity-box>
<p class="comment">该套餐在单个CDN节点上的带宽限制不填或者为0表示不限制。</p>
</td>
</tr>
<tr>
<td>总网站数限制</td>
<td>
<digit-input name="totalServers" size="6" style="width: 6em" value="1" maxlength="6"></digit-input>
<p class="comment">当前套餐能够绑定的总网站数量0表示不限制。</p>
</td>
</tr>
<tr>
<td>总域名数限制</td>
<td>
<digit-input name="totalServerNames" size="6" style="width: 6em" value="0" maxlength="6"></digit-input>
<p class="comment">当前套餐能够绑定的总域名数量即每个网站的所有域名总数量0表示不限制。</p>
</td>
</tr>
<tr>
<td>每个网站最多域名数</td>
<td>
<digit-input name="totalServerNamesPerServer" size="6" style="width: 6em" value="0" maxlength="6"></digit-input>
<p class="comment">当前套餐能够绑定的单个网站能够添加的最多域名数量0表示不限制。</p>
</td>
</tr>
<tr>
<td>单日请求数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="dailyRequests" size="16" style="width: 12em" value="0" maxlength="16" @input="changeDailyRequests"></digit-input>
<span class="ui label">次/每日</span>
</div>
<p class="comment"><span v-if="dailyRequestsFormat.length > 0">当前输入:{{dailyRequestsFormat}}。</span>当前套餐下的所有网站每天可以处理的请求数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>单月请求数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="monthlyRequests" size="16" style="width: 12em" value="0" maxlength="16" @input="changeMonthlyRequests"></digit-input>
<span class="ui label">次/每月</span>
</div>
<p class="comment"><span v-if="monthlyRequestsFormat.length > 0">当前输入:{{monthlyRequestsFormat}}。</span>当前套餐下的所有网站每月可以处理的请求数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>单日Websocket连接数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="dailyWebsocketConnections" size="16" style="width: 12em" value="0" maxlength="16" @input="changeDailyWebsocketConnections"></digit-input>
<span class="ui label">次/每日</span>
</div>
<p class="comment"><span v-if="dailyWebsocketConnectionsFormat.length > 0">当前输入:{{dailyWebsocketConnectionsFormat}}。</span>当前套餐下的所有网站每天可以接受的Websocket连接数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>单月Websocket连接数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="monthlyWebsocketConnections" size="16" style="width: 12em" value="0" maxlength="16" @input="changeMonthlyWebsocketConnections"></digit-input>
<span class="ui label">次/每月</span>
</div>
<p class="comment"><span v-if="monthlyWebsocketConnectionsFormat.length > 0">当前输入:{{monthlyWebsocketConnectionsFormat}}。</span>当前套餐下的所有网站每月可以接受的Websocket连接数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>文件上传最大尺寸</td>
<td>
<size-capacity-box :v-name="'maxUploadSizeJSON'" :v-unit="'gb'"></size-capacity-box>
<p class="comment">单次上传的文件最大内容尺寸0表示不限制。</p>
</td>
</tr>
</table>
<table class="ui table selectable definition">
<tr>
<td class="title">包含所有功能</td>
<td>
<checkbox name="hasFullFeatures" v-model="hasFullFeatures"></checkbox>
<p class="comment">选中后,表示当前套餐用户包含所有功能;取消选中后,可以选择支持的功能。</p>
</td>
</tr>
<tr v-show="!hasFullFeatures">
<td>选择支持的功能</td>
<td>
<div class="feature-boxes">
<div class="feature-box" v-for="feature in features">
<checkbox name="featureCodes" :v-value="feature.code" v-model="feature.isChecked">{{feature.name}}</checkbox>
<p class="comment">{{feature.description}}</p>
</div>
</div>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,61 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/plans#bottom")
this.dailyRequestsFormat = ""
this.changeDailyRequests = function (v) {
if (v <= 0) {
this.dailyRequestsFormat = ""
} else {
this.dailyRequestsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
this.monthlyRequestsFormat = ""
this.changeMonthlyRequests = function (v) {
if (v <= 0) {
this.monthlyRequestsFormat = ""
} else {
this.monthlyRequestsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
this.dailyWebsocketConnectionsFormat = ""
this.changeDailyWebsocketConnections = function (v) {
if (v <= 0) {
this.dailyWebsocketConnectionsFormat = ""
} else {
this.dailyWebsocketConnectionsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
this.monthlyWebsocketConnectionsFormat = ""
this.changeMonthlyWebsocketConnections = function (v) {
if (v <= 0) {
this.monthlyWebsocketConnectionsFormat = ""
} else {
this.monthlyWebsocketConnectionsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
/**
* format number to zh-cn|tw|hk wan(s)
*/
this.formatZHW = function (v) {
let count = v / 10000
if (v >= 1000000000000) {
return ",相当于" + (v / 1000000000000) + "兆"
}
if (v >= 100000000) {
return ",相当于" + (v / 100000000) + "亿"
}
if (v >= 10000) {
return ",相当于" + (v / 10000) + "万"
}
return ""
}
/**
* features
*/
this.hasFullFeatures = true
})

View File

@@ -0,0 +1,13 @@
.feature-boxes {
.feature-box {
margin-bottom: 1em;
width: 24em;
float: left;
}
.feature-box:hover {
label {
font-weight: bold;
}
}
}

View File

@@ -0,0 +1,66 @@
{$layout}
{$template "menu"}
<div v-if="!canUsePlans">
<div class="ui message warning">尚未在 <a href="/finance/fee">[计费设置]</a> 里开启"使用套餐计费"选项,用户将无法使用套餐。</div>
</div>
<p class="comment" v-if="plans.length == 0">暂时还没有套餐。</p>
<table class="ui table selectable celled" v-if="plans.length > 0" id="sortable-table">
<thead>
<tr>
<th style="width:3em"></th>
<th>名称</th>
<th>集群</th>
<th>网站数</th>
<th class="four wide">价格设置</th>
<th class="width6">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tbody v-for="plan in plans" :v-id="plan.id">
<tr>
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
<td>
<a :href="'/plans/plan?planId=' + plan.id">{{plan.name}}</a>
<div v-if="plan.description.length > 0"><span class="small grey">{{plan.description}}</span></div>
<div>
<grey-label v-if="plan.priceType == 'period'">按时间周期</grey-label>
<grey-label v-if="plan.priceType == 'traffic'">按流量</grey-label>
<grey-label v-if="plan.priceType == 'bandwidth'">按带宽</grey-label>
</div>
</td>
<td>
<span v-if="plan.cluster.id > 0">
<link-icon :href="'/clusters/cluster?clusterId=' + plan.cluster.id">{{plan.cluster.name}}</link-icon>
</span>
<span v-else class="red">[已删除]</span>
</td>
<td>
<span v-if="plan.totalServers > 0">{{plan.totalServers}}</span>
<span v-else class="disabled">没有限制</span>
</td>
<td>
<!-- 价格设置 -->
<plan-price-view :v-plan="plan"></plan-price-view>
<!-- 流量限制 -->
<plan-limit-view :value="plan"></plan-limit-view>
<!-- 带宽限制 -->
<plan-bandwidth-limit-view :value="plan"></plan-bandwidth-limit-view>
</td>
<td><label-on :v-is-on="plan.isOn"></label-on></td>
<td>
<a :href="'/plans/plan?planId=' + plan.id">详情</a> &nbsp;
<a href="" @click.prevent="deletePlan(plan.id)">删除</a>
</td>
</tr>
</tbody>
</table>
<p v-if="plans.length > 0" class="comment">可以拖动左侧的<i class="icon bars"></i>排序。</p>
<div id="bottom"></div>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,28 @@
Tea.context(function () {
this.$delay(function () {
this.sortTable()
})
this.deletePlan = function (planId) {
teaweb.confirm("确定要删除此套餐吗?", function () {
this.$post(".plan.delete")
.params({
planId: planId
})
.refresh()
})
}
this.sortTable = function () {
let that = this
sortTable(function (ids) {
that.$post(".sort")
.params({
ids: ids
})
.success(function () {
teaweb.successToast("保存成功", 1000)
})
})
}
})

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item href="/plans">所有套餐</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/plans/plan?planId=' + plan.id" code="index">"{{plan.name}}"详情</menu-item>
<menu-item :href="'/plans/plan/update?planId=' + plan.id" code="update">修改</menu-item>
</first-menu>

View File

@@ -0,0 +1,133 @@
{$layout}
{$template "menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">套餐名称</td>
<td>
{{plan.name}}
</td>
</tr>
<tr>
<td>状态</td>
<td><label-on :v-is-on="plan.isOn"></label-on></td>
</tr>
<tr>
<td>套餐简介</td>
<td>
<span v-if="plan.description.length > 0">{{plan.description}}</span>
<span v-else class="disabled">还没有简介。</span>
</td>
</tr>
<tr>
<td>关联集群</td>
<td>
<span v-if="plan.cluster.id > 0">
<link-icon :href="'/clusters/cluster?clusterId=' + plan.cluster.id">{{plan.cluster.name}}</link-icon>
</span>
<span v-else class="red">[已删除]</span>
<p class="comment">使用当前套餐的网站都将会部署在此集群上。</p>
</td>
</tr>
<tr>
<td>价格设置</td>
<td>
<plan-price-view :v-plan="plan"></plan-price-view>
</td>
</tr>
</table>
<table class="ui table selectable definition">
<tr>
<td class="title">流量限制</td>
<td>
<div v-if="plan.trafficLimit != null && plan.trafficLimit.isOn">
<plan-limit-view :value="plan" :v-single-mode="true"></plan-limit-view>
</div>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>带宽限制</td>
<td>
<div v-if="plan.bandwidthLimitPerNode != null && plan.bandwidthLimitPerNode.count > 0">
<bandwidth-size-capacity-view :v-value="plan.bandwidthLimitPerNode"></bandwidth-size-capacity-view>
</div>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>总网站数限制</td>
<td>
<span v-if="plan.totalServers > 0">{{plan.totalServers}}</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>总域名数限制</td>
<td>
<span v-if="plan.totalServerNames > 0">{{plan.totalServerNames}}</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>每个网站最多域名数</td>
<td>
<span v-if="plan.totalServerNamesPerServer > 0">{{plan.totalServerNamesPerServer}}</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>单日请求数限制</td>
<td>
<span v-if="plan.dailyRequests > 0">{{plan.dailyRequestsFormat}}次/日</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>单月请求数限制</td>
<td>
<span v-if="plan.monthlyRequests > 0">{{plan.monthlyRequestsFormat}}次/月</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>单日Websocket连接数限制</td>
<td>
<span v-if="plan.dailyWebsocketConnections > 0">{{plan.dailyWebsocketConnections}}次/日</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>单月Websocket连接数限制</td>
<td>
<span v-if="plan.monthlyWebsocketConnections > 0">{{plan.monthlyWebsocketConnections}}次/月</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
<tr>
<td>文件上传最大尺寸</td>
<td>
<span v-if="plan.maxUploadSize != null && plan.maxUploadSize.count > 0">
<size-capacity-view :v-value="plan.maxUploadSize"></size-capacity-view>
</span>
<span v-else class="disabled">没有限制</span>
</td>
</tr>
</table>
<table class="ui table selectable definition">
<tr>
<td class="title">支持的功能</td>
<td>
<div v-if="plan.hasFullFeatures">所有功能</div>
<div v-else>
<span v-if="plan.features == null || plan.features.length == 0" class="disabled">没有任何功能</span>
<span v-else>
<span class="ui label basic small" v-for="feature in plan.features" :class="{disabled: !feature.isChecked}">{{feature.name}}</span>
</span>
</div>
</td>
</tr>
</table>

View File

@@ -0,0 +1,4 @@
Tea.context(function () {
this.plan.dailyRequestsFormat = teaweb.formatNumber(this.plan.dailyRequests)
this.plan.monthlyRequestsFormat = teaweb.formatNumber(this.plan.monthlyRequests)
})

View File

@@ -0,0 +1,9 @@
.feature-boxes .feature-box {
margin-bottom: 1em;
width: 24em;
float: left;
}
.feature-boxes .feature-box:hover label {
font-weight: bold;
}
/*# sourceMappingURL=update.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["update.less"],"names":[],"mappings":"AAAA,cACC;EACC,kBAAA;EACA,WAAA;EACA,WAAA;;AAJF,cAOC,aAAY,MACX;EACC,iBAAA","file":"update.css"}

View File

@@ -0,0 +1,160 @@
{$layout}
{$template "menu"}
<div class="ui warning message">注意:修改套餐内容后,用户所有相关已购套餐也会自动变更。</div>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="planId" :value="plan.id"/>
<table class="ui table selectable definition">
<tr>
<td class="title">套餐名称 *</td>
<td>
<input type="text" name="name" ref="focus" maxlength="100" v-model="plan.name"/>
</td>
</tr>
<tr>
<td>套餐简介</td>
<td>
<input type="text" name="description" maxlength="100" v-model="plan.description"/>
<p class="comment">为用户介绍当前套餐的应用场景。</p>
</td>
</tr>
<tr>
<td>关联集群 *</td>
<td>
<cluster-selector :v-cluster-id="plan.cluster.id"></cluster-selector>
<p class="comment">使用当前套餐的网站都将会部署在此集群上。</p>
</td>
</tr>
<tr>
<td>价格设置 *</td>
<td>
<plan-price-config-box
:v-price-type="plan.priceType"
:v-monthly-price="plan.monthlyPrice"
:v-seasonally-price="plan.seasonallyPrice"
:v-yearly-price="plan.yearlyPrice"
:v-traffic-price="plan.trafficPrice"
:v-bandwidth-price="plan.bandwidthPrice">
</plan-price-config-box>
</td>
</tr>
</table>
<table class="ui definition selectable table">
<tr>
<td class="title">流量限制</td>
<td>
<traffic-limit-config-box :v-traffic-limit="plan.trafficLimit"></traffic-limit-config-box>
</td>
</tr>
<tr>
<td>带宽限制</td>
<td>
<bandwidth-size-capacity-box :v-name="'bandwidthLimitPerNodeJSON'" :v-value="plan.bandwidthLimitPerNode"></bandwidth-size-capacity-box>
<p class="comment">该套餐在单个CDN节点上的带宽限制不填或者为0表示不限制。</p>
</td>
</tr>
<tr>
<td>总网站数限制</td>
<td>
<digit-input name="totalServers" size="6" style="width: 6em" value="0" maxlength="6" v-model="plan.totalServers"></digit-input>
<p class="comment">当前套餐能够绑定的总网站数量0表示不限制。</p>
</td>
</tr>
<tr>
<td>总域名数限制</td>
<td>
<digit-input name="totalServerNames" size="6" style="width: 6em" value="0" maxlength="6" v-model="plan.totalServerNames"></digit-input>
<p class="comment">当前套餐能够绑定的总域名数量即每个网站的所有域名总数量0表示不限制。</p>
</td>
</tr>
<tr>
<td>每个网站最多域名数</td>
<td>
<digit-input name="totalServerNamesPerServer" size="6" style="width: 6em" value="0" maxlength="6" v-model="plan.totalServerNamesPerServer"></digit-input>
<p class="comment">当前套餐能够绑定的单个网站能够添加的最多域名数量0表示不限制。</p>
</td>
</tr>
<tr>
<td>单日请求数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="dailyRequests" size="16" style="width: 12em" value="0" maxlength="16" @input="changeDailyRequests" v-model="plan.dailyRequests"></digit-input>
<span class="ui label">次/每日</span>
</div>
<p class="comment"><span v-if="dailyRequestsFormat.length > 0">当前输入:{{dailyRequestsFormat}}。</span>当前套餐下的所有网站每天可以处理的请求数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>单月请求数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="monthlyRequests" size="16" style="width: 12em" value="0" maxlength="16" @input="changeMonthlyRequests" v-model="plan.monthlyRequests"></digit-input>
<span class="ui label">次/每月</span>
</div>
<p class="comment"><span v-if="monthlyRequestsFormat.length > 0">当前输入:{{monthlyRequestsFormat}}。</span>当前套餐下的所有网站每月可以处理的请求数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>单日Websocket连接数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="dailyWebsocketConnections" size="16" style="width: 12em" value="0" maxlength="16" @input="changeDailyWebsocketConnections" v-model="plan.dailyWebsocketConnections"></digit-input>
<span class="ui label">次/每日</span>
</div>
<p class="comment"><span v-if="dailyWebsocketConnectionsFormat.length > 0">当前输入:{{dailyWebsocketConnectionsFormat}}。</span>当前套餐下的所有网站每天可以接受的Websocket连接数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>单月Websocket连接数限制</td>
<td>
<div class="ui input right labeled">
<digit-input name="monthlyWebsocketConnections" size="16" style="width: 12em" value="0" maxlength="16" @input="changeMonthlyWebsocketConnections" v-model="plan.monthlyWebsocketConnections"></digit-input>
<span class="ui label">次/每月</span>
</div>
<p class="comment"><span v-if="monthlyWebsocketConnectionsFormat.length > 0">当前输入:{{monthlyWebsocketConnectionsFormat}}。</span>当前套餐下的所有网站每月可以接受的Websocket连接数限制0表示不限制。</p>
</td>
</tr>
<tr>
<td>文件上传最大尺寸</td>
<td>
<size-capacity-box :v-name="'maxUploadSizeJSON'" :v-value="plan.maxUploadSize"></size-capacity-box>
<p class="comment">单次上传的文件最大内容尺寸0表示不限制。</p>
</td>
</tr>
</table>
<table class="ui table selectable definition">
<tr>
<td class="title">包含所有功能</td>
<td>
<checkbox name="hasFullFeatures" v-model="plan.hasFullFeatures"></checkbox>
<p class="comment">选中后,表示当前套餐用户包含所有功能;取消选中后,可以选择支持的功能。</p>
</td>
</tr>
<tr v-show="!plan.hasFullFeatures">
<td>选择支持的功能</td>
<td>
<div class="feature-boxes">
<div class="feature-box" v-for="feature in features">
<checkbox name="featureCodes" :v-value="feature.code" v-model="feature.isChecked">{{feature.name}}</checkbox>
<p class="comment">{{feature.description}}</p>
</div>
</div>
</td>
</tr>
</table>
<table class="ui table selectable definition">
<tr>
<td class="title">启用当前套餐</td>
<td>
<checkbox name="isOn" v-model="plan.isOn"></checkbox>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,61 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/plans/plan?planId=" + this.plan.id)
this.$delay(function () {
this.changeDailyRequests(this.plan.dailyRequests)
this.changeMonthlyRequests(this.plan.monthlyRequests)
})
this.dailyRequestsFormat = ""
this.changeDailyRequests = function (v) {
if (v <= 0) {
this.dailyRequestsFormat = ""
} else {
this.dailyRequestsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
this.monthlyRequestsFormat = ""
this.changeMonthlyRequests = function (v) {
if (v <= 0) {
this.monthlyRequestsFormat = ""
} else {
this.monthlyRequestsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
this.dailyWebsocketConnectionsFormat = ""
this.changeDailyWebsocketConnections = function (v) {
if (v <= 0) {
this.dailyWebsocketConnectionsFormat = ""
} else {
this.dailyWebsocketConnectionsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
this.monthlyWebsocketConnectionsFormat = ""
this.changeMonthlyWebsocketConnections = function (v) {
if (v <= 0) {
this.monthlyWebsocketConnectionsFormat = ""
} else {
this.monthlyWebsocketConnectionsFormat = teaweb.formatNumber(v) + this.formatZHW(v)
}
}
/**
* format number to zh-cn|tw|hk wan(s)
*/
this.formatZHW = function (v) {
let count = v / 10000
if (v >= 1000000000000) {
return ",相当于" + (v / 1000000000000) + "兆"
}
if (v >= 100000000) {
return ",相当于" + (v / 100000000) + "亿"
}
if (v >= 10000) {
return ",相当于" + (v / 10000) + "万"
}
return ""
}
})

View File

@@ -0,0 +1,13 @@
.feature-boxes {
.feature-box {
margin-bottom: 1em;
width: 24em;
float: left;
}
.feature-box:hover {
label {
font-weight: bold;
}
}
}

View File

@@ -0,0 +1,111 @@
{$layout "layout_popup"}
<h3>购买套餐</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">用户 *</td>
<td><plan-user-selector @change="changeUserId"></plan-user-selector>
<p class="comment" v-if="userAccount != null">用户账户余额{{userAccount.total}}元。</p>
</td>
</tr>
<tr>
<td>备注名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus"/>
<p class="comment">用于识别不同的套餐。</p>
</td>
</tr>
<tr>
<td>套餐 *</td>
<td>
<select class="ui dropdown auto-width" name="planId" v-model="planId" @change="changePlanId(planId)">
<option value="0">[选择套餐]</option>
<option v-for="plan in plans" :value="plan.id">{{plan.name}}</option>
</select>
<p class="comment" v-if="plan != null">
<span v-if="plan.priceType == 'period'">按时间周期付费,{{plan.monthlyPrice}}元/月,{{plan.seasonallyPrice}}元/季度,{{plan.yearlyPrice}}元/年。</span>
<span v-if="plan.priceType == 'traffic'">按流量付费。</span>
</p>
</td>
</tr>
<!-- 按时间周期计费 -->
<tbody v-if="plan != null && plan.priceType == 'period'">
<tr>
<td>周期 *</td>
<td>
<select class="ui dropdown auto-width" name="period" v-model="period">
<option value="monthly">按月</option>
<option value="seasonally">按季度</option>
<option value="yearly">按年</option>
</select>
</td>
</tr>
<tr v-if="period == 'monthly'">
<td>月数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="countMonths" value="1" style="width: 5em" maxlength="4" v-model="countMonths"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr v-if="period == 'seasonally'">
<td>季度数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="countSeasons" value="1" style="width: 5em" maxlength="4" v-model="countSeasons"/>
<span class="ui label">季度</span>
</div>
</td>
</tr>
<tr v-if="period == 'yearly'">
<td>年数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="countYears" value="1" style="width: 5em" maxlength="4" v-model="countYears"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr>
<td>预计费用</td>
<td>
<span v-if="fee > 0">{{fee}}元</span>
<span v-else>-</span>
</td>
</tr>
</tbody>
<!-- 按流量计费 -->
<tbody v-if="plan != null && plan.priceType == 'traffic'">
<tr>
<td>结束日期</td>
<td>
<datepicker :v-name="'dayTo'" :v-bottom-left="true" :v-value="defaultDayTo"></datepicker>
</td>
</tr>
<tr>
<td>预计费用</td>
<td>月结</td>
</tr>
</tbody>
<!-- 按带宽计费 -->
<tbody v-if="plan != null && plan.priceType == 'bandwidth'">
<tr>
<td>结束日期</td>
<td>
<datepicker :v-name="'dayTo'" :v-bottom-left="true" :v-value="defaultDayTo"></datepicker>
</td>
</tr>
<tr>
<td>预计费用</td>
<td>月结</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,87 @@
Tea.context(function () {
this.planId = 0
this.plan = null
this.period = "monthly"
this.fee = 0
this.changePlanId = function (planId) {
if (planId == 0) {
this.plan = null
} else {
this.period = "monthly"
this.plan = this.plans.$find(function (k, v) {
return v.id == planId
})
if (this.plan != null) {
this.fee = this.plan.monthlyPrice
}
}
}
this.countMonths = 1
this.countSeasons = 1
this.countYears = 1
this.$delay(function () {
this.$watch("period", function (period) {
this.countMonths = 1
this.countSeasons = 1
this.countYears = 1
switch(period) {
case "monthly":
this.fee = this.countMonths * this.plan.monthlyPrice
break
case "seasonally":
this.fee = this.countSeasons * this.plan.seasonallyPrice
break
case "yearly":
this.fee = this.countYears * this.plan.yearlyPrice
break
}
})
this.$watch("countMonths", function (months) {
let count = parseInt(months)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.plan.monthlyPrice * count
})
this.$watch("countSeasons", function (seasons) {
let count = parseInt(seasons)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.plan.seasonallyPrice * count
})
this.$watch("countYears", function (years) {
let count = parseInt(years)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.plan.yearlyPrice * count
})
})
/**
* 用户账户
*/
this.userAccount = null
this.changeUserId = function (userId) {
if (userId == 0) {
this.userAccount = null
return
}
this.$post(".userAccount")
.params({
userId: userId
})
.success(function (resp) {
this.userAccount = resp.data.account
})
}
})

View File

@@ -0,0 +1,63 @@
{$layout}
<first-menu>
<menu-item href=".?type=available" :active="type == 'available'">有效套餐({{countAvailable}})</menu-item>
<menu-item href=".?type=expiring7" :active="type == 'expiring7'">7天内过期(<span v-if="countExpiring7 > 0" class="red">{{countExpiring7}}</span><span v-else>0</span>)</menu-item>
<menu-item href=".?type=expiring30" :active="type == 'expiring30'">30天内过期(<span v-if="countExpiring30 > 0" class="red">{{countExpiring30}}</span><span v-else>0</span>)</menu-item>
<menu-item href=".?type=expired" :active="type == 'expired'">过期套餐({{countExpired}})</menu-item>
<span class="item disabled">|</span>
<menu-item @click.prevent="createUserPlan">[新购套餐]</menu-item>
</first-menu>
<div v-if="!canUsePlans">
<div class="margin"></div>
<div class="ui message warning">尚未在 <a href="/finance/fee">[计费设置]</a> 里开启"使用套餐计费"选项,用户将无法使用套餐。</div>
</div>
<p class="comment" v-if="userPlans.length == 0">暂时还没有已购套餐。</p>
<table class="ui table selectable celled" v-if="userPlans.length > 0">
<thead>
<tr>
<th>用户</th>
<th class="three wide">套餐</th>
<th class="three wide">备注名称</th>
<th class="three wide">有效期</th>
<th class="three wide">网站</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="userPlan in userPlans">
<td>
<span v-if="userPlan.user.id > 0">
<link-icon :href="'/users/user?userId=' + userPlan.user.id">{{userPlan.user.fullname}} <span class="small">({{userPlan.user.username}})</span></link-icon>
</span>
<span class="red" v-else>[已删除]</span>
</td>
<td>
<span v-if="userPlan.plan.id > 0">
<link-icon :href="'/plans/plan?planId=' + userPlan.plan.id">{{userPlan.plan.name}}</link-icon>
</span>
<span class="red" v-else>[已删除]</span>
</td>
<td>
<span v-if="userPlan.name.length > 0">{{userPlan.name}}</span>
<span v-else class="disabled">-</span>
</td>
<td>{{userPlan.dayTo}}</td>
<td>
<div v-if="userPlan.servers != null && userPlan.servers.length > 0">
<div v-for="server in userPlan.servers">
<a :href="'/servers/server/settings?serverId=' + server.id">{{server.name}}</a>
</div>
</div>
<span v-else class="disabled">尚未绑定</span>
</td>
<td>
<a href="" @click.prevent="renewUserPlan(userPlan.id)">续费</a> &nbsp;
<a href="" @click.prevent="deleteUserPlan(userPlan.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,19 @@
Tea.context(function () {
this.createUserPlan = function () {
teaweb.popupSuccess(".createPopup", null, "26em")
}
this.deleteUserPlan = function (userPlanId) {
teaweb.confirm("确定要删除此套餐吗?", function () {
this.$post(".delete")
.params({
userPlanId: userPlanId
})
.refresh()
})
}
this.renewUserPlan = function (userPlanId) {
teaweb.popupSuccess(".renewPopup?userPlanId=" + userPlanId, null, "26em")
}
})

View File

@@ -0,0 +1,100 @@
{$layout "layout_popup"}
<h3>续费套餐</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="userPlanId" :value="userPlanId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">用户</td>
<td>
<user-link :v-user="user"></user-link>
<p class="comment" v-if="userAccount != null">用户账户余额{{userAccount.total}}元。</p>
</td>
</tr>
<tr>
<td>套餐 *</td>
<td>
{{plan.name}}
<p class="comment" v-if="plan != null">
<span v-if="plan.priceType == 'period'">按时间周期付费,{{plan.monthlyPrice}}元/月,{{plan.seasonallyPrice}}元/季度,{{plan.yearlyPrice}}元/年。</span>
<span v-if="plan.priceType == 'traffic'">按流量付费。</span>
</p>
</td>
</tr>
<tbody v-if="plan != null && plan.priceType == 'period'">
<tr>
<td>周期 *</td>
<td>
<select class="ui dropdown auto-width" name="period" v-model="period">
<option value="monthly">按月</option>
<option value="seasonally">按季度</option>
<option value="yearly">按年</option>
</select>
</td>
</tr>
<tr v-if="period == 'monthly'">
<td>月数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="countMonths" value="1" style="width: 5em" maxlength="4" v-model="countMonths"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr v-if="period == 'seasonally'">
<td>季度数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="countSeasons" value="1" style="width: 5em" maxlength="4" v-model="countSeasons"/>
<span class="ui label">季度</span>
</div>
</td>
</tr>
<tr v-if="period == 'yearly'">
<td>年数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="countYears" value="1" style="width: 5em" maxlength="4" v-model="countYears"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr>
<td>预计费用</td>
<td>
<span v-if="fee > 0">{{fee}}元</span>
<span v-else>-</span>
</td>
</tr>
</tbody>
<tbody v-if="plan != null && plan.priceType == 'traffic'">
<tr>
<td>结束日期</td>
<td>
<datepicker :v-name="'dayTo'" :v-bottom-left="true" :v-value="defaultDayTo"></datepicker>
</td>
</tr>
<tr>
<td>预计费用</td>
<td>月结</td>
</tr>
</tbody>
<tbody v-if="plan != null && plan.priceType == 'bandwidth'">
<tr>
<td>结束日期</td>
<td>
<datepicker :v-name="'dayTo'" :v-bottom-left="true" :v-value="defaultDayTo"></datepicker>
</td>
</tr>
<tr>
<td>预计费用</td>
<td>月结</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,50 @@
Tea.context(function () {
this.period = "monthly"
this.fee = this.plan.monthlyPrice
this.countMonths = 1
this.countSeasons = 1
this.countYears = 1
this.$delay(function () {
this.$watch("period", function (period) {
this.countMonths = 1
this.countSeasons = 1
this.countYears = 1
switch(period) {
case "monthly":
this.fee = this.countMonths * this.plan.monthlyPrice
break
case "seasonally":
this.fee = this.countSeasons * this.plan.seasonallyPrice
break
case "yearly":
this.fee = this.countYears * this.plan.yearlyPrice
break
}
})
this.$watch("countMonths", function (months) {
let count = parseInt(months)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.plan.monthlyPrice * count
})
this.$watch("countSeasons", function (seasons) {
let count = parseInt(seasons)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.plan.seasonallyPrice * count
})
this.$watch("countYears", function (years) {
let count = parseInt(years)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.plan.yearlyPrice * count
})
})
})