This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
<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'">过期套餐<span class="red">({{countExpired}})</span></menu-item>
<span class="item disabled">|</span>
<menu-item href=".buy" code="buy">[新购套餐]</menu-item>
</first-menu>

View File

@@ -0,0 +1,39 @@
table.plans-table {
cursor: pointer;
}
table.plans-table th {
text-align: center !important;
line-height: 1.8;
}
table.plans-table th span.small {
font-weight: normal;
}
table.plans-table td {
text-align: center !important;
}
table.plans-table td.value-name {
text-align: left !important;
}
table.plans-table th.selected {
background: #276AC6;
color: white !important;
}
table.plans-table th.selected span {
color: white !important;
}
table.plans-table th.selected,
table.plans-table td.selected {
border-left: 1px #276AC6 solid !important;
border-right: 1px #276AC6 solid !important;
}
table.plans-table th.selected {
border-top: 1px #276AC6 solid !important;
}
table.plans-table td.bottom.selected {
border-bottom: 1px #276AC6 solid !important;
}
.ui.structured.celled.table tr td:not(.value-category-name),
.ui.structured.celled.table tr th:not(.value-category-name) {
border-right: none;
}
/*# sourceMappingURL=buy.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["buy.less"],"names":[],"mappings":"AAEA,KAAK;EACJ,eAAA;;AADD,KAAK,YAGJ;EACC,6BAAA;EACA,gBAAA;;AALF,KAAK,YAGJ,GAIC,KAAI;EACH,mBAAA;;AARH,KAAK,YAYJ;EACC,6BAAA;;AAbF,KAAK,YAgBJ,GAAE;EACD,2BAAA;;AAjBF,KAAK,YAoBJ,GAAE;EACD,mBAAA;EACA,uBAAA;;AAtBF,KAAK,YAoBJ,GAAE,SAID;EACC,uBAAA;;AAzBH,KAAK,YA6BJ,GAAE;AA7BH,KAAK,YA6BS,GAAE;EACd,8BAAA;EACA,+BAAA;;AA/BF,KAAK,YAkCJ,GAAE;EACD,6BAAA;;AAnCF,KAAK,YAsCJ,GAAE,OAAO;EACR,gCAAA;;AAIF,GAAG,WAAW,OAAO,MAAO,GAAG,GAAE,IAAI;AAAwB,GAAG,WAAW,OAAO,MAAO,GAAG,GAAE,IAAI;EACjG,kBAAA","file":"buy.css"}

View File

@@ -0,0 +1,255 @@
{$layout "layout"}
{$template "menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="planId" :value="planId"/>
<table class="ui table selectable celled structured plans-table">
<thead>
<tr>
<th></th>
<th class="two wide"><span class="small">用量/功能</span></th>
<th v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<div class="plan-name">{{plan.name}}</div>
<div><span class="small grey">{{plan.description}}</span>&nbsp;</div>
</th>
</tr>
</thead>
<!-- 选择 -->
<tr>
<td></td>
<td></td>
<td v-for="plan in plans" class="" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}">
<button class="ui button basic" :class="{primary: selectedPlan != null && plan.id == selectedPlan.id}" type="button" @click.prevent="selectPlan(plan, true)">选择</button>
</td>
</tr>
<!-- 流量限制 -->
<tr>
<td rowspan="2" class="value-category-name">流量配额</td>
<td class="value-name">日流量限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<div v-if="plan.trafficLimit != null && plan.trafficLimit.isOn && plan.trafficLimit.dailySize != null && plan.trafficLimit.dailySize.count > 0">
{{composeCapacity(plan.trafficLimit.dailySize)}}
</div>
<div v-else>无限制</div>
</td>
</tr>
<tr>
<td class="value-name">月流量限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<div v-if="plan.trafficLimit != null && plan.trafficLimit.isOn && plan.trafficLimit.monthlySize != null && plan.trafficLimit.monthlySize.count > 0">
{{composeCapacity(plan.trafficLimit.monthlySize)}}
</div>
<div v-else>无限制</div>
</td>
</tr>
<!-- 带宽限制 -->
<tr>
<td class="value-category-name">带宽限制</td>
<td class="value-name">单节点带宽限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<div v-if="plan.bandwidthLimitPerNode != null && plan.bandwidthLimitPerNode.count > 0">
<bandwidth-size-capacity-view :v-value="plan.bandwidthLimitPerNode"></bandwidth-size-capacity-view>
</div>
<div v-else>无限制</div>
</td>
</tr>
<!-- 用量限制 -->
<tr>
<td rowspan="8" class="value-category-name">用量配额</td>
<td class="value-name">总网站数限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<plan-quota-value v-model="plan.totalServers"></plan-quota-value>
</td>
</tr>
<tr>
<td class="value-name">总域名数限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<plan-quota-value v-model="plan.totalServerNames"></plan-quota-value>
</td>
</tr>
<tr>
<td class="value-name">每个网站最多域名数</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<plan-quota-value v-model="plan.totalServerNamesPerServer"></plan-quota-value>
</td>
</tr>
<tr>
<td class="value-name">单日请求数限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<plan-quota-value v-model="plan.dailyRequests"></plan-quota-value>
</td>
</tr>
<tr>
<td class="value-name">单月请求数限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<plan-quota-value v-model="plan.monthlyRequests"></plan-quota-value>
</td>
</tr>
<tr>
<td class="value-name">单日Websocket连接数限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<plan-quota-value v-model="plan.dailyWebsocketConnections"></plan-quota-value>
</td>
</tr>
<tr>
<td class="value-name">单月Websocket连接数限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<plan-quota-value v-model="plan.monthlyWebsocketConnections"></plan-quota-value>
</td>
</tr>
<tr>
<td class="value-name">文件上传限制</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<span v-if="plan.maxUploadSize != null && plan.maxUploadSize.count > 0">{{composeCapacity(plan.maxUploadSize)}}</span>
<span v-else>无限制</span>
</td>
</tr>
<!-- 功能限制 -->
<tr>
<td :colspan="plans.length+2">
<more-options-indicator>
<span v-if="!moreOptionsVisible">查看网站功能支持</span>
<span v-if="moreOptionsVisible">收起网站功能支持</span>
</more-options-indicator>
</td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr v-for="(feature, index) in allFeatures">
<td :rowspan="allFeatures.length" v-if="index == 0" class="value-category-name">网站功能</td>
<td class="value-name">{{feature.name}}</td>
<td v-for="plan in plans" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}" @click.prevent="selectPlan(plan)">
<span v-if="plan.hasFullFeatures || (plan.featureCodes != null && plan.featureCodes.$contains(feature.code))">
<i class="icon check green"></i>
</span>
<span v-else>/</span>
</td>
</tr>
</tbody>
<!-- 选择 -->
<tr>
<td></td>
<td></td>
<td v-for="plan in plans" class="bottom" :class="{selected: selectedPlan != null && plan.id == selectedPlan.id}">
<button class="ui button basic" :class="{primary: selectedPlan != null && plan.id == selectedPlan.id}" type="button" @click.prevent="selectPlan(plan)">选择</button>
</td>
</tr>
</table>
<table class="ui table definition selectable">
<tr v-if="selectedPlan != null">
<td>已选择套餐</td>
<td><strong>{{selectedPlan.name}}</strong></td>
</tr>
<tr>
<td class="title">账户余额</td>
<td>
<span v-if="userAccount != null">{{userAccount.total}}元</span>
<span v-else>0元</span>
&nbsp; <a href="/finance/charge"><span class="small">[充值]</span></a>
</td>
</tr>
<tr v-if="selectedPlan != null">
<td>计费方式</td>
<td>
<span v-if="selectedPlan.priceType == 'period'">按时间周期付费,{{selectedPlan.monthlyPrice}}元/月,{{selectedPlan.seasonallyPrice}}元/季度,{{selectedPlan.yearlyPrice}}元/年。</span>
<span v-if="selectedPlan.priceType == 'traffic'">按流量付费。</span>
<span v-if="selectedPlan.priceType == 'bandwidth'">按带宽付费。</span>
</td>
</tr>
<!-- 按时间周期计费 -->
<tbody v-if="selectedPlan != null && selectedPlan.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="selectedPlan != null && selectedPlan.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="selectedPlan != null && selectedPlan.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>
<tr>
<td>备注名称</td>
<td>
<input type="text" name="name" maxlength="50"/>
<p class="comment">用于识别不同的套餐,默认为套餐名称。</p>
</td>
</tr>
</table>
<submit-btn>确定购买</submit-btn>
</form>

View File

@@ -0,0 +1,88 @@
Tea.context(function () {
this.success = NotifySuccess("购买成功", ".")
this.$delay(function () {
if (this.planId >0) {
let that = this
let plan = this.plans.$find(function (k, v) {
return v.id == that.planId
})
if (plan != null) {
this.selectPlan(plan, true)
}
}
})
this.selectedPlan = null
this.period = "monthly"
this.fee = 0
this.planDescription = ""
this.selectPlan = function (plan, goBottom) {
this.planId = plan.id
this.selectedPlan = plan
this.period = "monthly"
this.fee = plan.monthlyPrice
if (goBottom) {
setTimeout(function () {
window.scrollTo(0, 1000000)
})
}
}
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.selectedPlan.monthlyPrice
break
case "seasonally":
this.fee = this.countSeasons * this.selectedPlan.seasonallyPrice
break
case "yearly":
this.fee = this.countYears * this.selectedPlan.yearlyPrice
break
}
})
this.$watch("countMonths", function (months) {
let count = parseInt(months)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.selectedPlan.monthlyPrice * count
})
this.$watch("countSeasons", function (seasons) {
let count = parseInt(seasons)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.selectedPlan.seasonallyPrice * count
})
this.$watch("countYears", function (years) {
let count = parseInt(years)
if (isNaN(count) || count < 1) {
count = 1
}
this.fee = this.selectedPlan.yearlyPrice * count
})
})
Vue.component("plan-quota-value", {
props:["value"],
template:`<span><span v-if="value > 0">{{value}}</span><span v-else class="disabled">无限制</span></span>`
})
this.composeCapacity = function (capacity) {
return teaweb.convertSizeCapacityToString(capacity)
}
})

View File

@@ -0,0 +1,48 @@
@SELECTED_PLAN_COLOR: #276AC6;
table.plans-table {
cursor: pointer;
th {
text-align: center !important;
line-height: 1.8;
span.small {
font-weight: normal;
}
}
td {
text-align: center !important;
}
td.value-name {
text-align: left !important;
}
th.selected {
background: @SELECTED_PLAN_COLOR;
color: white !important;
span {
color: white !important;
}
}
th.selected, td.selected {
border-left: 1px @SELECTED_PLAN_COLOR solid !important;
border-right: 1px @SELECTED_PLAN_COLOR solid !important;
}
th.selected {
border-top: 1px @SELECTED_PLAN_COLOR solid !important;
}
td.bottom.selected {
border-bottom: 1px @SELECTED_PLAN_COLOR solid !important;
}
}
.ui.structured.celled.table tr td:not(.value-category-name), .ui.structured.celled.table tr th:not(.value-category-name) {
border-right: none;
}

View File

@@ -0,0 +1,57 @@
{$layout}
{$template "menu"}
<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>备注名</th>
<th>价格设置</th>
<th>有效期</th>
<th>网站</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="userPlan in userPlans">
<td>
<span v-if="userPlan.plan.id > 0">
{{userPlan.plan.name}}
</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>
<div v-if="userPlan.plan != null">
<!-- 价格设置 -->
<plan-price-view :v-plan="userPlan.plan"></plan-price-view>
<!-- 流量限制 -->
<plan-limit-view :value="userPlan.plan"></plan-limit-view>
<!-- 带宽限制 -->
<plan-bandwidth-limit-view :value="userPlan.plan"></plan-bandwidth-limit-view>
</div>
</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/plan?serverId=' + server.id" v-if="server.type == 'httpProxy' || server.type == 'httpWeb'">{{server.name}}</a>
<a :href="'/lb/server/settings/plan?serverId=' + server.id" v-if="server.type == 'tcpProxy' || server.type == 'udpProxy'">{{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,15 @@
Tea.context(function () {
this.deleteUserPlan = function (userPlanId) {
teaweb.confirm("确定要删除此套餐吗?", function () {
this.$post(".delete")
.params({
userPlanId: userPlanId
})
.refresh()
})
}
this.renewUserPlan = function (userPlanId) {
teaweb.popupSuccess(".renewPopup?userPlanId=" + userPlanId, null, "27em")
}
})

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>
<span v-if="userAccount != null">{{userAccount.total}}元</span>
<span v-else>0元</span>
</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
})
})
})