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,6 @@
<first-menu>
<menu-item href="." code="index">账户列表</menu-item>
<menu-item href=".update" code="update">充值消费</menu-item>
</first-menu>
<div class="margin"></div>

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item href="/finance/accounts">账户列表</menu-item>
<menu-item :href="'.?accountId=' + account.id" code="index">"{{account.user.fullname}}<span class="small">{{account.user.username}}</span>"账户详情</menu-item>
<menu-item :href="'.logs?accountId=' + account.id" code="log">收支明细</menu-item>
<menu-item :href="'.update?accountId=' + account.id" code="update">充值消费</menu-item>
</first-menu>

View File

@@ -0,0 +1,53 @@
{$layout}
{$template "menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">余额</td>
<td>{{account.total}}元</td>
</tr>
<tr>
<td>冻结</td>
<td>{{account.totalFrozen}}元 &nbsp; <tip-icon content="冻结后不能使用的余额"></tip-icon></td>
</tr>
</table>
<h3>最近收支明细</h3>
<p class="comment" v-if="logs.length == 0">暂时还没有操作日志。</p>
<table class="ui table selectable celled" v-if="logs.length > 0">
<thead>
<tr>
<th class="two wide">操作类型</th>
<th class="two wide">操作金额</th>
<th class="two wide">冻结金额</th>
<th class="two wide">剩余余额</th>
<th class="two wide">剩余冻结余额</th>
<th class="four wide">时间</th>
</tr>
</thead>
<tbody v-for="log in logs">
<tr>
<td>
<span v-if="log.event != null">{{log.event.name}}</span>
<span v-else class="disabled">其他</span>
</td>
<td>
<span v-if="log.delta != 0" :class="{red: log.delta < 0, green: log.delta > 0}">{{log.delta}}元</span>
<span v-else class="disabled">0</span>
</td>
<td>
<span v-if="log.deltaFrozen != 0">{{log.deltaFrozen}}元</span>
<span v-else class="disabled">0</span>
</td>
<td>{{log.total}}元</td>
<td><span :class="{disabled: log.totalFrozen == 0}">{{log.totalFrozen}}元</span></td>
<td>{{log.createdTime}}</td>
</tr>
<tr v-if="log.description.length > 0">
<td colspan="10">
<span class="grey">描述:<keyword :v-word="keyword">{{log.description}}</keyword></span>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,46 @@
{$layout}
{$template "menu"}
<p class="comment" v-if="logs.length == 0">暂时还没有操作日志。</p>
<table class="ui table selectable celled" v-if="logs.length > 0">
<thead>
<tr>
<th class="two wide">操作类型</th>
<th class="two wide">操作金额</th>
<th class="two wide">冻结金额</th>
<th class="two wide">剩余余额</th>
<th class="two wide">剩余冻结余额</th>
<th class="three wide">时间</th>
</tr>
</thead>
<tbody v-for="log in logs">
<tr>
<td>
<span v-if="log.event != null">{{log.event.name}}</span>
<span v-else class="disabled">其他</span>
</td>
<td>
<span v-if="log.delta != 0" :class="{red: log.delta < 0, green: log.delta > 0}">{{log.delta}}元</span>
<span v-else class="disabled">0</span>
</td>
<td>
<span v-if="log.deltaFrozen != 0">{{log.deltaFrozen}}元</span>
<span v-else class="disabled">0</span>
</td>
<td>{{log.total}}元</td>
<td>
<span :class="{disabled: log.totalFrozen == 0}">{{log.totalFrozen}}元</span>
</td>
<td>{{log.createdTime}}</td>
</tr>
<tr v-if="log.description.length > 0">
<td colspan="10">
<span class="grey">描述:<keyword :v-word="keyword">{{log.description}}</keyword></span>
</td>
</tr>
</tbody>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,40 @@
{$layout}
{$template "menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="accountId" :value="account.id"/>
<table class="ui table definition selectable">
<tr>
<td>当前账户余额</td>
<td>{{account.total}}元</td>
</tr>
<tr>
<td class="title">操作类型 *</td>
<td>
<select class="ui dropdown auto-width" name="eventType" v-model="eventType">
<option v-for="eventType in eventTypes" :value="eventType.code">{{eventType.name}}</option>
</select>
<p class="comment">{{event.description}}</p>
</td>
</tr>
<tr>
<td>操作金额 *</td>
<td>
<div class="ui input left right labeled">
<span class="ui label green" v-if="event != null && event.isPositive">+</span>
<span class="ui label red" v-if="event != null && !event.isPositive">-</span>
<input type="text" name="delta" value="" style="width: 8em" maxlength="8"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<textarea rows="3" name="description"></textarea>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,14 @@
Tea.context(function () {
this.eventType = this.eventTypes[0].code
this.event = this.eventTypes[0]
this.$delay(function () {
this.$watch("eventType", function (eventType) {
this.event = this.eventTypes.$find(function (k, v) {
return v.code == eventType
})
})
})
this.success = NotifyReloadSuccess("操作成功")
})

View File

@@ -0,0 +1,39 @@
{$layout}
{$template "menu"}
<form class="ui form" method="get" action="/finance/accounts">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="用户名、全名"/>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a href="/finance/accounts" v-if="keyword.length > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="accounts.length == 0">暂时还没有用户账户。</p>
<table class="ui table selectable celled" v-if="accounts.length > 0">
<thead>
<tr>
<th>用户</th>
<th class="three wide">余额</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="account in accounts">
<td>
<a :href="'/finance/accounts/account?accountId=' + account.id"><user-link :v-user="account.user" :v-keyword="keyword"></user-link></a>
</td>
<td>
{{account.total}}元
</td>
<td>
<a :href="'/finance/accounts/account?accountId=' + account.id">详情</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,44 @@
{$layout}
{$template "menu"}
<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>
<finance-user-selector @change="changeUser"></finance-user-selector>
<p class="comment">
<span v-if="userAccount != null">余额{{userAccount.total}}元。</span>
</p>
</td>
</tr>
<tr>
<td>操作类型 *</td>
<td>
<select class="ui dropdown auto-width" name="eventType" v-model="eventType">
<option v-for="eventType in eventTypes" :value="eventType.code">{{eventType.name}}</option>
</select>
<p class="comment">{{event.description}}</p>
</td>
</tr>
<tr>
<td>操作金额 *</td>
<td>
<div class="ui input left right labeled">
<span class="ui label green" v-if="event != null && event.isPositive">+</span>
<span class="ui label red" v-if="event != null && !event.isPositive">-</span>
<input type="text" name="delta" value="" style="width: 8em" maxlength="8"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<textarea rows="3" name="description"></textarea>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,27 @@
Tea.context(function () {
this.eventType = this.eventTypes[0].code
this.event = this.eventTypes[0]
this.$delay(function () {
this.$watch("eventType", function (eventType) {
this.event = this.eventTypes.$find(function (k, v) {
return v.code == eventType
})
})
})
this.success = NotifyReloadSuccess("操作成功")
this.userAccount = null
this.changeUser = function (userId) {
if (userId > 0) {
this.$post(".userAccount")
.params({userId: userId})
.success(function (resp) {
this.userAccount = resp.data.account
})
} else {
this.userAccount = null
}
}
})

View File

@@ -0,0 +1,4 @@
<first-menu>
<menu-item href="/finance/bills" code="index">账单</menu-item>
<!--<menu-item href="/finance/bills/generate" code="generate">生成账单</menu-item>-->
</first-menu>

View File

@@ -0,0 +1,146 @@
{$layout}
{$template "menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">账单编号</td>
<td>{{bill.code}}</td>
</tr>
<tr>
<td>用户</td>
<td><user-link :v-user="bill.user"></user-link></td>
</tr>
<tr>
<td>结算周期</td>
<td>{{bill.pricePeriodName}}</td>
</tr>
<tr>
<td>月份</td>
<td>{{bill.month}}</td>
</tr>
<tr v-if="bill.pricePeriod == 'daily'">
<td>日期</td>
<td>
<span v-if="bill.pricePeriod == 'daily'">
<span v-if="bill.dayFrom == bill.dayTo">{{bill.dayFrom}}</span>
<span v-else>{{bill.dayFrom}} - {{bill.dayTo}}</span>
</span>
</td>
</tr>
<tr>
<td>项目</td>
<td>{{bill.typeName}}</td>
</tr>
<tr>
<td>金额</td>
<td>¥{{bill.amount}}元
<span v-if="!bill.canPay" class="small grey">&nbsp;(预估)</span>
</td>
</tr>
<tr>
<td>描述</td>
<td>{{bill.description}}</td>
</tr>
<tr>
<td>已支付</td>
<td>
<span class="green" v-if="bill.isPaid">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>操作</td>
<td>
<span v-if="bill.isPaid" class="disabled" title="已支付,无需重复支付">已支付</span>
<span v-else>
<a href="" v-if="bill.canPay" @click.prevent="payBill(bill.id)">支付</a>
<span v-else class="disabled"></span>
</span>
</td>
</tr>
</table>
<div v-if="serverBills.length > 0">
<h4>网站/套餐子账单</h4>
<table class="ui table selectable celled">
<thead>
<tr>
<th>网站</th>
<th>套餐</th>
<th>流量</th>
<th>带宽</th>
<th>费用</th>
<th>更新时间</th>
</tr>
</thead>
<tr v-for="serverBill in serverBills">
<td>
<span v-if="serverBill.server != null && serverBill.server.id > 0">{{serverBill.server.name}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="serverBill.plan.id > 0">
{{serverBill.plan.name}}
<span class="small grey" v-if="serverBill.plan.priceType == 'traffic'">(按流量计费)</span>
<span class="small grey" v-if="serverBill.plan.priceType == 'bandwidth'">(按带宽计费)</span>
</span>
<span v-else>默认
<span v-if="serverBill.priceType == 'traffic'" class="small grey">(按流量计费)</span>
<span v-if="serverBill.priceType == 'bandwidth'" class="small grey">(按带宽计费)</span>
<span v-if="serverBill.priceType.length == 0" class="small grey">(按区域流量计费)</span>
</span>
</td>
<td>{{serverBill.traffic}}</td>
<td>{{serverBill.bandwidthPercentileSize}}<span class="grey small">{{serverBill.bandwidthPercentile}}th</span></td>
<td>{{serverBill.amount}}元</td>
<td>{{serverBill.createdTime}}</td>
</tr>
</table>
<div v-html="page"></div>
</div>
<div v-if="trafficBills.length > 0">
<h4>流量子账单</h4>
<table class="ui table selectable celled">
<thead>
<tr>
<th>区域</th>
<th>计费方式</th>
<th>计费带宽</th>
<th>带宽百分位</th>
<th>计费流量</th>
<th>流量包使用</th>
<th>单位价格</th>
<th>总价格</th>
</tr>
</thead>
<tr v-for="trafficBill in trafficBills">
<td>
<span v-if="trafficBill.region.id > 0">{{trafficBill.region.name}}</span>
<span v-else class="disabled">[未分区]</span>
</td>
<td>{{trafficBill.priceTypeName}}</td>
<td>
<span v-if="trafficBill.priceType == 'bandwidth'">{{trafficBill.bandwidthMB}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="trafficBill.priceType == 'bandwidth'">{{trafficBill.bandwidthPercentile}}th</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="trafficBill.priceType == 'traffic'">{{trafficBill.trafficGB}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="trafficBill.priceType == 'traffic' && trafficBill.trafficPackageGB != '0B'">{{trafficBill.trafficPackageGB}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="trafficBill.priceType == 'bandwidth'">{{trafficBill.pricePerUnit}}元/Mbps</span>
<span v-if="trafficBill.priceType == 'traffic'">{{trafficBill.pricePerUnit}}元/GiB</span>
</td>
<td>{{trafficBill.amount}}元</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,15 @@
Tea.context(function () {
this.payBill = function (billId) {
teaweb.confirm("确定要使用用户账户支付此账单吗? ", function () {
this.$post(".pay")
.params({
billId: billId
})
.success(function () {
teaweb.success("支付成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,8 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<div>
<button type="button" class="ui primary button" @click.prevent="generateBills()">生成上个月({{month}})账单</button>
<p class="comment">多次点击不会重复生成同样的账单。</p>
</div>

View File

@@ -0,0 +1,16 @@
Tea.context(function () {
this.generateBills = function () {
let that = this
teaweb.confirm("确定要生成上个月的账单吗?", function () {
that.$post(".generate")
.params({
month: that.month
})
.success(function () {
teaweb.success("生成成功", function () {
window.location = "/finance/bills"
})
})
})
}
})

View File

@@ -0,0 +1,55 @@
{$layout}
{$template "menu"}
<p class="comment" v-if="bills.length == 0">暂时还没有账单。</p>
<table class="ui table selectable celled" v-if="bills.length > 0">
<thead>
<tr>
<th class="three wide">账单编号</th>
<th>用户</th>
<th style="width: 6em">结算周期</th>
<th style="width: 6em">月份</th>
<th style="width: 7em">日期</th>
<th style="width: 7em">项目</th>
<th class="wide10">金额</th>
<th style="width: 5em">已支付</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="bill in bills">
<td>
<a :href="'/finance/bills/bill?billId=' + bill.id">{{bill.code}}</a>
<div v-if="bill.isOverdue">
<span class="ui red basic small">逾期</span>
</div>
</td>
<td><user-link :v-user="bill.user"></user-link></td>
<td>{{bill.pricePeriodName}}</td>
<td>{{bill.month}}</td>
<td>
<span v-if="bill.pricePeriod == 'daily'">
<span v-if="bill.dayFrom == bill.dayTo">{{bill.dayFrom}}</span>
<span v-else>{{bill.dayFrom}} - {{bill.dayTo}}</span>
</span>
</td>
<td>{{bill.typeName}}</td>
<td><span :class="{disabled: bill.amount == '0'}">¥{{bill.amount}}元</span>
<span v-if="!bill.canPay" class="small grey"><br/>预估</span>
</td>
<td>
<span class="green" v-if="bill.isPaid">Y</span>
<span v-else class="disabled">N</span>
</td>
<td>
<span v-if="bill.isPaid" class="disabled" title="已支付,无需重复支付">已支付</span>
<span v-else>
<a href="" v-if="bill.canPay" @click.prevent="payBill(bill.id)">支付</a>
<span v-else class="disabled"></span>
</span>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,15 @@
Tea.context(function () {
this.payBill = function (billId) {
teaweb.confirm("确定要使用用户账户支付此账单吗? ", function () {
this.$post(".pay")
.params({
billId: billId
})
.success(function () {
teaweb.success("支付成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,9 @@
<first-menu>
<menu-item code="basic" href=".">基础设置</menu-item>
<menu-item code="bandwidth" href=".bandwidth">按区域带宽计费</menu-item>
<menu-item code="traffic" href=".traffic">按区域流量计费</menu-item>
<span class="item disabled">|</span>
<menu-item code="calculator" href=".calculator">价格计算器</menu-item>
<span class="item disabled">|</span>
<a href="/clusters/regions" class="item" target="_blank">区域设置 &nbsp; <i class="icon external small"></i></a>
</first-menu>

View File

@@ -0,0 +1,20 @@
th span {
font-size: 0.9em;
font-weight: normal;
color: grey;
}
th a {
font-weight: normal;
visibility: hidden;
}
th:hover a {
visibility: visible;
}
td a {
visibility: hidden;
font-size: 0.8em;
}
td:hover a {
visibility: visible;
}
/*# sourceMappingURL=bandwidth.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["bandwidth.less"],"names":[],"mappings":"AAAA,EACC;EACC,gBAAA;EACA,mBAAA;EACA,WAAA;;AAJF,EAOC;EACC,mBAAA;EACA,kBAAA;;AAIF,EAAE,MACD;EACC,mBAAA;;AAIF,EACC;EACC,kBAAA;EACA,gBAAA;;AAIF,EAAE,MACD;EACC,mBAAA","file":"bandwidth.css"}

View File

@@ -0,0 +1,40 @@
{$layout}
{$template "menu"}
<div class="ui message warning" v-if="config.bandwidthPrice != null && !config.bandwidthPrice.supportRegions">
尚未开启按区域带宽计费。 &nbsp; <a href="/finance/fee">[修改]</a>
</div>
<p class="comment" v-if="regions.length == 0">暂时还没有区域。</p>
<table class="ui table selectable small definition celled">
<thead class="full-width">
<tr>
<th class="three wide">区域\带宽区间</th>
<th v-for="item in items" class="center">
{{item.name}}
<br/>
<span>{{item.minSize}}-{{item.maxSize}}</span>
<br/>
&nbsp; <a href="" title="修改" @click.prevent="updateItem(item.id)"><i class="icon pencil small"></i></a> &nbsp;
<a href="" title="删除" @click.prevent="deleteItem(item.id)"><i class="icon remove small"></i></a>
</th>
<th class="width10 center">
<a href="" @click.prevent="createItem" style="visibility: visible">[+添加带宽区间]</a>
</th>
</tr>
</thead>
<tr v-for="region in regions">
<td class="">{{region.name}}</td>
<td v-for="item in items" class="center">
<div>
<span v-if="region.prices[item.id.toString()] != null">¥{{region.prices[item.id.toString()]}}元/Mbps &nbsp;</span>
<span v-else>&nbsp;</span>
</div>
<div>
<a href="" title="修改单位价格" @click.prevent="updatePrice(region.id, item.id)">[设置]</a>
</div>
</td>
<td></td>
</tr>
</table>

View File

@@ -0,0 +1,42 @@
Tea.context(function () {
this.createItem = function () {
teaweb.popup(Tea.url(".items.createBandwidthPopup"), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".items.updateBandwidthPopup", {itemId: itemId}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteItem = function (itemId) {
let that = this
teaweb.confirm("确定要删除此带宽区间吗?", function () {
that.$post(".items.delete")
.params({
itemId: itemId
})
.refresh()
})
}
this.updatePrice = function (regionId, itemId) {
teaweb.popup(Tea.url(".updatePricePopup", {regionId: regionId, itemId: itemId}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
})

View File

@@ -0,0 +1,31 @@
th {
span {
font-size: 0.9em;
font-weight: normal;
color: grey;
}
a {
font-weight: normal;
visibility: hidden;
}
}
th:hover {
a {
visibility: visible;
}
}
td {
a {
visibility: hidden;
font-size: 0.8em;
}
}
td:hover {
a {
visibility: visible;
}
}

View File

@@ -0,0 +1,72 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<form class="ui form" data-tea-action="$" data-tea-success="success" ref="calculatorForm">
<table class="ui table definition selectable">
<tr>
<td class="title">计费类型</td>
<td>
<radio name="priceType" :v-value="'bandwidth'" v-model="config.priceType">按带宽计费</radio>
&nbsp; &nbsp;
<radio name="priceType" :v-value="'traffic'" v-model="config.priceType">按流量计费</radio> &nbsp; &nbsp;
</td>
</tr>
<tr v-show="config.priceType == 'bandwidth'">
<td>所需带宽</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="bandwidth" size="10" maxlength="10" @input="change" ref="bandwidthInput"/>
</div>
<div class="ui field">
<select class="ui dropdown auto-width" name="bandwidthUnit" @change="change">
<option value="mb">Mbps</option>
<option value="gb">Gbps</option>
<option value="tb">Tbps</option>
</select>
</div>
</div>
</td>
</tr>
<tr v-show="config.priceType == 'traffic'">
<td>所需流量</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="traffic" size="10" maxlength="10" @input="change" ref="trafficInput"/>
</div>
<div class="ui field">
<select class="ui dropdown auto-width" name="trafficUnit" @change="change">
<option value="gb">GiB</option>
<option value="tb">TiB</option>
<option value="eb">EiB</option>
</select>
</div>
</div>
</td>
</tr>
<tr v-show="regions.length > 0">
<td>所在区域</td>
<td>
<select class="ui dropdown auto-width" name="regionId" @change="change">
<option value="0">[不区分区域]</option>
<option v-for="region in regions" :value="region.id">{{region.name}}</option>
</select>
</td>
</tr>
<tr>
<td>价格</td>
<td>
<strong v-if="formattedAmount.length > 0">
¥{{formattedAmount}}元
<span v-if="hasRegionPrice" class="small grey" style="font-weight: normal">(基于区域价格设定)</span>
</strong>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>
<submit-btn>计算价格</submit-btn>
</form>

View File

@@ -0,0 +1,46 @@
Tea.context(function () {
this.formattedAmount = ""
this.hasRegionPrice = false
this.$delay(function () {
this.changePriceType(this.config.priceType)
this.$watch("config.priceType", function (priceType) {
this.formattedAmount = ""
this.changePriceType(priceType)
})
})
this.success = function (resp) {
this.formattedAmount = resp.data.amountFormatted
this.hasRegionPrice = resp.data.hasRegionPrice
}
this.changePriceType = function (priceType) {
switch (priceType) {
case "traffic":
this.$refs.trafficInput.focus()
break
case "bandwidth":
this.$refs.bandwidthInput.focus()
break
}
}
this.requestId = ""
this.change = function () {
this.formattedAmount = ""
this.hasRegionPrice = false
let requestId = Math.random().toString()
this.requestId = requestId
this.$post("$")
.form(this.$refs.calculatorForm)
.success(function (resp) {
if (requestId == this.requestId) {
this.success(resp)
}
})
}
})

View File

@@ -0,0 +1,156 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<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>
<checkbox name="isOn" v-model="config.isOn"></checkbox>
</td>
</tr>
<tbody v-show="config.isOn">
<tr v-show="!config.enablePlans">
<td>默认结算周期</td>
<td>
<radio name="pricePeriod" :v-value="'daily'" v-model="config.pricePeriod">按日结算</radio> &nbsp; &nbsp;
<radio name="pricePeriod" :v-value="'monthly'" v-model="config.pricePeriod">按月结算</radio> &nbsp;
<div v-if="config.pricePeriod == 'daily'">
<p class="comment">按自然日流量或带宽峰值结算,每天出前一天的账单。此选项不适用于套餐计费。<br/><strong style="margin-top: 0.3em;display: block">请谨慎修改此选项,如果当月已生成了按月结算账单,则结算周期变更需要在下个月才会生效。</strong></div>
<p class="comment" v-if="config.pricePeriod == 'monthly'">按自然月流量或带宽峰值结算,每月一日出上月的账单。此选项不适用于套餐计费。<br/><strong style="margin-top: 0.3em;display: block">请谨慎修改此选项,如果当月已生成了按日结算账单,则结算周期需要在下个月才会生效。</strong></p>
</td>
</tr>
<tr>
<td class="title">默认计费方式</td>
<td>
<radio name="priceType" :v-value="'bandwidth'" v-model="config.priceType">按带宽计费</radio> &nbsp; &nbsp;
<radio name="priceType" :v-value="'traffic'" v-model="config.priceType">按流量计费</radio> &nbsp; &nbsp;
<p class="comment">用户默认的计费方式,<strong>请谨慎修改此选项,如果当月已经生成了账单,则计费方式需要在下个月才会生效。</strong></p>
</td>
</tr>
<tr>
<td>按带宽计费设置</td>
<td>
<plan-price-bandwidth-config-box :v-plan-price-bandwidth-config="config.bandwidthPrice"></plan-price-bandwidth-config-box>
</td>
</tr>
<tr>
<td>按流量计费设置</td>
<td>
<plan-price-traffic-config-box :v-plan-price-traffic-config="config.trafficPrice"></plan-price-traffic-config-box>
</td>
</tr>
<tr>
<td class="color-border">使用套餐计费</td>
<td>
<checkbox name="enablePlans" v-model="config.enablePlans"></checkbox>
<p class="comment">选中后,表示使用套餐计费,而不使用流量和带宽计费;开启后可以在 <a href="/plans">[套餐管理]</a> 中添加套餐,服务可能在绑定套餐后才能使用;如果不开启,则表示自动使用流量和带宽阶梯价格计费。<br/><strong style="margin-top: 0.3em;display: block">请谨慎选择此选项,如果当月已经产生了按流量/带宽账单,再修改为使用套餐计费,可能会重复产生费用;同样,如果当月已经产生了套餐计费,再改为流量/带宽计费,也可能会重复产生费用。</strong></p>
</td>
</tr>
<tr v-show="config.enablePlans">
<td class="color-border">允许用户管理套餐</td>
<td>
<checkbox name="showPlansInUserSystem" v-model="config.showPlansInUserSystem"></checkbox>
<p class="comment">选中后,在用户界面中显示套餐列表,用户可以购买和续费套餐;取消选中,表示用户界面中不显示套餐列表。</p>
</td>
</tr>
</tbody>
</table>
<div v-show="config.isOn && !config.enablePlans">
<h4 style="margin-top: 1.5em">流量包</h4>
<table class="ui table selectable definition">
<tr>
<td class="title">启用流量包</td>
<td>
<checkbox name="enableTrafficPackages" v-model="config.enableTrafficPackages"></checkbox>
<p class="comment">启用后,在流量计费时优先从用户已购流量包中扣除。</p>
</td>
</tr>
<tr v-show="config.enableTrafficPackages">
<td>在用户界面显示流量包</td>
<td>
<checkbox name="showTrafficPackages" v-model="config.showTrafficPackages"></checkbox>
<p class="comment">在用户界面显示流量包相关内容,用户也可以购买流量包。</p>
</td>
</tr>
</table>
</div>
<div v-show="config.isOn && !config.enablePlans">
<h4 style="margin-top: 1.5em">用户自定义</h4>
<table class="ui table selectable definition">
<tr>
<td class="title">允许用户自选计费方式</td>
<td>
<checkbox name="userCanChangePriceType" v-model="config.userCanChangePriceType"></checkbox>
<p class="comment">选中后,表示允许用户可以自选计费方式(按流量还是按带宽);取消选中后,所有用户都将使用默认的计费方式。</p>
</td>
</tr>
<tr>
<td>允许用户自选计费周期</td>
<td>
<checkbox name="userCanChangePricePeriod" v-model="config.userCanChangePricePeriod"></checkbox>
<p class="comment">选中后,表示允许用户可以自选计费周期(按月还是按日);取消选中后,所有用户都将使用默认的计费周期。</p>
</td>
</tr>
</table>
<h4 style="margin-top: 1.5em">用户界面</h4>
<table class="ui table selectable definition">
<tr>
<td class="title">显示价格和价格计算器</td>
<td>
<checkbox name="userUIShowPrices" v-model="config.userUI.showPrices"></checkbox>
<p class="comment">选中后,表示在用户界面中显示价格和价格计算器。</p>
</td>
</tr>
</table>
</div>
<div v-show="config.isOn">
<h4 style="margin-top: 1.5em">逾期账单处理</h4>
<table class="ui table selectable definition">
<tr>
<td class="title">开启逾期账单处理</td>
<td>
<checkbox name="unpaidBillPolicyIsOn" v-model="config.unpaidBillPolicy.isOn"></checkbox>
<p class="comment">开启后,有逾期未支付账单,将会提醒用户。</p>
</td>
</tr>
<tr v-show="config.unpaidBillPolicy.isOn">
<td>日账单逾期天数</td>
<td>
<div class="ui input right labeled">
<input type="text" name="unpaidBillPolicyMinDailyBillDays" maxlength="4" style="width: 5em" v-model="config.unpaidBillPolicy.minDailyBillDays"/>
<span class="ui label"></span>
</div>
<p class="comment">按日结算的账单超过一定天数没有支付则认为逾期如果为0表示没有限制。</p>
</td>
</tr>
<tr v-show="config.unpaidBillPolicy.isOn">
<td>月账单逾期天数</td>
<td>
<div class="ui input right labeled">
<input type="text" name="unpaidBillPolicyMinMonthlyBillDays" maxlength="4" style="width: 5em" v-model="config.unpaidBillPolicy.minMonthlyBillDays"/>
<span class="ui label"></span>
</div>
<p class="comment">按月结算的账单超过一定天数没有支付则认为逾期如果为0表示没有限制。</p>
</td>
</tr>
<tr v-show="config.unpaidBillPolicy.isOn">
<td>逾期后停止网站</td>
<td>
<checkbox name="unpaidBillPolicyDisableServers" v-model="config.unpaidBillPolicy.disableServers"></checkbox>
<p class="comment">选中后,表示有逾期账单时停止用户的网站。</p>
</td>
</tr>
</table>
</div>
<div class="margin"></div>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,36 @@
{$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>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
<tr>
<td>最低带宽 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="minMB" maxlength="12" style="width:10em" v-model="minMB" autocomplete="off"/>
<span class="ui label">Mbps</span>
</div>
<p class="comment">使用比特作为最小单位。<span v-if="minSize.length > 0">相当于:{{minSize}}。</span></p>
</td>
</tr>
<tr>
<td>最高带宽 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="maxMB" maxlength="12" style="width:10em" v-model="maxMB" autocomplete="off"/>
<span class="ui label">Mbps</span>
</div>
<p class="comment">使用比特作为最小单位0表示没有限制。<span v-if="maxSize.length > 0">相当于:{{maxSize}}。</span></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,29 @@
Tea.context(function () {
this.minMB = ""
this.minSize = ""
this.maxMB = ""
this.maxSize = ""
this.$delay(function () {
let that = this
this.$watch("minMB", function (v) {
v = parseInt(v)
if (isNaN(v) || v <= 0) {
that.minSize = ""
} else {
that.minSize = teaweb.formatBits(v * Math.pow(1024, 2))
}
})
this.$watch("maxMB", function (v) {
v = parseInt(v)
if (isNaN(v) || v < 0) {
that.maxSize = ""
} else if (v == 0) {
that.maxSize = "∞"
} else {
that.maxSize = teaweb.formatBits(v * Math.pow(1024, 2))
}
})
})
})

View File

@@ -0,0 +1,4 @@
h3 var {
font-style: normal;
}
/*# sourceMappingURL=createPopup.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["createPopup.less"],"names":[],"mappings":"AAAA,EAAG;EACF,kBAAA","file":"createPopup.css"}

View File

@@ -0,0 +1,36 @@
{$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>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
<tr>
<td>最低流量 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="minGB" maxlength="12" style="width:10em" v-model="minGB" autocomplete="off"/>
<span class="ui label">GiB</span>
</div>
<p class="comment">使用字节作为最小单位。<span v-if="minSize.length > 0">相当于:{{minSize}}。</span></p>
</td>
</tr>
<tr>
<td>最高流量 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="maxGB" maxlength="12" style="width:10em" v-model="maxGB" autocomplete="off"/>
<span class="ui label">GiB</span>
</div>
<p class="comment">使用字节作为最小单位0表示没有限制。<span v-if="maxSize.length > 0">相当于:{{maxSize}}。</span></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,29 @@
Tea.context(function () {
this.minGB = ""
this.minSize = ""
this.maxGB = ""
this.maxSize = ""
this.$delay(function () {
let that = this
this.$watch("minGB", function (v) {
v = parseInt(v)
if (isNaN(v) || v <= 0) {
that.minSize = ""
} else {
that.minSize = teaweb.formatBytes(v * Math.pow(1024, 3))
}
})
this.$watch("maxGB", function (v) {
v = parseInt(v)
if (isNaN(v) || v < 0) {
that.maxSize = ""
} else if (v == 0) {
that.maxSize = "∞"
} else {
that.maxSize = teaweb.formatBytes(v * Math.pow(1024, 3))
}
})
})
})

View File

@@ -0,0 +1,3 @@
h3 var {
font-style: normal;
}

View File

@@ -0,0 +1,37 @@
{$layout "layout_popup"}
<h3>修改带宽区间</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="itemId" :value="item.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="item.name"/>
</td>
</tr>
<tr>
<td>最低带宽 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="minMB" maxlength="12" style="width:10em" v-model="minMB" autocomplete="off"/>
<span class="ui label">Mbps</span>
</div>
<p class="comment">使用比特作为最小单位。<span v-if="minSize.length > 0">相当于:{{minSize}}。</span></p>
</td>
</tr>
<tr>
<td>最高带宽 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="maxMB" maxlength="12" style="width:10em" v-model="maxMB" autocomplete="off"/>
<span class="ui label">Mbps</span>
</div>
<p class="comment">使用比特作为最小单位0表示没有限制。<span v-if="maxSize.length > 0">相当于:{{maxSize}}。</span></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,32 @@
Tea.context(function () {
this.minMB = 0
this.minSize = ""
this.maxMB = 0
this.maxSize = ""
this.$delay(function () {
let that = this
this.$watch("minMB", function (v) {
v = parseInt(v)
if (isNaN(v) || v <= 0) {
that.minSize = ""
} else {
that.minSize = teaweb.formatBits(v * Math.pow(1024, 2))
}
})
this.$watch("maxMB", function (v) {
v = parseInt(v)
if (isNaN(v) || v < 0) {
that.maxSize = ""
} else if (v == 0) {
that.maxSize = "∞"
} else {
that.maxSize = teaweb.formatBits(v * Math.pow(1024, 2))
}
})
this.minMB = this.item.minMB
this.maxMB = this.item.maxMB
})
})

View File

@@ -0,0 +1,37 @@
{$layout "layout_popup"}
<h3>修改流量区间</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="itemId" :value="item.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="item.name"/>
</td>
</tr>
<tr>
<td>最低流量 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="minGB" maxlength="12" style="width:10em" v-model="minGB" autocomplete="off"/>
<span class="ui label">GiB</span>
</div>
<p class="comment">使用字节作为最小单位。<span v-if="minSize.length > 0">相当于:{{minSize}}。</span></p>
</td>
</tr>
<tr>
<td>最高流量 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="maxGB" maxlength="12" style="width:10em" v-model="maxGB" autocomplete="off"/>
<span class="ui label">GiB</span>
</div>
<p class="comment">使用字节作为最小单位0表示没有限制。<span v-if="maxSize.length > 0">相当于:{{maxSize}}。</span></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,32 @@
Tea.context(function () {
this.minGB = 0
this.minSize = ""
this.maxGB = 0
this.maxSize = ""
this.$delay(function () {
let that = this
this.$watch("minGB", function (v) {
v = parseInt(v)
if (isNaN(v) || v <= 0) {
that.minSize = ""
} else {
that.minSize = teaweb.formatBytes(v * Math.pow(1024, 3))
}
})
this.$watch("maxGB", function (v) {
v = parseInt(v)
if (isNaN(v) || v < 0) {
that.maxSize = ""
} else if (v == 0) {
that.maxSize = "∞"
} else {
that.maxSize = teaweb.formatBytes(v * Math.pow(1024, 3))
}
})
this.minGB = this.item.minGB
this.maxGB = this.item.maxGB
})
})

View File

@@ -0,0 +1,20 @@
th span {
font-size: 0.9em;
font-weight: normal;
color: grey;
}
th a {
font-weight: normal;
visibility: hidden;
}
th:hover a {
visibility: visible;
}
td a {
visibility: hidden;
font-size: 0.8em;
}
td:hover a {
visibility: visible;
}
/*# sourceMappingURL=prices.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["prices.less"],"names":[],"mappings":"AAAA,EACC;EACC,gBAAA;EACA,mBAAA;EACA,WAAA;;AAJF,EAOC;EACC,mBAAA;EACA,kBAAA;;AAIF,EAAE,MACD;EACC,mBAAA;;AAIF,EACC;EACC,kBAAA;EACA,gBAAA;;AAIF,EAAE,MACD;EACC,mBAAA","file":"prices.css"}

View File

@@ -0,0 +1,40 @@
{$layout}
{$template "menu"}
<div class="ui message warning" v-if="config.trafficPrice != null && !config.trafficPrice.supportRegions">
尚未开启按区域流量计费。 &nbsp; <a href="/finance/fee">[修改]</a>
</div>
<p class="comment" v-if="regions.length == 0">暂时还没有区域。</p>
<table class="ui table selectable small definition celled" v-if="regions.length > 0">
<thead class="full-width">
<tr>
<th class="three wide">区域\流量区间</th>
<th v-for="item in items" class="center">
{{item.name}}
<br/>
<span>{{item.minSize}}-{{item.maxSize}}</span>
<br/>
&nbsp; <a href="" title="修改" @click.prevent="updateItem(item.id)"><i class="icon pencil small"></i></a> &nbsp;
<a href="" title="删除" @click.prevent="deleteItem(item.id)"><i class="icon remove small"></i></a>
</th>
<th class="width10 center">
<a href="" @click.prevent="createItem" style="visibility: visible">[+添加流量区间]</a>
</th>
</tr>
</thead>
<tr v-for="region in regions">
<td class="">{{region.name}}</td>
<td v-for="item in items" class="center">
<div>
<span v-if="region.prices[item.id.toString()] != null">¥{{region.prices[item.id.toString()]}}元/GiB &nbsp;</span>
<span v-else>&nbsp;</span>
</div>
<div>
<a href="" title="修改单位价格" @click.prevent="updatePrice(region.id, item.id)">[设置]</a>
</div>
</td>
<td></td>
</tr>
</table>

View File

@@ -0,0 +1,42 @@
Tea.context(function () {
this.createItem = function () {
teaweb.popup(Tea.url(".items.createTrafficPopup"), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".items.updateTrafficPopup", {itemId: itemId}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteItem = function (itemId) {
let that = this
teaweb.confirm("确定要删除此流量区间吗?", function () {
that.$post(".items.delete")
.params({
itemId: itemId
})
.refresh()
})
}
this.updatePrice = function (regionId, itemId) {
teaweb.popup(Tea.url(".updatePricePopup", {regionId: regionId, itemId: itemId}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
})

View File

@@ -0,0 +1,31 @@
th {
span {
font-size: 0.9em;
font-weight: normal;
color: grey;
}
a {
font-weight: normal;
visibility: hidden;
}
}
th:hover {
a {
visibility: visible;
}
}
td {
a {
visibility: hidden;
font-size: 0.8em;
}
}
td:hover {
a {
visibility: visible;
}
}

View File

@@ -0,0 +1,40 @@
{$layout "layout_popup"}
<h3 v-if="item.type == 'traffic'">修改流量价格</h3>
<h3 v-if="item.type == 'bandwidth'">修改带宽价格</h3>
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="regionId" :value="region.id"/>
<input type="hidden" name="itemId" :value="item.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">区域</td>
<td>{{region.name}}</td>
</tr>
<tr>
<td>价格项名称</td>
<td>{{item.name}}</td>
</tr>
<tr v-show="item.type == 'traffic'">
<td>流量范围</td>
<td>{{item.minSize}} - {{item.maxSize}}</td>
</tr>
<tr v-show="item.type == 'bandwidth'">
<td>带宽范围</td>
<td>{{item.minSize}} - {{item.maxSize}}</td>
</tr>
<tr>
<td class="title">单位价格 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="price" maxlength="10" style="width:6em" ref="focus" v-model="price"/>
<span class="ui label" v-if="item.type == 'traffic'">元/GiB</span>
<span class="ui label" v-if="item.type == 'bandwidth'">元/Mbps</span>
</div>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item href="." code="daily">按天统计</menu-item>
<menu-item href=".monthly" code="monthly">按月统计</menu-item>
</first-menu>
<div class="margin"></div>

View File

@@ -0,0 +1,27 @@
{$layout}
<first-menu>
<menu-item href="/finance/income/monthly">按月统计</menu-item>
<menu-item :href="'/finance/income/daily?month=' + month" class="active">{{month}}</menu-item>
</first-menu>
<table class="ui table selectable celled" v-if="stats.length > 0">
<thead>
<tr>
<th>日期</th>
<th>收入</th>
<th>支出</th>
</tr>
</thead>
<tr v-for="stat in stats">
<td>{{stat.day}}</td>
<td>
<span v-if="stat.income > 0">{{stat.income}}元</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="stat.expense > 0">-{{stat.expense}}元</span>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,38 @@
{$layout}
{$template "menu"}
<form class="ui form" action="/finance/income">
<div class="ui fields inline">
<div class="ui field">
<datepicker :v-name="'dayFrom'" :v-value="dayFrom"></datepicker>
</div>
<div class="ui field">-</div>
<div class="ui field">
<datepicker :v-name="'dayTo'" :v-value="dayTo"></datepicker>
</div>
<div class="ui field">
<button class="ui button" type="submit">查看</button>
</div>
</div>
</form>
<table class="ui table selectable celled" v-if="stats.length > 0">
<thead>
<tr>
<th>日期</th>
<th>收入</th>
<th>支出</th>
</tr>
</thead>
<tr v-for="stat in stats">
<td>{{stat.day}}</td>
<td>
<span v-if="stat.income > 0">{{stat.income}}元</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="stat.expense > 0">-{{stat.expense}}元</span>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,40 @@
{$layout}
{$template "menu"}
<form class="ui form" action="/finance/income/monthly">
<div class="ui fields inline">
<div class="ui field">
<datepicker :v-name="'dayFrom'" :v-value="dayFrom"></datepicker>
</div>
<div class="ui field">-</div>
<div class="ui field">
<datepicker :v-name="'dayTo'" :v-value="dayTo"></datepicker>
</div>
<div class="ui field">
<button class="ui button" type="submit">查看</button>
</div>
</div>
</form>
<table class="ui table selectable celled" v-if="stats.length > 0">
<thead>
<tr>
<th>月份</th>
<th>收入</th>
<th>支出</th>
</tr>
</thead>
<tr v-for="stat in stats">
<td>
<a :href="'/finance/income/daily?month=' + stat.month">{{stat.month}}</a>
</td>
<td>
<span v-if="stat.income > 0">{{stat.income}}元</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="stat.expense > 0">-{{stat.expense}}元</span>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,66 @@
{$layout}
<form class="ui form" action="/finance/logs">
<div class="ui fields inline">
<div class="ui field">
<input type="text" placeholder="用户名、姓名、描述" name="keyword" v-model="keyword"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="eventType" v-model="eventType">
<option value="">[类型]</option>
<option v-for="event in events" :value="event.code">{{event.name}}</option>
</select>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a href="/finance/logs" v-if="keyword.length > 0 || eventType.length > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="logs.length == 0">暂时还没有操作日志。</p>
<table class="ui table selectable celled" v-if="logs.length > 0">
<thead>
<tr>
<th class="two wide">用户</th>
<th class="two wide">操作类型</th>
<th class="two wide">操作金额</th>
<th class="two wide">冻结金额</th>
<th class="two wide">剩余余额</th>
<th class="two wide">剩余冻结余额</th>
<th class="three wide">时间</th>
</tr>
</thead>
<tbody v-for="log in logs">
<tr>
<td>
<a :href="'/finance/accounts/account?accountId=' + log.accountId"><user-link :v-user="log.user" :v-keyword="keyword"></user-link></a>
</td>
<td>
<span v-if="log.event != null">{{log.event.name}}</span>
<span v-else class="disabled">其他</span>
</td>
<td>
<span v-if="log.delta != 0" :class="{red: log.delta < 0, green: log.delta > 0}">{{log.deltaFormatted}}元</span>
<span v-else class="disabled">0</span>
</td>
<td>
<span v-if="log.deltaFrozen != 0">{{log.deltaFrozen}}元</span>
<span v-else class="disabled">0</span>
</td>
<td>{{log.totalFormatted}}元</td>
<td><span :class="{disabled: log.totalFrozen == 0}">{{log.totalFrozen}}元</span></td>
<td>{{log.createdTime}}</td>
</tr>
<tr v-if="log.description.length > 0">
<td colspan="10">
<span class="grey">描述:<keyword :v-word="keyword">{{log.description}}</keyword></span>
</td>
</tr>
</tbody>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,5 @@
<first-menu>
<menu-item href="/finance/orders" code="order">订单列表</menu-item>
<menu-item href="/finance/orders/methods" code="method">支付方式</menu-item>
<menu-item href="/finance/orders/setting" code="setting">订单设置</menu-item>
</first-menu>

View File

@@ -0,0 +1,69 @@
{$layout}
{$template "menu"}
<p class="ui message warning" v-if="!enablePay">暂时未启用在线支付,去<a href="/finance/orders/setting">设置</a></p>
<div class="margin"></div>
<form class="ui form" action="/finance/orders">
<div class="ui fields inline">
<div class="ui field">
<finance-user-selector :v-user-id="userId"></finance-user-selector>
</div>
<div class="ui field">
<select class="ui dropdown" name="status" v-model="status">
<option value="">[状态]</option>
<option v-for="statusDef in statusList" :value="statusDef.code">{{statusDef.name}}</option>
</select>
</div>
<div class="ui field">
<input type="text" placeholder="订单号" name="keyword" v-model="keyword"/>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a href="/finance/orders" v-if="userId > 0 || status.length > 0 || keyword.length > 0">[清除条件]</a>
</div>
</div>
</form>
<not-found-box v-if="orders.length == 0">暂时还没有订单。</not-found-box>
<table class="ui table selectable celled" v-if="orders.length > 0">
<thead>
<tr>
<th style="width: 12em">订单号</th>
<th>订单金额</th>
<th style="width: 12em">创建时间</th>
<th>类型</th>
<th>状态</th>
<th>支付方式</th>
<th>用户</th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="order in orders">
<td><a :href="'/finance/orders/order?orderCode=' + order.code"><keyword :v-word="keyword">{{order.code}}</keyword></a></td>
<td>¥{{order.amount}}</td>
<td>{{order.createdTime}}</td>
<td>
<span>{{order.typeName}}</span>
</td>
<td>
<span :class="{green: order.status == 'finished', disabled: order.status == 'cancelled'}">{{order.statusName}}</span>
</td>
<td>
<span v-if="order.method.id > 0">{{order.method.name}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<user-link :v-user="order.user" v-if="order.user.id > 0"></user-link>
<span v-if="order.user.id == 0" class="disabled">[已删除]</span>
</td>
<td>
<a :href="'/finance/orders/order?orderCode=' + order.code">详情</a>
</td>
</tr>
</table>
<page-box></page-box>

View File

@@ -0,0 +1,121 @@
{$layout "layout_popup"}
<h3>添加支付方式</h3>
<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" maxlength="30" ref="focus"/>
</td>
</tr>
<tr>
<td>类型</td>
<td>
<select name="parentCode" class="ui dropdown auto-width" v-model="parentCode">
<option value="">[自定义支付方式]</option>
<option v-for="presetMethod in presetMethods" :value="presetMethod.code">{{presetMethod.name}}</option>
</select>
</td>
</tr>
<tr>
<td>支付方式代号 *</td>
<td>
<input type="text" name="code" maxlength="60"/>
<p class="comment">只能包含数字、字母和下划线;自定义支付页面可以根据此代号判断订单来自哪种支付方式。</p>
</td>
</tr>
<tr>
<td>支付方式说明 *</td>
<td>
<textarea rows="3" name="description" maxlength="100"></textarea>
</td>
</tr>
<tr>
<td>支持终端</td>
<td>
<select class="ui dropdown auto-width" name="clientType" v-model="clientTypeCode">
<option v-for="clientType in clientTypes" :value="clientType.code">{{clientType.name}}</option>
</select>
<p class="comment" v-for="clientType in clientTypes" v-if="clientType.code == clientTypeCode">{{clientType.description}}</p>
</td>
</tr>
<tr v-show="clientTypeCode == 'mobile'">
<td>二维码扫描提示</td>
<td>
<input type="text" name="qrcodeTitle" maxlength="100"/>
<p class="comment">用来提示用户使用App扫描二维码类似于"请使用支付宝APP扫描下方二维码完成支付"。</p>
</td>
</tr>
<!-- 自定义支付方式 -->
<tbody v-if="parentCode == ''">
<tr>
<td>自定义支付URL *</td>
<td>
<input type="text" name="url" maxlength="200"/>
<p class="comment">用户选择此支付方式后系统会将订单信息放在此URL参数中。</p>
</td>
</tr>
</tbody>
<!-- 支付宝 -->
<tbody v-if="parentCode == 'alipay'">
<tr>
<td>APPID *</td>
<td>
<input type="text" name="paramsAlipayAppId" maxlength="20"/>
<p class="comment">签约的支付宝应用ID为一组数字。</p>
</td>
</tr>
<tr>
<td>私钥 *</td>
<td>
<textarea name="paramsAlipayPrivateKey" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>应用公钥证书 *</td>
<td>
<textarea name="paramsAlipayAppPublicCert" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>支付宝公钥证书 *</td>
<td>
<textarea name="paramsAlipayPublicCert" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>支付宝根证书 *</td>
<td>
<textarea name="paramsAlipayRootCert" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>产品代号 *</td>
<td>
<input type="text" name="paramsAlipayProductCode" maxlength="100" value="QUICK_WAP_WAY"/>
<p class="comment">和支付宝签约的产品代号。</p>
</td>
</tr>
<tr>
<td>沙箱应用</td>
<td>
<checkbox name="paramsAlipayIsSandbox"></checkbox>
<p class="comment">选中后,表示当前支付宝应用为沙箱应用,仅用来模拟测试。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,4 @@
Tea.context(function () {
this.parentCode = ""
this.clientTypeCode = this.clientTypes[0].code
})

View File

@@ -0,0 +1,42 @@
{$layout}
{$template "../menu"}
<second-menu>
<menu-item @click.prevent="createMethod">[添加支付方式]</menu-item>
</second-menu>
<p class="ui message warning" v-if="!enablePay">暂时未启用在线支付,去<a href="/finance/orders/setting">设置</a></p>
<div v-if="methods.length == 0">
<not-found-box>暂时还没有支付方式。</not-found-box>
</div>
<table class="ui table selectable celled" v-if="methods.length > 0">
<thead>
<tr>
<th class="three wide">支付方式</th>
<th class="three wide">代号</th>
<th>描述</th>
<th class="width6">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="method in methods">
<td>
<a :href="'/finance/orders/methods/method?methodId=' + method.id">{{method.name}}</a>
<div v-if="method.parentName.length > 0 || (method.clientType.length > 0 && method.clientType != 'all')">
<grey-label v-if="method.parentName.length > 0">{{method.parentName}}</grey-label>
<grey-label v-if="method.clientType.length > 0 && method.clientType != 'all'">{{method.clientTypeName}}</grey-label>
</div>
</td>
<td>{{method.code}}</td>
<td>{{method.description}}</td>
<td>
<label-on :v-is-on="method.isOn"></label-on>
</td>
<td>
<a :href="'/finance/orders/methods/method?methodId=' + method.id">详情</a> &nbsp;
<a href="" @click.prevent="deleteMethod(method.id)">删除</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,24 @@
Tea.context(function () {
this.createMethod = function () {
teaweb.popup("/finance/orders/methods/createPopup", {
height: "27em",
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.deleteMethod = function (methodId) {
let that = this
teaweb.confirm("确定要删除此支付方式吗?", function () {
that.$post("/finance/orders/methods/method/delete")
.params({
methodId: methodId
})
.success(function () {
teaweb.successRefresh("删除成功")
})
})
}
})

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item href="/finance/orders/methods">支付方式</menu-item>
<span class="item disabled" style="padding: 0">&raquo;</span>
<menu-item :href="'/finance/orders/methods/method?methodId=' + method.id" code="index">"{{method.name}}"详情</menu-item>
<menu-item :href="'/finance/orders/methods/method/update?methodId=' + method.id" code="update">修改</menu-item>
</first-menu>

View File

@@ -0,0 +1,4 @@
.scroll-box {
max-height: 10em;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,gBAAA","file":"index.css"}

View File

@@ -0,0 +1,121 @@
{$layout}
{$template "menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">支付方式名称</td>
<td>
{{method.name}}
</td>
</tr>
<tr>
<td>状态</td>
<td>
<label-on :v-is-on="method.isOn"></label-on>
</td>
</tr>
<tr>
<td>支付方式代号</td>
<td>
{{method.code}}
</td>
</tr>
<tr>
<td>支付方式说明</td>
<td>
{{method.description}}
</td>
</tr>
<tr>
<td>支持终端</td>
<td>
{{method.clientTypeName}}
<p class="comment">{{method.clientTypeDescription}}</p>
</td>
</tr>
<tr>
<td>密钥</td>
<td>{{method.secret}}</td>
</tr>
<tr>
<td>类型</td>
<td>
<span v-if="method.parentCode.length == 0">自定义</span>
<span v-else>{{method.parentName}}</span>
</td>
</tr>
<tr v-show="method.clientType == 'mobile'">
<td>二维码扫描提示</td>
<td>
<div v-if="method.qrcodeTitle.length > 0">
{{method.qrcodeTitle}}
<p class="comment">用来提示用户使用App扫描二维码类似于"请使用支付宝APP扫描下方二维码完成支付"。</p>
</div>
<div v-else><span class="title disabled">没有设置</span></div>
</td>
</tr>
<!-- 自定义支付 -->
<tr v-show="method.parentCode.length == 0">
<td class="color-border">自定义支付URL</td>
<td>
{{method.url}}
<p class="comment">用户点击此支付方式后系统将会跳转到这个支付URL并传递<code-label class="grey">{{urlArgs}}</code-label></p>
</td>
</tr>
<!-- 支付宝 -->
<tbody v-if="method.parentCode == 'alipay'">
<tr>
<td class="color-border">APPID</td>
<td>
{{method.params.appId}}
</td>
</tr>
<tr>
<td class="color-border">私钥</td>
<td>
<pre class="scroll-box">{{method.params.privateKey}}</pre>
</td>
</tr>
<tr>
<td class="color-border">应用公钥证书</td>
<td>
<pre class="scroll-box">{{method.params.appPublicCert}}</pre>
</td>
</tr>
<tr>
<td class="color-border">支付宝公钥证书</td>
<td>
<pre class="scroll-box">{{method.params.alipayPublicCert}}</pre>
</td>
</tr>
<tr>
<td class="color-border">支付宝根证书</td>
<td>
<pre class="scroll-box">{{method.params.alipayRootCert}}</pre>
</td>
</tr>
<tr>
<td class="color-border">产品代号</td>
<td>
{{method.params.productCode}}
</td>
</tr>
<tr>
<td class="color-border">沙箱应用</td>
<td>
<span v-if="method.params.isSandbox" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
</tbody>
<tr v-if="method.payNotifyURL.length > 0">
<td class="color-border">支付结果通知URL</td>
<td>{{method.payNotifyURL}}
<p class="comment">前缀部分默认使用用户节点的第一个访问地址,<a href="/settings/userNodes">修改 &raquo;</a> </p>
</td>
</tr>
</table>

View File

@@ -0,0 +1,7 @@
Tea.context(function () {
this.urlArgs = ""
if (this.method.url.indexOf("?") < 0) {
this.urlArgs += "?"
}
this.urlArgs += "EdgeOrderMethod=" + this.method.code + "&EdgeOrderCode=$订单号&EdgeOrderAmount=$支付金额&EdgeOrderTimestamp=$时间戳&EdgeOrderSign=$参数签名"
})

View File

@@ -0,0 +1,3 @@
.scroll-box {
max-height: 10em;
}

View File

@@ -0,0 +1,130 @@
{$layout}
{$template "menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="methodId" :value="method.id"/>
<table class="ui table selectable definition">
<tr>
<td class="title">支付方式名称 *</td>
<td>
<input type="text" name="name" maxlength="30" ref="focus" v-model="method.name"/>
</td>
</tr>
<tr>
<td>类型</td>
<td>
<span v-if="method.parentName.length == 0">自定义</span>
<span v-else>{{method.parentName}}</span>
</td>
</tr>
<tr>
<td>支付方式代号 *</td>
<td>
<input type="text" name="code" maxlength="60" v-model="method.code"/>
<p class="comment">只能包含数字、字母和下划线;自定义支付页面可以根据此代号判断订单来自哪种支付方式。</p>
</td>
</tr>
<tr>
<td>支付方式说明 *</td>
<td>
<textarea rows="3" name="description" maxlength="100" v-model="method.description"></textarea>
</td>
</tr>
<tr>
<td>支持终端</td>
<td>
<select class="ui dropdown auto-width" name="clientType" v-model="method.clientType">
<option v-for="clientType in clientTypes" :value="clientType.code">{{clientType.name}}</option>
</select>
<p class="comment" v-for="clientType in clientTypes" v-if="clientType.code == method.clientType">{{clientType.description}}</p>
</td>
</tr>
<tr v-show="method.clientType == 'mobile'">
<td>二维码扫描提示</td>
<td>
<input type="text" name="qrcodeTitle" maxlength="100" v-model="method.qrcodeTitle"/>
<p class="comment">用来提示用户使用App扫描二维码类似于"请使用支付宝APP扫描下方二维码完成支付"。</p>
</td>
</tr>
<!-- 自定义 -->
<tbody v-if="method.parentCode == ''">
<tr>
<td>自定义支付URL *</td>
<td>
<input type="text" name="url" maxlength="200" v-model="method.url"/>
<p class="comment">用户选择此支付方式后系统会将订单信息放在此URL参数中。</p>
</td>
</tr>
</tbody>
<!-- 支付宝 -->
<tbody v-if="method.parentCode == 'alipay'">
<tr>
<td>APPID *</td>
<td>
<input type="text" name="paramsAlipayAppId" maxlength="20" v-model="method.params.appId"/>
<p class="comment">签约的支付宝应用ID为一组数字。</p>
</td>
</tr>
<tr>
<td>私钥 *</td>
<td>
<textarea name="paramsAlipayPrivateKey" v-model="method.params.privateKey" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>应用公钥证书 *</td>
<td>
<textarea name="paramsAlipayAppPublicCert" v-model="method.params.appPublicCert" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>支付宝公钥证书 *</td>
<td>
<textarea name="paramsAlipayPublicCert" v-model="method.params.alipayPublicCert" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>支付宝根证书 *</td>
<td>
<textarea name="paramsAlipayRootCert" v-model="method.params.alipayRootCert" spellcheck="false"></textarea>
<p class="comment">如果你获得的是证书文件,你需要将文件内容拷贝到这里。</p>
</td>
</tr>
<tr>
<td>产品代号 *</td>
<td>
<input type="text" name="paramsAlipayProductCode" v-model="method.params.productCode" maxlength="100" value=""/>
<p class="comment">和支付宝签约的产品代号。</p>
</td>
</tr>
<tr>
<td>沙箱应用</td>
<td>
<checkbox name="paramsAlipayIsSandbox" v-model="method.params.isSandbox"></checkbox>
<p class="comment">选中后,表示当前支付宝应用为沙箱应用,仅用来模拟测试。</p>
</td>
</tr>
</tbody>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr >
<td>启用</td>
<td>
<checkbox name="isOn" v-model="method.isOn"></checkbox>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,8 @@
Tea.context(function () {
this.success = function () {
let that = this
teaweb.success("保存成功", function () {
window.location = "/finance/orders/methods/method?methodId=" + that.method.id
})
}
})

View File

@@ -0,0 +1,60 @@
{$layout}
{$template "../menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">订单号</td>
<td>{{order.code}}</td>
</tr>
<tr>
<td>订单金额</td>
<td>¥{{order.amount}}</td>
</tr>
<tr>
<td>创建时间</td>
<td>{{order.createdTime}}</td>
</tr>
<tr>
<td>类型</td>
<td>
{{order.typeName}}
</td>
</tr>
<tr>
<td>状态</td>
<td>
<span v-if="order.status == 'none' && order.isExpired" class="red">已过期</span>
<span v-else>
<span :class="{green: order.status == 'finished', disabled: order.status == 'cancelled'}">{{order.statusName}}</span>
</span>
</td>
</tr>
<tr v-if="order.status == 'finished'">
<td>支付时间</td>
<td>{{order.finishedTime}}</td>
</tr>
<tr v-if="order.status == 'cancelled'">
<td>取消时间</td>
<td>{{order.cancelledTime}}</td>
</tr>
<tr>
<td>支付方式</td>
<td>
<span v-if="order.method.id > 0">{{order.method.name}}</span>
<span v-else class="disabled">-</span>
</td>
</tr>
<tr>
<td>用户</td>
<td>
<user-link :v-user="order.user" v-if="order.user.id > 0"></user-link>
<span v-if="order.user.id == 0" class="disabled">[已删除]</span>
</td>
</tr>
<tr v-if="order.status == 'none'">
<td>操作</td>
<td>
<a href="" @click.prevent="finishOrder">[完成支付]</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,16 @@
Tea.context(function () {
this.finishOrder = function () {
let that = this
teaweb.confirm("html:确定要将此订单设置为完成状态吗?<br/>订单完成后对应的账户余额和服务状态将会发生变化。", function () {
that.$post("/finance/orders/order/finish")
.params({
orderCode: that.order.code
})
.success(function () {
teaweb.success("操作成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,4 @@
textarea.disabled {
color: #ddd !important;
}
/*# sourceMappingURL=setting.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["setting.less"],"names":[],"mappings":"AAAA,QAAQ;EACP,WAAA","file":"setting.css"}

View File

@@ -0,0 +1,32 @@
{$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>
<checkbox name="enablePay" v-model="config.enablePay"></checkbox>
<p class="comment">启用后,用户在用户系统中在线创建和支付订单。此功能需要你已经添加了支付方式。<span v-if="!hasValidMethods" class="red">暂时没有添加可用的支付方式,此功能将不可用。</span></p>
</td>
</tr>
<tr>
<td>不启用支付时的提示</td>
<td>
<textarea name="disablePageHTML" v-model="config.disablePageHTML" :class="{disabled: config.enablePay}" rows="3"></textarea>
<p class="comment">在没有启用在线支付或者没有可用的在线支付方式时的提示文字。支持HTML。</p>
</td>
</tr>
<tr>
<td>订单过期时间</td>
<td>
<time-duration-box :v-name="'orderLifeJSON'" :v-value="config.orderLife"></time-duration-box>
<p class="comment">超过这个时间订单不能再支付。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,3 @@
textarea.disabled {
color: #ddd!important;
}

View File

@@ -0,0 +1,8 @@
<first-menu>
<menu-item href="/finance/packages" code="index">流量包</menu-item>
<menu-item href="/finance/packages/periods" code="period">有效期选项</menu-item>
<span class="item disabled">|</span>
<menu-item href="/finance/packages/user-packages" code="userPackage">用户流量包</menu-item>
<span class="item disabled">|</span>
<a href="/clusters/regions" target="_blank" class="item">区域设置 &nbsp; <i class="icon external small"></i></a>
</first-menu>

View File

@@ -0,0 +1,32 @@
{$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>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="size" size="4" maxlength="4" ref="focus"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="unit">
<option v-for="unit in unitOptions" :value="unit.code">{{unit.name}}</option>
</select>
</div>
</div>
<p class="comment">保存后不允许再修改此选项。</p>
</td>
</tr>
<tr>
<td>价格</td>
<td><span class="grey">在创建后设置</span></td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,45 @@
{$layout}
{$template "menu"}
<second-menu>
<menu-item @click.prevent="createPackage">[添加流量包]</menu-item>
</second-menu>
<div v-if="!enableTrafficPackages">
<div class="margin"></div>
<div class="ui message warning">尚未在 <a href="/finance/fee">[计费设置]</a> 里开启"启用流量包"选项,用户将无法使用设置的流量包。</div>
</div>
<p class="comment" v-if="packages.length == 0">暂时还没有流量包。</p>
<div v-if="packages.length > 0">
<table class="ui table selectable celled">
<thead>
<tr>
<th>流量包尺寸</th>
<th class="width10">价格项</th>
<th class="width6">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="p in packages">
<td>
<a href="" @click.prevent="updatePackage(p.id)">{{p.size}}{{p.unit.replace(/(.)B/, "$1iB")}} <i class="icon expand small"></i></a>
</td>
<td>
<a href="" @click.prevent="updatePrices(p.id)">
<span class="" v-if="p.countPrices > 0">{{p.countPrices}}<span class="grey">/{{totalPriceItems}}</span></span>
<span class="" v-else>[设置价格]</span>
</a>
</td>
<td>
<label-on :v-is-on="p.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="updatePackage(p.id)">修改</a>
&nbsp;
<a href="" @click.prevent="deletePackage(p.id)">删除</a>
</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,44 @@
Tea.context(function () {
this.createPackage = function () {
teaweb.popup("/finance/packages/createPopup", {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updatePackage = function (packageId) {
teaweb.popup("/finance/packages/updatePopup?packageId=" + packageId, {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deletePackage = function (packageId) {
let that = this
teaweb.confirm("确定要删除此流量包吗?", function () {
that.$post("/finance/packages/delete")
.params({packageId: packageId})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
this.updatePrices = function (packageId) {
teaweb.popup("/finance/packages/updatePricesPopup?packageId=" + packageId, {
width: "54em",
height: "30em",
onClose: function () {
teaweb.reload()
}
})
}
})

View File

@@ -0,0 +1,26 @@
{$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>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="count" size="4" maxlength="4" ref="focus" placeholder="数量"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="unit">
<option value="month"></option>
<option value="year"></option>
</select>
</div>
</div>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,35 @@
{$layout}
{$template "../menu"}
<second-menu>
<menu-item @click.prevent="createPeriod">[添加有效期选项]</menu-item>
</second-menu>
<div v-if="!enableTrafficPackages">
<div class="margin"></div>
<div class="ui message warning">尚未在 <a href="/finance/fee">[计费设置]</a> 里开启"启用流量包"选项,用户将无法使用设置的流量包。</div>
</div>
<p class="comment" v-if="periods.length == 0">暂时还没有有效期选项设置。</p>
<table class="ui table celled selectable" v-if="periods.length > 0">
<thead>
<tr>
<th>有效期选项</th>
<th class="width6">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="period in periods">
<td>
<a href="" @click.prevent="updatePeriod(period.id)">{{period.count}}{{period.unitName}} <i class="icon expand small"></i></a>
</td>
<td>
<label-on :v-is-on="period.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="updatePeriod(period.id)">修改</a> &nbsp;
<a href="" @click.prevent="deletePeriod(period.id)">删除</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,32 @@
Tea.context(function () {
this.createPeriod = function () {
teaweb.popup("/finance/packages/periods/createPopup", {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.updatePeriod = function (periodId) {
teaweb.popup("/finance/packages/periods/period/updatePopup?periodId=" + periodId, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.deletePeriod = function (periodId) {
let that = this
teaweb.confirm("确定要删除此有效期选项吗?", function () {
that.$post("/finance/packages/periods/period/delete")
.params({
periodId: periodId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,23 @@
{$layout "layout_popup"}
<h3>修改有效期选项</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="periodId" :value="period.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">有效期选项</td>
<td>{{period.count}}{{period.unitName}}</td>
</tr>
<tr>
<td>启用</td>
<td>
<checkbox name="isOn" v-model="period.isOn"></checkbox>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,26 @@
{$layout "layout_popup"}
<h3>修改流量包</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="packageId" :value="package.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">流量包尺寸</td>
<td>
{{package.size}}{{package.unit}}
</td>
</tr>
<tr>
<td>启用</td>
<td>
<checkbox name="isOn" v-model="package.isOn"></checkbox>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,51 @@
{$layout "layout_popup"}
<h3>流量包"{{package.size}}{{package.unit}}"价格 <span class="grey" style="font-size: 0.7em">(全部{{regions.length * periods.length}}项)</span></h3>
<table class="ui table celled selectable definition">
<!-- header -->
<thead class="full-width">
<tr>
<th class="two wide">
区域\有效期
</th>
<th v-for="period in periods" style="width: 10em">
{{period.name}}
</th>
<th>
<a href="/finance/packages/periods" target="_blank" style="font-weight: normal">[添加有效期]</a>
</th>
</tr>
</thead>
<tr v-for="region in regions">
<td>{{region.name}}</td>
<td v-for="period in periods">
<span v-if="prices[region.id + '@' + period.id] > 0">{{prices[region.id + '@' + period.id]}}元</span>
<span v-else class="disabled">[无]</span>
<div>
<a href="" @click.prevent="editPrice(region.id, period.id)"><span class="small">[设置]</span></a>
<div v-show="editingRegionId == region.id && editingPeriodId == period.id">
<div class="ui input small right labeled">
<input type="text" size="6" maxlength="6" :ref="'input' + region.id + '_' + period.id" placeholder="价格" @keyup.enter="savePrice(region.id, period.id)" @keypress.enter.prevent="1"/>
<span class="ui label"></span>
</div>
<div style="margin-top: 0.6em">
<button class="ui button tiny primary" type="button" @click.prevent="savePrice(region.id, period.id)">保存</button> &nbsp; <a href="" title="取消" @click.prevent="cancelEditing"><i class="icon remove small"></i></a>
</div>
</div>
</div>
</td>
<td></td>
</tr>
<!-- footer -->
<tr>
<td>
<a href="/clusters/regions" target="_blank" style="font-weight: normal">[添加区域]</a>
</td>
<td v-for="period in periods">
</td>
<td></td>
</tr>
</table>

View File

@@ -0,0 +1,72 @@
Tea.context(function () {
this.editingRegionId = 0
this.editingPeriodId = 0
this.editPrice = function (regionId, periodId) {
this.editingRegionId = regionId
this.editingPeriodId = periodId
let refs = this.$refs
if (typeof refs == "object") {
for (let k in refs) {
if (typeof k == "string" && k == "input" + regionId + "_" + periodId) {
let inputs = refs[k]
if (inputs.length > 0) {
setTimeout(function () {
inputs[0].focus()
}, 10)
}
break
}
}
}
}
this.cancelEditing = function () {
this.editingRegionId = 0
this.editingPeriodId = 0
}
this.savePrice = function (regionId, periodId) {
let refs = this.$refs
let price = -1
if (typeof refs == "object") {
for (let k in refs) {
if (typeof k == "string" && k == "input" + regionId + "_" + periodId) {
let inputs = refs[k]
if (inputs.length > 0) {
let input = inputs[0]
let newPrice = parseFloat(input.value)
if (isNaN(newPrice) || newPrice < 0) {
teaweb.warn("请输入一个正确的数字", function () {
input.focus()
})
return
}
price = newPrice
}
break
}
}
}
if (price < 0) {
teaweb.warn("请输入一个正确的数字", function () {
})
return
}
this.$post("/finance/packages/updatePrice")
.params({
packageId: this.package.id,
regionId: regionId,
periodId: periodId,
price: price
})
.success(function () {
this.editingRegionId = 0
this.editingPeriodId = 0
this.prices[regionId + "@" + periodId] = price
})
}
})

View File

@@ -0,0 +1,60 @@
{$layout "layout_popup"}
<h3>添加用户流量包</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="packageId" :value="packageId"/>
<input type="hidden" name="regionId" :value="regionId"/>
<input type="hidden" name="periodId" :value="periodId"/>
<table class="ui table definition selectable">
<tr>
<td>选择用户 *</td>
<td>
<user-selector></user-selector>
</td>
</tr>
<tr>
<td class="title">选择流量包规格 *</td>
<td>
<span v-if="packages.length > 0"><a v-for="p in packages" class="ui label basic" :class="{blue: p.id == packageId}" @click.prevent="selectPackage(p.id)">{{p.size}}{{p.unit.toUpperCase().replace(/(.)B/, "$1iB")}}</a></span>
<span v-else="" class="red">没有流量包可供选择</span>
</td>
</tr>
<tr>
<td>选择区域 *</td>
<td>
<span v-if="regions.length > 0"><a v-for="region in regions" class="ui label basic" :class="{blue: region.id == regionId && hasRegionPrice(region.id), disabled: !hasRegionPrice(region.id)}" @click.prevent="selectRegion(region.id)">{{region.name}}</a></span>
<span v-else="" class="red">没有区域可供选择</span>
</td>
</tr>
<tr>
<td>选择有效期 *</td>
<td>
<span v-if="periods.length > 0"><a v-for="period in periods" class="ui label basic" :class="{blue: period.id == periodId && hasPeriodPrice(period.id), disabled: !hasPeriodPrice(period.id)}" @click.prevent="selectPeriod(period.id)">{{period.count}}{{period.unitName}}</a></span>
<span v-else="" class="red">没有有效期可供选择</span>
</td>
</tr>
<tr>
<td>流量包数量 *</td>
<td>
<div class="ui input">
<select class="ui dropdown" name="count" v-model="count" @change="changeCount">
<option v-for="i in 20" :value="i">{{i}}</option>
</select>
</div>
</td>
</tr>
<tr>
<td>价格</td>
<td>
<span v-if="amount == 0" class="disabled">没有找到对应价格</span>
<span v-if="amount > 0">{{amount}}元</span>
<p class="comment">管理员操作时,此价格仅供展示,并不会从用户账户中扣款。</p>
</td>
</tr>
</table>
<submit-btn :class="{disabled: amount <= 0}"></submit-btn>
</form>

View File

@@ -0,0 +1,70 @@
Tea.context(function () {
this.packageId = 0
this.regionId = 0
this.periodId = 0
this.amount = -1
this.count = 1
this.selectPackage = function (packageId) {
this.packageId = packageId
this.regionId = 0
this.periodId = 0
this.reloadPrice()
}
this.selectRegion = function (regionId) {
this.regionId = regionId
this.periodId = 0
this.reloadPrice()
}
this.selectPeriod = function (periodId) {
this.periodId = periodId
this.reloadPrice()
}
this.changeCount = function () {
this.reloadPrice()
}
var requestId = 0
this.reloadPrice = function () {
var newRequestId = (++requestId)
this.$post(".price")
.params({
packageId: this.packageId,
regionId: this.regionId,
periodId: this.periodId,
count: this.count
})
.success(function (resp) {
if (newRequestId == requestId) {
this.amount = resp.data.amount
}
})
}
this.hasRegionPrice = function (regionId) {
if (this.packageId <= 0) {
return false
}
for (let k in this.prices) {
if (k.startsWith(this.packageId + "@" + regionId + "@")) {
return true
}
}
return false
}
this.hasPeriodPrice = function (periodId) {
if (this.packageId <= 0 || this.regionId <= 0) {
return false
}
for (let k in this.prices) {
if (k == this.packageId + "@" + this.regionId + "@" + periodId) {
return true
}
}
return false
}
})

View File

@@ -0,0 +1,53 @@
{$layout}
{$template "../menu"}
<second-menu>
<menu-item @click.prevent="createUserPackage">[添加用户流量包]</menu-item>
</second-menu>
<p class="comment" v-if="userPackages.length == 0">暂时还没有用户流量包。</p>
<div v-if="userPackages.length > 0">
<table class="ui table selectable celled">
<thead>
<tr>
<th>用户</th>
<th>流量包</th>
<th style="width: 7em">区域</th>
<th style="width: 7em">有效期</th>
<th style="width: 7em">开始日期</th>
<th style="width: 7em">结束日期</th>
<th style="width: 7em">已用流量</th>
<th style="width: 7em">剩余流量</th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="up in userPackages">
<td><user-link :v-user="up.user"></user-link></td>
<td>
<span v-if="up.package != null">{{up.package.size}}{{up.package.unit.toUpperCase().replace(/(.)B/, "$1iB")}}</span>
<span v-else class="disabled">[已删除]</span>
<div v-if="up.isUsedAll || up.isExpired">
<span v-if="up.isUsedAll" class="small red">已用尽</span>
<span v-else-if="up.isExpired" class="small red">已过期</span>
</div>
</td>
<td>
<span v-if="up.region != null">{{up.region.name}}</span>
<span v-else="" class="disabled">[已删除]</span>
</td>
<td>{{up.periodCount}}{{up.periodUnitName}}</td>
<td>{{up.dayFrom}}</td>
<td>{{up.dayTo}}</td>
<td><span :class="{disabled: up.usedSize == '0B'}">{{up.usedSize}}</span></td>
<td><span :class="{disabled: up.availableSize == '0B'}">{{up.availableSize}}</span></td>
<td>
<a href="" v-if="up.canDelete" @click.prevent="deleteUserPackage(up.id)">删除</a>
</td>
</tr>
</table>
<page-box></page-box>
</div>

View File

@@ -0,0 +1,24 @@
Tea.context(function () {
this.createUserPackage = function () {
teaweb.popup("/finance/packages/user-packages/createPopup", {
width: "44em",
height: "28em",
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.deleteUserPackage = function (userPackageId) {
let that = this
teaweb.confirm("确定要删除此用户流量包吗?", function () {
that.$post(".delete")
.params({
userPackageId: userPackageId
})
.success(function () {
teaweb.successRefresh("删除成功")
})
})
}
})