diff --git a/internal/configloaders/admin_module.go b/internal/configloaders/admin_module.go index 0d14aba5..ab215075 100644 --- a/internal/configloaders/admin_module.go +++ b/internal/configloaders/admin_module.go @@ -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, diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go index 4ed6e9e5..5e29ff0e 100644 --- a/internal/rpc/rpc_client.go +++ b/internal/rpc/rpc_client.go @@ -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() diff --git a/internal/web/actions/actionutils/parent_action.go b/internal/web/actions/actionutils/parent_action.go index 3ef8fed4..871e45b1 100644 --- a/internal/web/actions/actionutils/parent_action.go +++ b/internal/web/actions/actionutils/parent_action.go @@ -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 视图里可以使用的数据 diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go index aedb7673..50c8d919 100644 --- a/internal/web/helpers/user_must_auth.go +++ b/internal/web/helpers/user_must_auth.go @@ -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, diff --git a/web/public/js/components/common/datepicker.js b/web/public/js/components/common/datepicker.js new file mode 100644 index 00000000..5a6b22a8 --- /dev/null +++ b/web/public/js/components/common/datepicker.js @@ -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: `
+ +
` +}) \ No newline at end of file diff --git a/web/public/js/components/plans/plan-price-config-box.js b/web/public/js/components/plans/plan-price-config-box.js new file mode 100644 index 00000000..b84f15e6 --- /dev/null +++ b/web/public/js/components/plans/plan-price-config-box.js @@ -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: `
+ + + + + + +
+  按时间周期     +  按带宽用量 +
+ + +
+
+ + + + + + + + + + + + + +
月度价格 +
+ + +
+
季度价格 +
+ + +
+
年度价格 +
+ + +
+
+
+ + +
+
+ + + + + +
基础带宽费用 +
+ + 元/GB +
+
+
+
` +}) \ No newline at end of file diff --git a/web/public/js/components/plans/plan-price-view.js b/web/public/js/components/plans/plan-price-view.js new file mode 100644 index 00000000..43174314 --- /dev/null +++ b/web/public/js/components/plans/plan-price-view.js @@ -0,0 +1,18 @@ +Vue.component("plan-price-view", { + props: ["v-plan"], + data: function () { + return { + plan: this.vPlan + } + }, + template: `
+ + 月度:¥{{plan.monthlyPrice}}元
+ 季度:¥{{plan.seasonallyPrice}}元
+ 年度:¥{{plan.yearlyPrice}}元 +
+ + 基础价格:¥{{plan.bandwidthPrice.base}}元/GB + +
` +}) \ No newline at end of file diff --git a/web/public/js/components/plans/plan-user-selector.js b/web/public/js/components/plans/plan-user-selector.js new file mode 100644 index 00000000..836a13eb --- /dev/null +++ b/web/public/js/components/plans/plan-user-selector.js @@ -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: `
+ +
` +}) \ No newline at end of file diff --git a/web/public/js/components/server/bandwidth-limit-view.js b/web/public/js/components/server/bandwidth-limit-view.js new file mode 100644 index 00000000..8ebd7f0d --- /dev/null +++ b/web/public/js/components/server/bandwidth-limit-view.js @@ -0,0 +1,16 @@ +// 显示带宽限制说明 +Vue.component("bandwidth-limit-view", { + props: ["v-bandwidth-limit"], + data: function () { + return { + config: this.vBandwidthLimit + } + }, + template: `
+
+ 日带宽限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}
+ 月带宽限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}
+
+ 没有限制。 +
` +}) \ No newline at end of file diff --git a/web/public/js/utils.js b/web/public/js/utils.js index 7350590a..8b563458 100644 --- a/web/public/js/utils.js +++ b/web/public/js/utils.js @@ -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); diff --git a/web/views/@default/servers/certs/acme/index.html b/web/views/@default/servers/certs/acme/index.html index d5f6d0ab..bea95516 100644 --- a/web/views/@default/servers/certs/acme/index.html +++ b/web/views/@default/servers/certs/acme/index.html @@ -9,7 +9,7 @@ 有效证书({{countAvailable}}) 过期证书({{countExpired}}) 7天内过期({{count7Days}}) - 30天过期({{count30Days}}) + 30天内过期({{count30Days}})
diff --git a/web/views/@default/servers/certs/index.html b/web/views/@default/servers/certs/index.html index 37bb2846..b9d1d2c8 100644 --- a/web/views/@default/servers/certs/index.html +++ b/web/views/@default/servers/certs/index.html @@ -8,8 +8,8 @@ 有效证书({{countAvailable}}) 过期证书({{countExpired}}) 7天内过期({{count7Days}}) - 30天过期({{count30Days}}) - | + 30天内过期({{count30Days}}) + | [上传证书]