增加套餐相关代码

This commit is contained in:
GoEdgeLab
2021-10-29 14:02:01 +08:00
parent 60653667aa
commit bdb8e98af6
12 changed files with 324 additions and 10 deletions

View File

@@ -19,6 +19,7 @@ const (
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
AdminModuleCodePlan AdminModuleCode = "plan" // 套餐
AdminModuleCodeLog AdminModuleCode = "log" // 日志
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
@@ -203,6 +204,11 @@ func AllModuleMaps() []maps.Map {
"code": AdminModuleCodeFinance,
"url": "/finance",
},
{
"name": "套餐管理",
"code": AdminModuleCodePlan,
"url": "/plans",
},
{
"name": "日志审计",
"code": AdminModuleCodeLog,

View File

@@ -464,6 +464,14 @@ func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceC
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
}
func (this *RPCClient) PlanRPC() pb.PlanServiceClient {
return pb.NewPlanServiceClient(this.pickConn())
}
func (this *RPCClient) UserPlanRPC() pb.UserPlanServiceClient {
return pb.NewUserPlanServiceClient(this.pickConn())
}
// Context 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()

View File

@@ -19,6 +19,8 @@ type ParentAction struct {
actions.ActionObject
rpcClient *rpc.RPCClient
ctx context.Context
}
// Parent 可以调用自身的一个简便方法
@@ -117,6 +119,10 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
// AdminContext 获取Context
func (this *ParentAction) AdminContext() context.Context {
if this.ctx != nil {
return this.ctx
}
if this.rpcClient == nil {
rpcClient, err := rpc.SharedRPC()
if err != nil {
@@ -125,7 +131,8 @@ func (this *ParentAction) AdminContext() context.Context {
}
this.rpcClient = rpcClient
}
return this.rpcClient.Context(this.AdminId())
this.ctx = this.rpcClient.Context(this.AdminId())
return this.ctx
}
// ViewData 视图里可以使用的数据

View File

@@ -374,6 +374,21 @@ func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64
"icon": "yen sign",
"isOn": teaconst.IsPlus,
},
/**{
"code": "plans",
"module": configloaders.AdminModuleCodePlan,
"name": "套餐管理",
"icon": "puzzle piece",
"isOn": teaconst.IsPlus,
"subItems": []maps.Map{
{
"name": "已购套餐",
"url": "/plans/userPlans",
"code": "userPlans",
"isOn": teaconst.IsPlus,
},
},
},**/
{
"code": "admins",
"module": configloaders.AdminModuleCodeAdmin,

View File

@@ -0,0 +1,34 @@
Vue.component("datepicker", {
props: ["v-name", "v-value", "v-bottom-left"],
mounted: function () {
let that = this
teaweb.datepicker(this.$refs.dayInput, function (v) {
that.day = v
that.change()
}, !!this.vBottomLeft)
},
data: function () {
let name = this.vName
if (name == null) {
name = "day"
}
let day = this.vValue
if (day == null) {
day = ""
}
return {
name: name,
day: day
}
},
methods: {
change: function () {
this.$emit("change", this.day)
}
},
template: `<div style="display: inline-block">
<input type="text" :name="name" v-model="day" placeholder="YYYY-MM-DD" style="width:8.6em" maxlength="10" @input="change" ref="dayInput" autocomplete="off"/>
</div>`
})

View File

@@ -0,0 +1,166 @@
// 套餐价格配置
Vue.component("plan-price-config-box", {
props: ["v-price-type", "v-monthly-price", "v-seasonally-price", "v-yearly-price", "v-bandwidth-price"],
data: function () {
let priceType = this.vPriceType
if (priceType == null) {
priceType = "period"
}
let monthlyPriceNumber = 0
let monthlyPrice = this.vMonthlyPrice
if (monthlyPrice == null || monthlyPrice <= 0) {
monthlyPrice = ""
} else {
monthlyPrice = monthlyPrice.toString()
monthlyPriceNumber = parseFloat(monthlyPrice)
if (isNaN(monthlyPriceNumber)) {
monthlyPriceNumber = 0
}
}
let seasonallyPriceNumber = 0
let seasonallyPrice = this.vSeasonallyPrice
if (seasonallyPrice == null || seasonallyPrice <= 0) {
seasonallyPrice = ""
} else {
seasonallyPrice = seasonallyPrice.toString()
seasonallyPriceNumber = parseFloat(seasonallyPrice)
if (isNaN(seasonallyPriceNumber)) {
seasonallyPriceNumber = 0
}
}
let yearlyPriceNumber = 0
let yearlyPrice = this.vYearlyPrice
if (yearlyPrice == null || yearlyPrice <= 0) {
yearlyPrice = ""
} else {
yearlyPrice = yearlyPrice.toString()
yearlyPriceNumber = parseFloat(yearlyPrice)
if (isNaN(yearlyPriceNumber)) {
yearlyPriceNumber = 0
}
}
let bandwidthPrice = this.vBandwidthPrice
let bandwidthPriceBaseNumber = 0
if (bandwidthPrice != null) {
bandwidthPriceBaseNumber = bandwidthPrice.base
} else {
bandwidthPrice = {
base: 0
}
}
let bandwidthPriceBase = ""
if (bandwidthPriceBaseNumber > 0) {
bandwidthPriceBase = bandwidthPriceBaseNumber.toString()
}
return {
priceType: priceType,
monthlyPrice: monthlyPrice,
seasonallyPrice: seasonallyPrice,
yearlyPrice: yearlyPrice,
monthlyPriceNumber: monthlyPriceNumber,
seasonallyPriceNumber: seasonallyPriceNumber,
yearlyPriceNumber: yearlyPriceNumber,
bandwidthPriceBase: bandwidthPriceBase,
bandwidthPrice: bandwidthPrice
}
},
watch: {
monthlyPrice: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.monthlyPriceNumber = price
},
seasonallyPrice: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.seasonallyPriceNumber = price
},
yearlyPrice: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.yearlyPriceNumber = price
},
bandwidthPriceBase: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.bandwidthPrice.base = price
}
},
template: `<div>
<input type="hidden" name="priceType" :value="priceType"/>
<input type="hidden" name="monthlyPrice" :value="monthlyPriceNumber"/>
<input type="hidden" name="seasonallyPrice" :value="seasonallyPriceNumber"/>
<input type="hidden" name="yearlyPrice" :value="yearlyPriceNumber"/>
<input type="hidden" name="bandwidthPriceJSON" :value="JSON.stringify(bandwidthPrice)"/>
<div>
<radio :v-value="'period'" :value="priceType" v-model="priceType">&nbsp;按时间周期</radio> &nbsp; &nbsp;
<radio :v-value="'bandwidth'" :value="priceType" v-model="priceType">&nbsp;按带宽用量</radio>
</div>
<!-- 按时间周期 -->
<div v-show="priceType == 'period'">
<div class="ui divider"></div>
<table class="ui table">
<tr>
<td class="title">月度价格</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 7em" maxlength="10" v-model="monthlyPrice"/>
<span class="ui label">元</span>
</div>
</td>
</tr>
<tr>
<td class="title">季度价格</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 7em" maxlength="10" v-model="seasonallyPrice"/>
<span class="ui label">元</span>
</div>
</td>
</tr>
<tr>
<td class="title">年度价格</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 7em" maxlength="10" v-model="yearlyPrice"/>
<span class="ui label">元</span>
</div>
</td>
</tr>
</table>
</div>
<!-- 按带宽 -->
<div v-show="priceType =='bandwidth'">
<div class="ui divider"></div>
<table class="ui table">
<tr>
<td class="title">基础带宽费用</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="bandwidthPriceBase" maxlength="10" style="width: 7em"/>
<span class="ui label">元/GB</span>
</div>
</td>
</tr>
</table>
</div>
</div>`
})

View File

@@ -0,0 +1,18 @@
Vue.component("plan-price-view", {
props: ["v-plan"],
data: function () {
return {
plan: this.vPlan
}
},
template: `<div>
<span v-if="plan.priceType == 'period'">
<span v-if="plan.monthlyPrice > 0">月度:¥{{plan.monthlyPrice}}元<br/></span>
<span v-if="plan.seasonallyPrice > 0">季度:¥{{plan.seasonallyPrice}}元<br/></span>
<span v-if="plan.yearlyPrice > 0">年度:¥{{plan.yearlyPrice}}元</span>
</span>
<span v-if="plan.priceType == 'bandwidth'">
基础价格:¥{{plan.bandwidthPrice.base}}元/GB
</span>
</div>`
})

View File

@@ -0,0 +1,28 @@
Vue.component("plan-user-selector", {
mounted: function () {
let that = this
Tea.action("/plans/users/options")
.post()
.success(function (resp) {
that.users = resp.data.users
})
},
props: ["v-user-id"],
data: function () {
let userId = this.vUserId
if (userId == null) {
userId = 0
}
return {
users: [],
userId: userId
}
},
template: `<div>
<select class="ui dropdown auto-width" name="userId" v-model="userId">
<option value="0">[选择用户]</option>
<option v-for="user in users" :value="user.id">{{user.fullname}} ({{user.username}})</option>
</select>
</div>`
})

View File

@@ -0,0 +1,16 @@
// 显示带宽限制说明
Vue.component("bandwidth-limit-view", {
props: ["v-bandwidth-limit"],
data: function () {
return {
config: this.vBandwidthLimit
}
},
template: `<div>
<div v-if="config.isOn">
<span v-if="config.dailySize != null && config.dailySize.count > 0">日带宽限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}<br/></span>
<span v-if="config.monthlySize != null && config.monthlySize.count > 0">月带宽限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}<br/></span>
</div>
<span v-else class="disabled">没有限制。</span>
</div>`
})

View File

@@ -69,13 +69,13 @@ window.teaweb = {
}
document.head.append(element)
},
datepicker: function (element, callback) {
datepicker: function (element, callback, bottomLeft) {
// 加载
if (typeof Pikaday == "undefined") {
let that = this
this.loadJS("/js/moment.min.js", function () {
that.loadJS("/js/pikaday.js", function () {
that.datepicker(element, callback)
that.datepicker(element, callback, bottomLeft)
})
})
this.loadCSS("/js/pikaday.css")
@@ -89,8 +89,8 @@ window.teaweb = {
if (typeof (element) == "string") {
element = document.getElementById(element);
}
var year = new Date().getFullYear();
var picker = new Pikaday({
let year = new Date().getFullYear();
let picker = new Pikaday({
field: element,
firstDay: 1,
minDate: new Date(year - 1, 0, 1),
@@ -109,8 +109,9 @@ window.teaweb = {
if (typeof (callback) == "function") {
callback.call(Tea.Vue, picker.toString());
}
}
});
},
reposition: !bottomLeft
})
},
formatBytes: function (bytes) {
@@ -256,6 +257,21 @@ window.teaweb = {
}
});
},
popupSuccess: function (url, width, height) {
let options = {}
if (width != null) {
options["width"] = width
}
if (height != null) {
options["height"] = height
}
options["callback"] = function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
this.popup(url, options)
},
popupFinish: function () {
if (window.POPUP_CALLBACK != null) {
window.POPUP_CALLBACK.apply(window, arguments);

View File

@@ -9,7 +9,7 @@
<menu-item :href="'/servers/certs/acme?type=available&keyword=' + keyword" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=expired&keyword=' + keyword" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=7days&keyword=' + keyword" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=30days&keyword=' + keyword" :active="type == '30days'">30天过期({{count30Days}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=30days&keyword=' + keyword" :active="type == '30days'">30天过期({{count30Days}})</menu-item>
<div class="item right">
<form class="ui form">
<div class="ui fields inline">

View File

@@ -8,8 +8,8 @@
<menu-item :href="'/servers/certs?type=available&keyword=' + keyword" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs?type=expired&keyword=' + keyword" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs?type=7days&keyword=' + keyword" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs?type=30days&keyword=' + keyword" :active="type == '30days'">30天过期({{count30Days}})</menu-item>
<span class="item">|</span>
<menu-item :href="'/servers/certs?type=30days&keyword=' + keyword" :active="type == '30days'">30天过期({{count30Days}})</menu-item>
<span class="item disabled">|</span>
<a href="" class="item" @click.prevent="uploadCert">[上传证书]</a>
</second-menu>