mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
feat: v2 Initial commit
This commit is contained in:
32
.air.toml
Normal file
32
.air.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
root = "."
|
||||
tmp_dir = "storage/temp"
|
||||
|
||||
[build]
|
||||
bin = "./storage/temp/main"
|
||||
cmd = "go build -o ./storage/temp/main ."
|
||||
delay = 1000
|
||||
exclude_dir = ["storage", "database"]
|
||||
exclude_file = []
|
||||
exclude_regex = []
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
send_interrupt = false
|
||||
stop_on_error = true
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
|
||||
JWT_SECRET=
|
||||
72
.gitignore
vendored
Normal file
72
.gitignore
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
tmp
|
||||
.env
|
||||
.history
|
||||
|
||||
# Golang #
|
||||
# `go test -c` 生成的二进制文件
|
||||
*.test
|
||||
# go coverage 工具
|
||||
*.out
|
||||
*.prof
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
# 编译文件 #
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
|
||||
# 压缩包 #
|
||||
# Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit
|
||||
*.7z
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
|
||||
# 日志文件和数据库 #
|
||||
*.log
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
# 临时文件 #
|
||||
tmp/
|
||||
.tmp/
|
||||
|
||||
# 系统生成文件 #
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.TemporaryItems
|
||||
.fseventsd
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# IDE 和编辑器 #
|
||||
.idea/
|
||||
/go_build_*
|
||||
out/
|
||||
.vscode/
|
||||
.vscode/settings.json
|
||||
*.sublime*
|
||||
__debug_bin
|
||||
.project
|
||||
|
||||
# 前端工具链 #
|
||||
.sass-cache/*
|
||||
node_modules/
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [2022] [HaoZi Technology Co., Ltd.]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 耗子Linux面板
|
||||
|
||||
这是耗子Linux面板的开源仓库,基于Apache License 2.0协议进行开源,目前处于积极开发状态。
|
||||
|
||||
#### 我们正在进行基于 Golang 的 V2 版本的开发,加入我们的 交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ频道:[耗子](https://pd.qq.com/s/fyol46wfy) 获取最新动态。
|
||||
|
||||
广告: [`WeAvatar` —统一头像服务](https://weavatar.com)
|
||||
|
||||
## 运行环境
|
||||
|
||||
面板仅支持主流 `amd64` | `arm64` 系统的最新版本,且随着系统版本的更新,面板也会逐步停止对旧版本的支持。
|
||||
|
||||
不支持 `Ubuntu`,因为其发版太频繁,难以维护。
|
||||
|
||||
低配机器建议使用 `Debian`,资源占用较 `RHEL` 系更低。其他机器建议使用 `RockyLinux` | `AlmaLinux`,维护周期更长。
|
||||
|
||||
不在下表中的其他 `RHEL` 系统,可自行尝试安装,但不保证能够正常运行,且不提供技术支持。
|
||||
|
||||
| 系统 | 版本 |
|
||||
|------------|----|
|
||||
| RockyLinux | 9 |
|
||||
| AlmaLinux | 9 |
|
||||
| Debian | 12 |
|
||||
|
||||
## 安装面板
|
||||
|
||||
```shell
|
||||
TODO
|
||||
```
|
||||
|
||||
## 日常维护
|
||||
|
||||
```shell
|
||||
TODO
|
||||
```
|
||||
|
||||
## 问题反馈
|
||||
|
||||
使用类问题,可在 [WePublish社区论坛](https://wepublish.cn/forum) 提问寻求帮助。
|
||||
|
||||
对于面板自身问题,可在 GitHub 的`Issues`
|
||||
页面提交问题反馈,注意[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)。
|
||||
|
||||
交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ频道:[耗子](https://pd.qq.com/s/fyol46wfy)
|
||||
|
||||
## 赞助商
|
||||
|
||||
### 服务器
|
||||
|
||||
- [盾云](https://www.ddunyun.com/)
|
||||
|
||||
### CDN
|
||||
|
||||
- [盾云CDN](http://cdn.ddunyun.com/)
|
||||
|
||||
- [又拍云](https://www.upyun.com/)
|
||||
|
||||
#### 接受云资源和资金赞助,可通过QQ群咨询联系
|
||||
|
||||
## Star 趋势
|
||||
|
||||
[](https://starchart.cc/HaoZi-Team/Panel)
|
||||
78
app/console/commands/monitoring.go
Normal file
78
app/console/commands/monitoring.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gookit/color"
|
||||
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"panel/app/models"
|
||||
"panel/packages/helpers"
|
||||
)
|
||||
|
||||
type Monitoring struct {
|
||||
}
|
||||
|
||||
// Signature The name and signature of the console command.
|
||||
func (receiver *Monitoring) Signature() string {
|
||||
return "panel:monitoring"
|
||||
}
|
||||
|
||||
// Description The console command description.
|
||||
func (receiver *Monitoring) Description() string {
|
||||
return "[面板] 系统监控"
|
||||
}
|
||||
|
||||
// Extend The console command extend.
|
||||
func (receiver *Monitoring) Extend() command.Extend {
|
||||
return command.Extend{
|
||||
Category: "panel",
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Execute the console command.
|
||||
func (receiver *Monitoring) Handle(ctx console.Context) error {
|
||||
var setting models.Setting
|
||||
if err := facades.Orm().Query().Where("key", "monitor").First(&setting); err != nil {
|
||||
return nil
|
||||
}
|
||||
if setting.Value == "0" || len(setting.Value) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
info, err := helpers.GetMonitoringInfo()
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板] 系统监控失败: %s", err.Error())
|
||||
color.Redf("[面板] 系统监控失败: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
err = facades.Orm().Query().Create(&models.Monitor{
|
||||
Info: info,
|
||||
})
|
||||
if err != nil {
|
||||
facades.Log().Errorf("[面板] 系统监控保存失败: %s", err.Error())
|
||||
color.Redf("[面板] 系统监控保存失败: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除过期数据
|
||||
err = facades.Orm().Query().Where("key", "monitor_days").First(&setting)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if setting.Value == "0" || len(setting.Value) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
days, err := strconv.Atoi(setting.Value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
_, err = facades.Orm().Query().Where("created_at < ?", carbon.Now().SubDays(days).ToDateTimeString()).Delete(&models.Monitor{})
|
||||
|
||||
return nil
|
||||
}
|
||||
20
app/console/kernel.go
Normal file
20
app/console/kernel.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/schedule"
|
||||
"panel/app/console/commands"
|
||||
)
|
||||
|
||||
type Kernel struct {
|
||||
}
|
||||
|
||||
func (kernel *Kernel) Schedule() []schedule.Event {
|
||||
return []schedule.Event{}
|
||||
}
|
||||
|
||||
func (kernel *Kernel) Commands() []console.Command {
|
||||
return []console.Command{
|
||||
&commands.Monitoring{},
|
||||
}
|
||||
}
|
||||
94
app/http/controllers/user_controller.go
Normal file
94
app/http/controllers/user_controller.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
models "panel/app/Models"
|
||||
"panel/app/http/requests"
|
||||
)
|
||||
|
||||
type UserController struct {
|
||||
//Dependent services
|
||||
}
|
||||
|
||||
func NewUserController() *UserController {
|
||||
return &UserController{
|
||||
//Inject services
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserController) Login(ctx http.Context) {
|
||||
var loginRequest requests.LoginRequest
|
||||
errors, err := ctx.Request().ValidateRequest(&loginRequest)
|
||||
if err != nil {
|
||||
ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
|
||||
"code": 422,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if errors != nil {
|
||||
ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
|
||||
"code": 422,
|
||||
"message": errors.All(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
err = facades.Orm().Query().Where("username", loginRequest.Username).First(&user)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][UserController] 查询用户失败 ", err)
|
||||
ctx.Response().Json(http.StatusInternalServerError, http.Json{
|
||||
"code": 500,
|
||||
"message": "系统内部错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if user.ID == 0 || !facades.Hash().Check(loginRequest.Password, user.Password) {
|
||||
ctx.Response().Json(http.StatusUnauthorized, http.Json{
|
||||
"code": 401,
|
||||
"message": "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查密码是否需要重新哈希
|
||||
if facades.Hash().NeedsRehash(user.Password) {
|
||||
// 更新密码
|
||||
user.Password, err = facades.Hash().Make(loginRequest.Password)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][UserController] 更新密码失败 ", err)
|
||||
ctx.Response().Json(http.StatusInternalServerError, http.Json{
|
||||
"code": 500,
|
||||
"message": "系统内部错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
token, loginErr := facades.Auth().LoginUsingID(ctx, user.ID)
|
||||
if loginErr != nil {
|
||||
facades.Log().Error("[面板][UserController] 登录失败 ", loginErr)
|
||||
ctx.Response().Json(http.StatusInternalServerError, http.Json{
|
||||
"code": 500,
|
||||
"message": loginErr.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
"code": 0,
|
||||
"message": "登录成功",
|
||||
"data": http.Json{
|
||||
"access_token": token,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (r *UserController) Info(ctx http.Context) {
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
"Hello": "Goravel",
|
||||
})
|
||||
}
|
||||
18
app/http/kernel.go
Normal file
18
app/http/kernel.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
|
||||
"panel/app/http/middleware"
|
||||
)
|
||||
|
||||
type Kernel struct {
|
||||
}
|
||||
|
||||
// The application's global HTTP middleware stack.
|
||||
// These middleware are run during every request to your application.
|
||||
func (kernel Kernel) Middleware() []http.Middleware {
|
||||
return []http.Middleware{
|
||||
middleware.Static(),
|
||||
}
|
||||
}
|
||||
0
app/http/middleware/.gitignore
vendored
Normal file
0
app/http/middleware/.gitignore
vendored
Normal file
63
app/http/middleware/jwt.go
Normal file
63
app/http/middleware/jwt.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/goravel/framework/auth"
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
)
|
||||
|
||||
// Jwt 确保通过 JWT 鉴权
|
||||
func Jwt() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
token := ctx.Request().Header("Authorization", "")
|
||||
if len(token) == 0 {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"code": 401,
|
||||
"message": "未登录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// JWT 鉴权
|
||||
if _, err := facades.Auth().Parse(ctx, token); err != nil {
|
||||
if errors.Is(err, auth.ErrorTokenExpired) {
|
||||
token, err = facades.Auth().Refresh(ctx)
|
||||
if err != nil {
|
||||
// Refresh time exceeded
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"code": 401,
|
||||
"message": "登录已过期",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
token = "Bearer " + token
|
||||
} else {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"code": 401,
|
||||
"message": "登录已过期",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 取出用户信息
|
||||
var user models.User
|
||||
if err := facades.Auth().User(ctx, &user); err != nil {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
|
||||
"code": 403,
|
||||
"message": "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.WithValue("user", user)
|
||||
|
||||
ctx.Response().Header("Authorization", token)
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
16
app/http/middleware/static.go
Normal file
16
app/http/middleware/static.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-contrib/static"
|
||||
|
||||
contractshttp "github.com/goravel/framework/contracts/http"
|
||||
frameworkhttp "github.com/goravel/framework/http"
|
||||
)
|
||||
|
||||
func Static() contractshttp.Middleware {
|
||||
return func(ctx contractshttp.Context) {
|
||||
static.Serve("/", static.LocalFile("./public", false))(ctx.(*frameworkhttp.GinContext).Instance())
|
||||
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
38
app/http/requests/login_request.go
Normal file
38
app/http/requests/login_request.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
}
|
||||
|
||||
func (r *LoginRequest) Authorize(ctx http.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *LoginRequest) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"login": "required",
|
||||
"password": "required|min_len:8",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LoginRequest) Messages(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"login.required": "登录名不能为空",
|
||||
"password.required": "密码不能为空",
|
||||
"password.min_len": "密码长度不能小于 8 位",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LoginRequest) Attributes(ctx http.Context) map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (r *LoginRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
|
||||
return nil
|
||||
}
|
||||
15
app/models/cron.go
Normal file
15
app/models/cron.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Cron struct {
|
||||
orm.Model
|
||||
Name string `gorm:"not null;unique"`
|
||||
Status bool `gorm:"not null;default:false"`
|
||||
Type string `gorm:"not null"`
|
||||
Time string `gorm:"not null"`
|
||||
Shell string `gorm:"default:null"`
|
||||
Log string `gorm:"default:null"`
|
||||
}
|
||||
16
app/models/database.go
Normal file
16
app/models/database.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
orm.Model
|
||||
Name string `gorm:"unique;not null"`
|
||||
Type string `gorm:"not null;index"`
|
||||
Host string `gorm:"not null"`
|
||||
Port int `gorm:"not null"`
|
||||
Username string `gorm:"not null"`
|
||||
Password string `gorm:"default:null"`
|
||||
Remark string `gorm:"default:null"`
|
||||
}
|
||||
12
app/models/monitor.go
Normal file
12
app/models/monitor.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"panel/packages/helpers"
|
||||
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
orm.Model
|
||||
Info helpers.MonitoringInfo `gorm:"type:json;serializer:json"`
|
||||
}
|
||||
13
app/models/plugin.go
Normal file
13
app/models/plugin.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
orm.Model
|
||||
Slug string `gorm:"unique;not null"`
|
||||
Version string `gorm:"not null"`
|
||||
Show bool `gorm:"default:false;not null"`
|
||||
ShowOrder int `gorm:"default:0;not null"`
|
||||
}
|
||||
11
app/models/setting.go
Normal file
11
app/models/setting.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
orm.Model
|
||||
Key string `gorm:"unique;not null"`
|
||||
Value string `gorm:"default:null"`
|
||||
}
|
||||
13
app/models/task.go
Normal file
13
app/models/task.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
orm.Model
|
||||
Name string `gorm:"not null"`
|
||||
Status string `gorm:"not null;default:'waiting'"`
|
||||
Shell string `gorm:"default:null"`
|
||||
Log string `gorm:"default:null"`
|
||||
}
|
||||
13
app/models/user.go
Normal file
13
app/models/user.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
orm.Model
|
||||
Username string `gorm:"unique;not null"`
|
||||
Password string `gorm:"not null"`
|
||||
Email string `gorm:"default:null"`
|
||||
orm.SoftDeletes
|
||||
}
|
||||
15
app/models/website.go
Normal file
15
app/models/website.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
type Website struct {
|
||||
orm.Model
|
||||
Name string `gorm:"unique;not null"`
|
||||
Status bool `gorm:"default:true;not null;index"`
|
||||
Path string `gorm:"not null"`
|
||||
Php int `gorm:"default:0;not null;index"`
|
||||
Ssl bool `gorm:"default:false;not null;index"`
|
||||
Remark string `gorm:"default:null"`
|
||||
}
|
||||
16
app/providers/app_service_provider.go
Normal file
16
app/providers/app_service_provider.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
)
|
||||
|
||||
type AppServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *AppServiceProvider) Register(app foundation.Application) {
|
||||
|
||||
}
|
||||
|
||||
func (receiver *AppServiceProvider) Boot(app foundation.Application) {
|
||||
|
||||
}
|
||||
16
app/providers/auth_service_provider.go
Normal file
16
app/providers/auth_service_provider.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
)
|
||||
|
||||
type AuthServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *AuthServiceProvider) Register(app foundation.Application) {
|
||||
|
||||
}
|
||||
|
||||
func (receiver *AuthServiceProvider) Boot(app foundation.Application) {
|
||||
|
||||
}
|
||||
21
app/providers/console_service_provider.go
Normal file
21
app/providers/console_service_provider.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/console"
|
||||
)
|
||||
|
||||
type ConsoleServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *ConsoleServiceProvider) Register(app foundation.Application) {
|
||||
kernel := console.Kernel{}
|
||||
facades.Schedule().Register(kernel.Schedule())
|
||||
facades.Artisan().Register(kernel.Commands())
|
||||
}
|
||||
|
||||
func (receiver *ConsoleServiceProvider) Boot(app foundation.Application) {
|
||||
|
||||
}
|
||||
22
app/providers/event_service_provider.go
Normal file
22
app/providers/event_service_provider.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/event"
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
type EventServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *EventServiceProvider) Register(app foundation.Application) {
|
||||
facades.Event().Register(receiver.listen())
|
||||
}
|
||||
|
||||
func (receiver *EventServiceProvider) Boot(app foundation.Application) {
|
||||
|
||||
}
|
||||
|
||||
func (receiver *EventServiceProvider) listen() map[event.Event][]event.Listener {
|
||||
return map[event.Event][]event.Listener{}
|
||||
}
|
||||
22
app/providers/queue_service_provider.go
Normal file
22
app/providers/queue_service_provider.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/contracts/queue"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
type QueueServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *QueueServiceProvider) Register(app foundation.Application) {
|
||||
facades.Queue().Register(receiver.Jobs())
|
||||
}
|
||||
|
||||
func (receiver *QueueServiceProvider) Boot(app foundation.Application) {
|
||||
|
||||
}
|
||||
|
||||
func (receiver *QueueServiceProvider) Jobs() []queue.Job {
|
||||
return []queue.Job{}
|
||||
}
|
||||
27
app/providers/route_service_provider.go
Normal file
27
app/providers/route_service_provider.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/http"
|
||||
"panel/routes"
|
||||
)
|
||||
|
||||
type RouteServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *RouteServiceProvider) Register(app foundation.Application) {
|
||||
//Add HTTP middlewares
|
||||
facades.Route().GlobalMiddleware(http.Kernel{}.Middleware()...)
|
||||
}
|
||||
|
||||
func (receiver *RouteServiceProvider) Boot(app foundation.Application) {
|
||||
receiver.configureRateLimiting()
|
||||
|
||||
routes.Web()
|
||||
}
|
||||
|
||||
func (receiver *RouteServiceProvider) configureRateLimiting() {
|
||||
|
||||
}
|
||||
24
app/providers/validation_service_provider.go
Normal file
24
app/providers/validation_service_provider.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
type ValidationServiceProvider struct {
|
||||
}
|
||||
|
||||
func (receiver *ValidationServiceProvider) Register(app foundation.Application) {
|
||||
|
||||
}
|
||||
|
||||
func (receiver *ValidationServiceProvider) Boot(app foundation.Application) {
|
||||
if err := facades.Validation().AddRules(receiver.rules()); err != nil {
|
||||
facades.Log().Errorf("add rules error: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (receiver *ValidationServiceProvider) rules() []validation.Rule {
|
||||
return []validation.Rule{}
|
||||
}
|
||||
17
bootstrap/app.go
Normal file
17
bootstrap/app.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/foundation"
|
||||
|
||||
"panel/config"
|
||||
)
|
||||
|
||||
func Boot() {
|
||||
app := foundation.NewApplication()
|
||||
|
||||
//Bootstrap the application
|
||||
app.Boot()
|
||||
|
||||
//Bootstrap the config.
|
||||
config.Boot()
|
||||
}
|
||||
7
bootstrap/plugins.go
Normal file
7
bootstrap/plugins.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package bootstrap
|
||||
|
||||
import "panel/plugins/openresty"
|
||||
|
||||
func Plugins() {
|
||||
openresty.Boot()
|
||||
}
|
||||
90
config/app.go
Normal file
90
config/app.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/auth"
|
||||
"github.com/goravel/framework/cache"
|
||||
"github.com/goravel/framework/console"
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/crypt"
|
||||
"github.com/goravel/framework/database"
|
||||
"github.com/goravel/framework/event"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/hash"
|
||||
"github.com/goravel/framework/http"
|
||||
"github.com/goravel/framework/log"
|
||||
"github.com/goravel/framework/mail"
|
||||
"github.com/goravel/framework/queue"
|
||||
"github.com/goravel/framework/route"
|
||||
"github.com/goravel/framework/schedule"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
"github.com/goravel/framework/validation"
|
||||
|
||||
"panel/app/providers"
|
||||
)
|
||||
|
||||
// Boot Start all init methods of the current folder to bootstrap all config.
|
||||
func Boot() {}
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("app", map[string]any{
|
||||
// Application Name
|
||||
//
|
||||
// This value is the name of your application. This value is used when the
|
||||
// framework needs to place the application's name in a notification or
|
||||
// any other location as required by the application or its packages.
|
||||
"name": "Panel",
|
||||
|
||||
// Application Environment
|
||||
//
|
||||
// This value determines the "environment" your application is currently
|
||||
// running in. This may determine how you prefer to configure various
|
||||
// services the application utilizes. Set this in your ".env" file.
|
||||
"env": config.Env("APP_ENV", "production"),
|
||||
|
||||
// Application Debug Mode
|
||||
"debug": config.Env("APP_DEBUG", false),
|
||||
|
||||
// Application Timezone
|
||||
//
|
||||
// Here you may specify the default timezone for your application.
|
||||
// Example: UTC, Asia/Shanghai
|
||||
// More: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
"timezone": carbon.UTC,
|
||||
|
||||
// Encryption Key
|
||||
//
|
||||
// 32 character string, otherwise these encrypted strings
|
||||
// will not be safe. Please do this before deploying an application!
|
||||
"key": config.Env("APP_KEY", ""),
|
||||
|
||||
// Autoload service providers
|
||||
//
|
||||
// The service providers listed here will be automatically loaded on the
|
||||
// request to your application. Feel free to add your own services to
|
||||
// this array to grant expanded functionality to your applications.
|
||||
"providers": []foundation.ServiceProvider{
|
||||
&log.ServiceProvider{},
|
||||
&console.ServiceProvider{},
|
||||
&database.ServiceProvider{},
|
||||
&cache.ServiceProvider{},
|
||||
&http.ServiceProvider{},
|
||||
&route.ServiceProvider{},
|
||||
&schedule.ServiceProvider{},
|
||||
&event.ServiceProvider{},
|
||||
&queue.ServiceProvider{},
|
||||
&mail.ServiceProvider{},
|
||||
&auth.ServiceProvider{},
|
||||
&hash.ServiceProvider{},
|
||||
&crypt.ServiceProvider{},
|
||||
&validation.ServiceProvider{},
|
||||
&providers.AppServiceProvider{},
|
||||
&providers.AuthServiceProvider{},
|
||||
&providers.RouteServiceProvider{},
|
||||
&providers.ConsoleServiceProvider{},
|
||||
&providers.QueueServiceProvider{},
|
||||
&providers.EventServiceProvider{},
|
||||
&providers.ValidationServiceProvider{},
|
||||
},
|
||||
})
|
||||
}
|
||||
36
config/auth.go
Normal file
36
config/auth.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("auth", map[string]any{
|
||||
// Authentication Defaults
|
||||
//
|
||||
// This option controls the default authentication "guard"
|
||||
// reset options for your application. You may change these defaults
|
||||
// as required, but they're a perfect start for most applications.
|
||||
"defaults": map[string]any{
|
||||
"guard": "user",
|
||||
},
|
||||
|
||||
// Authentication Guards
|
||||
//
|
||||
// Next, you may define every authentication guard for your application.
|
||||
// Of course, a great default configuration has been defined for you
|
||||
// here which uses session storage and the Eloquent user provider.
|
||||
//
|
||||
// All authentication drivers have a user provider. This defines how the
|
||||
// users are actually retrieved out of your database or other storage
|
||||
// mechanisms used by this application to persist your user's data.
|
||||
//
|
||||
// Supported: "jwt"
|
||||
"guards": map[string]any{
|
||||
"user": map[string]any{
|
||||
"driver": "jwt",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
37
config/cache.go
Normal file
37
config/cache.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("cache", map[string]any{
|
||||
// Default Cache Store
|
||||
//
|
||||
// This option controls the default cache connection that gets used while
|
||||
// using this caching library. This connection is used when another is
|
||||
// not explicitly specified when executing a given caching function.
|
||||
"default": config.Env("CACHE_STORE", "memory"),
|
||||
|
||||
// Cache Stores
|
||||
//
|
||||
// Here you may define all the cache "stores" for your application as
|
||||
// well as their drivers. You may even define multiple stores for the
|
||||
// same cache driver to group types of items stored in your caches.
|
||||
// Available Drivers: "memory", "redis", "custom"
|
||||
"stores": map[string]any{
|
||||
"memory": map[string]any{
|
||||
"driver": "memory",
|
||||
},
|
||||
},
|
||||
|
||||
// Cache Key Prefix
|
||||
//
|
||||
// When utilizing a RAM based store such as APC or Memcached, there might
|
||||
// be other applications utilizing the same cache. So, we'll specify a
|
||||
// value to get prefixed to all our keys, so we can avoid collisions.
|
||||
// Must: a-zA-Z0-9_-
|
||||
"prefix": "panel_cache",
|
||||
})
|
||||
}
|
||||
24
config/cors.go
Normal file
24
config/cors.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("cors", map[string]any{
|
||||
// Cross-Origin Resource Sharing (CORS) Configuration
|
||||
//
|
||||
// Here you may configure your settings for cross-origin resource sharing
|
||||
// or "CORS". This determines what cross-origin operations may execute
|
||||
// in web browsers. You are free to adjust these settings as needed.
|
||||
//
|
||||
// To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
"allowed_methods": []string{"*"},
|
||||
"allowed_origins": []string{"*"},
|
||||
"allowed_headers": []string{"*"},
|
||||
"exposed_headers": []string{"*"},
|
||||
"max_age": 0,
|
||||
"supports_credentials": false,
|
||||
})
|
||||
}
|
||||
78
config/database.go
Normal file
78
config/database.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("database", map[string]any{
|
||||
// Default database connection name
|
||||
"default": "panel",
|
||||
|
||||
// Database connections
|
||||
"connections": map[string]any{
|
||||
"panel": map[string]any{
|
||||
"driver": "sqlite",
|
||||
"database": "database/panel.db",
|
||||
"prefix": "",
|
||||
"singular": false, // Table name is singular
|
||||
},
|
||||
},
|
||||
|
||||
// Set pool configuration
|
||||
"pool": map[string]any{
|
||||
// Sets the maximum number of connections in the idle
|
||||
// connection pool.
|
||||
//
|
||||
// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns,
|
||||
// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit.
|
||||
//
|
||||
// If n <= 0, no idle connections are retained.
|
||||
"max_idle_conns": 10,
|
||||
// Sets the maximum number of open connections to the database.
|
||||
//
|
||||
// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than
|
||||
// MaxIdleConns, then MaxIdleConns will be reduced to match the new
|
||||
// MaxOpenConns limit.
|
||||
//
|
||||
// If n <= 0, then there is no limit on the number of open connections.
|
||||
"max_open_conns": 100,
|
||||
// Sets the maximum amount of time a connection may be idle.
|
||||
//
|
||||
// Expired connections may be closed lazily before reuse.
|
||||
//
|
||||
// If d <= 0, connections are not closed due to a connection's idle time.
|
||||
// Unit: Second
|
||||
"conn_max_idletime": 3600,
|
||||
// Sets the maximum amount of time a connection may be reused.
|
||||
//
|
||||
// Expired connections may be closed lazily before reuse.
|
||||
//
|
||||
// If d <= 0, connections are not closed due to a connection's age.
|
||||
// Unit: Second
|
||||
"conn_max_lifetime": 3600,
|
||||
},
|
||||
|
||||
// Migration Repository Table
|
||||
//
|
||||
// This table keeps track of all the migrations that have already run for
|
||||
// your application. Using this information, we can determine which of
|
||||
// the migrations on disk haven't actually been run in the database.
|
||||
"migrations": "migrations",
|
||||
|
||||
// Redis Databases
|
||||
//
|
||||
// Redis is an open source, fast, and advanced key-value store that also
|
||||
// provides a richer body of commands than a typical key-value system
|
||||
// such as APC or Memcached.
|
||||
"redis": map[string]any{
|
||||
"default": map[string]any{
|
||||
"host": config.Env("REDIS_HOST", ""),
|
||||
"password": config.Env("REDIS_PASSWORD", ""),
|
||||
"port": config.Env("REDIS_PORT", 6379),
|
||||
"database": config.Env("REDIS_DB", 0),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
32
config/filesystems.go
Normal file
32
config/filesystems.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("filesystems", map[string]any{
|
||||
// Default Filesystem Disk
|
||||
//
|
||||
// Here you may specify the default filesystem disk that should be used
|
||||
// by the framework. The "local" disk, as well as a variety of cloud
|
||||
// based disks are available to your application. Just store away!
|
||||
"default": config.Env("FILESYSTEM_DISK", "local"),
|
||||
|
||||
// Filesystem Disks
|
||||
//
|
||||
// Here you may configure as many filesystem "disks" as you wish, and you
|
||||
// may even configure multiple disks of the same driver. Defaults have
|
||||
// been set up for each driver as an example of the required values.
|
||||
//
|
||||
// Supported Drivers: "local", "s3", "oss", "cos", "minio", "custom"
|
||||
"disks": map[string]any{
|
||||
"local": map[string]any{
|
||||
"driver": "local",
|
||||
"root": "storage/app",
|
||||
"url": "http://localhost/storage",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
40
config/hashing.go
Normal file
40
config/hashing.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("hashing", map[string]any{
|
||||
// Hashing Driver
|
||||
//
|
||||
// This option controls the default diver that gets used
|
||||
// by the framework hash facade.
|
||||
// Default driver is "bcrypt".
|
||||
//
|
||||
// Supported Drivers: "bcrypt", "argon2id"
|
||||
"driver": "bcrypt",
|
||||
|
||||
// Bcrypt Hashing Options
|
||||
// rounds: The cost factor that should be used to compute the bcrypt hash.
|
||||
// The cost factor controls how much time is needed to compute a single bcrypt hash.
|
||||
// The higher the cost factor, the more hashing rounds are done. Increasing the cost
|
||||
// factor by 1 doubles the necessary time. After a certain point, the returns on
|
||||
// hashing time versus attacker time are diminishing, so choose your cost factor wisely.
|
||||
"bcrypt": map[string]any{
|
||||
"rounds": 10,
|
||||
},
|
||||
|
||||
// Argon2id Hashing Options
|
||||
// memory: A memory cost, which defines the memory usage, given in kibibytes.
|
||||
// time: A time cost, which defines the amount of computation
|
||||
// realized and therefore the execution time, given in number of iterations.
|
||||
// threads: A parallelism degree, which defines the number of parallel threads.
|
||||
"argon2id": map[string]any{
|
||||
"memory": 65536,
|
||||
"time": 4,
|
||||
"threads": 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
31
config/http.go
Normal file
31
config/http.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("http", map[string]any{
|
||||
// HTTP URL
|
||||
"url": "http://localhost",
|
||||
// HTTP Host
|
||||
"host": "0.0.0.0",
|
||||
// HTTP Port
|
||||
"port": "8888",
|
||||
// HTTPS Configuration
|
||||
"tls": map[string]any{
|
||||
// HTTPS Host
|
||||
"host": "0.0.0.0",
|
||||
// HTTPS Port
|
||||
"port": "8899",
|
||||
// SSL Certificate
|
||||
"ssl": map[string]any{
|
||||
// ca.pem
|
||||
"cert": "",
|
||||
// ca.key
|
||||
"key": "",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
41
config/jwt.go
Normal file
41
config/jwt.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("jwt", map[string]any{
|
||||
// JWT Authentication Secret
|
||||
//
|
||||
// Don't forget to set this in your .env file, as it will be used to sign
|
||||
// your tokens. A helper command is provided for this:
|
||||
// `go run . artisan jwt:secret`
|
||||
"secret": config.Env("JWT_SECRET", ""),
|
||||
|
||||
// JWT time to live
|
||||
//
|
||||
// Specify the length of time (in minutes) that the token will be valid for.
|
||||
// Defaults to 1 hour.
|
||||
//
|
||||
// You can also set this to 0, to yield a never expiring token.
|
||||
// Some people may want this behaviour for e.g. a mobile app.
|
||||
// This is not particularly recommended, so make sure you have appropriate
|
||||
// systems in place to revoke the token if necessary.
|
||||
"ttl": config.Env("JWT_TTL", 60),
|
||||
|
||||
// Refresh time to live
|
||||
//
|
||||
// Specify the length of time (in minutes) that the token can be refreshed
|
||||
// within. I.E. The user can refresh their token within a 2 week window of
|
||||
// the original token being created until they must re-authenticate.
|
||||
// Defaults to 2 weeks.
|
||||
//
|
||||
// You can also set this to null, to yield an infinite refresh time.
|
||||
// Some may want this instead of never expiring tokens for e.g. a mobile app.
|
||||
// This is not particularly recommended, so make sure you have appropriate
|
||||
// systems in place to revoke the token if necessary.
|
||||
"refresh_ttl": config.Env("JWT_REFRESH_TTL", 20160),
|
||||
})
|
||||
}
|
||||
42
config/logging.go
Normal file
42
config/logging.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("logging", map[string]any{
|
||||
// Default Log Channel
|
||||
//
|
||||
// This option defines the default log channel that gets used when writing
|
||||
// messages to the logs. The name specified in this option should match
|
||||
// one of the channels defined in the "channels" configuration array.
|
||||
"default": "stack",
|
||||
|
||||
// Log Channels
|
||||
//
|
||||
// Here you may configure the log channels for your application.
|
||||
// Available Drivers: "single", "daily", "custom", "stack"
|
||||
// Available Level: "debug", "info", "warning", "error", "fatal", "panic"
|
||||
"channels": map[string]any{
|
||||
"stack": map[string]any{
|
||||
"driver": "stack",
|
||||
"channels": []string{"daily"},
|
||||
},
|
||||
"single": map[string]any{
|
||||
"driver": "single",
|
||||
"path": "storage/logs/goravel.log",
|
||||
"level": "info",
|
||||
"print": true,
|
||||
},
|
||||
"daily": map[string]any{
|
||||
"driver": "daily",
|
||||
"path": "storage/logs/goravel.log",
|
||||
"level": "info",
|
||||
"days": 7,
|
||||
"print": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
43
config/mail.go
Normal file
43
config/mail.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import "github.com/goravel/framework/facades"
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("mail", map[string]any{
|
||||
// SMTP Host Address
|
||||
//
|
||||
// Here you may provide the host address of the SMTP server used by your
|
||||
// applications. A default option is provided that is compatible with
|
||||
// the Mailgun mail service which will provide reliable deliveries.
|
||||
"host": config.Env("MAIL_HOST", ""),
|
||||
|
||||
// SMTP Host Port
|
||||
//
|
||||
// This is the SMTP port used by your application to deliver e-mails to
|
||||
// users of the application. Like the host we have set this value to
|
||||
// stay compatible with the Mailgun e-mail application by default.
|
||||
"port": config.Env("MAIL_PORT", 587),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Global "From" Address
|
||||
// --------------------------------------------------------------------------
|
||||
//
|
||||
// You may wish for all e-mails sent by your application to be sent from
|
||||
// the same address. Here, you may specify a name and address that is
|
||||
// used globally for all e-mails that are sent by your application.
|
||||
"from": map[string]any{
|
||||
"address": config.Env("MAIL_FROM_ADDRESS", "hello@example.com"),
|
||||
"name": config.Env("MAIL_FROM_NAME", "Example"),
|
||||
},
|
||||
|
||||
// SMTP Server Username
|
||||
//
|
||||
// If your SMTP server requires a username for authentication, you should
|
||||
// set it here. This will get used to authenticate with your server on
|
||||
// connection. You may also set the "password" value below this one.
|
||||
"username": config.Env("MAIL_USERNAME"),
|
||||
|
||||
"password": config.Env("MAIL_PASSWORD"),
|
||||
})
|
||||
}
|
||||
28
config/queue.go
Normal file
28
config/queue.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("queue", map[string]any{
|
||||
// Default Queue Connection Name
|
||||
"default": config.Env("QUEUE_CONNECTION", "sync"),
|
||||
|
||||
// Queue Connections
|
||||
//
|
||||
// Here you may configure the connection information for each server that is used by your application.
|
||||
// Drivers: "sync", "redis"
|
||||
"connections": map[string]any{
|
||||
"sync": map[string]any{
|
||||
"driver": "sync",
|
||||
},
|
||||
"redis": map[string]any{
|
||||
"driver": "redis",
|
||||
"connection": "default",
|
||||
"queue": config.Env("REDIS_QUEUE", "default"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
0
database/migrations/.gitignore
vendored
Normal file
0
database/migrations/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS users;
|
||||
11
database/migrations/20230621124710_create_users_table.up.sql
Normal file
11
database/migrations/20230621124710_create_users_table.up.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE users
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
username varchar(255) NOT NULL,
|
||||
password varchar(255) NOT NULL,
|
||||
email varchar(255) DEFAULT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX users_username_unique ON users (username);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS websites;
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE websites
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name varchar(255) NOT NULL,
|
||||
status boolean DEFAULT 1 NOT NULL,
|
||||
path varchar(255) NOT NULL,
|
||||
php integer DEFAULT 0 NOT NULL,
|
||||
ssl boolean DEFAULT 0 NOT NULL,
|
||||
remark text DEFAULT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX websites_name_unique ON websites (name);
|
||||
CREATE INDEX websites_status_index ON websites (status);
|
||||
CREATE INDEX websites_php_index ON websites (php);
|
||||
CREATE INDEX websites_ssl_index ON websites (ssl);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS settings;
|
||||
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE settings
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
key varchar(255) NOT NULL,
|
||||
value varchar(255) DEFAULT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX settings_key_unique ON settings (key);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS databases;
|
||||
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE databases
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name varchar(255) NOT NULL,
|
||||
type varchar(255) NOT NULL,
|
||||
host varchar(255) NOT NULL,
|
||||
port integer NOT NULL,
|
||||
username varchar(255) NOT NULL,
|
||||
password varchar(255) DEFAULT NULL,
|
||||
remark text DEFAULT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX databases_name_unique ON databases (name);
|
||||
CREATE INDEX databases_type_index ON databases (type);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS plugins;
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE plugins
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
slug varchar(255) NOT NULL,
|
||||
version varchar(255) NOT NULL,
|
||||
show boolean DEFAULT 0 NOT NULL,
|
||||
show_order integer DEFAULT 0 NOT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX plugins_slug_unique ON plugins (slug);
|
||||
CREATE INDEX plugins_show_index ON plugins (show);
|
||||
CREATE INDEX plugins_show_order_index ON plugins (show_order);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS tasks;
|
||||
12
database/migrations/20230621124826_create_tasks_table.up.sql
Normal file
12
database/migrations/20230621124826_create_tasks_table.up.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE tasks
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name varchar(255) NOT NULL,
|
||||
status varchar(255) DEFAULT 'waiting' NOT NULL,
|
||||
shell varchar(255) DEFAULT NULL,
|
||||
log varchar(255) DEFAULT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX tasks_status_index ON tasks (status);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS monitors;
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE monitors
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
info text NOT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS crons;
|
||||
16
database/migrations/20230621124930_create_crons_table.up.sql
Normal file
16
database/migrations/20230621124930_create_crons_table.up.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE crons
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name varchar(255) NOT NULL,
|
||||
status boolean DEFAULT 0 NOT NULL,
|
||||
type varchar(255) NOT NULL,
|
||||
time varchar(255) NOT NULL,
|
||||
shell varchar(255) DEFAULT NULL,
|
||||
log varchar(255) DEFAULT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX crons_name_unique ON crons (name);
|
||||
CREATE INDEX crons_status_index ON crons (status);
|
||||
CREATE INDEX crons_type_index ON crons (type);
|
||||
171
go.mod
Normal file
171
go.mod
Normal file
@@ -0,0 +1,171 @@
|
||||
module panel
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/goravel/framework v1.12.2
|
||||
google.golang.org/grpc v1.56.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.0 // indirect
|
||||
cloud.google.com/go/compute v1.19.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v0.13.0 // indirect
|
||||
cloud.google.com/go/pubsub v1.30.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.16 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae // indirect
|
||||
github.com/RichardKnop/machinery/v2 v2.0.11 // indirect
|
||||
github.com/aws/aws-sdk-go v1.37.16 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/docker/cli v20.10.22+incompatible // indirect
|
||||
github.com/docker/docker v20.10.24+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-contrib/static v0.0.1 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.1 // indirect
|
||||
github.com/glebarez/sqlite v1.8.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-redsync/redsync/v4 v4.0.4 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.16.2 // indirect
|
||||
github.com/golang-module/carbon/v2 v2.2.3 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/s2a-go v0.1.3 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||
github.com/gookit/color v1.5.3 // indirect
|
||||
github.com/gookit/filter v1.1.4 // indirect
|
||||
github.com/gookit/goutil v0.5.15 // indirect
|
||||
github.com/gookit/validate v1.4.6 // indirect
|
||||
github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c // indirect
|
||||
github.com/goravel/file-rotatelogs/v2 v2.4.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.16.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.5 // indirect
|
||||
github.com/lib/pq v1.10.2 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/ory/dockertest/v3 v3.10.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rs/cors v1.9.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
github.com/streadway/amqp v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/unrolled/secure v1.13.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.25.6 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.mongodb.org/mongo-driver v1.7.5 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
google.golang.org/api v0.122.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.5.1 // indirect
|
||||
gorm.io/driver/postgres v1.5.2 // indirect
|
||||
gorm.io/driver/sqlserver v1.5.1 // indirect
|
||||
gorm.io/gorm v1.25.1 // indirect
|
||||
gorm.io/plugin/dbresolver v1.4.1 // indirect
|
||||
modernc.org/libc v1.22.3 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.21.1 // indirect
|
||||
)
|
||||
39
main.go
Normal file
39
main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright [2022] [HaoZi Technology Co., Ltd.]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/bootstrap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 启动框架
|
||||
bootstrap.Boot()
|
||||
|
||||
// 加载插件
|
||||
bootstrap.Plugins()
|
||||
|
||||
// 启动 HTTP 服务
|
||||
go func() {
|
||||
if err := facades.Route().Run(); err != nil {
|
||||
facades.Log().Errorf("Route run error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
select {}
|
||||
}
|
||||
285
packages/helpers/helpers.go
Normal file
285
packages/helpers/helpers.go
Normal file
@@ -0,0 +1,285 @@
|
||||
// Package helpers 存放辅助方法
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Empty 类似于 PHP 的 empty() 函数
|
||||
func Empty(val interface{}) bool {
|
||||
if val == nil {
|
||||
return true
|
||||
}
|
||||
v := reflect.ValueOf(val)
|
||||
switch v.Kind() {
|
||||
case reflect.String, reflect.Array:
|
||||
return v.Len() == 0
|
||||
case reflect.Map, reflect.Slice:
|
||||
return v.Len() == 0 || v.IsNil()
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface())
|
||||
}
|
||||
|
||||
// FirstElement 安全地获取 args[0],避免 panic: runtime error: index out of range
|
||||
func FirstElement(args []string) string {
|
||||
if len(args) > 0 {
|
||||
return args[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RandomNumber 生成长度为 length 随机数字字符串
|
||||
func RandomNumber(length int) string {
|
||||
table := [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
|
||||
b := make([]byte, length)
|
||||
n, err := io.ReadAtLeast(rand.Reader, b, length)
|
||||
if n != length {
|
||||
panic(err)
|
||||
}
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] = table[int(b[i])%len(table)]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// RandomString 生成长度为 length 的随机字符串
|
||||
func RandomString(length int) string {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
letters := "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
for i, v := range b {
|
||||
b[i] = letters[v%byte(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// MD5 生成字符串的 MD5 值
|
||||
func MD5(str string) string {
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(str)))
|
||||
}
|
||||
|
||||
// FormatBytes 格式化bytes
|
||||
func FormatBytes(size float64) string {
|
||||
units := []string{"B", "KB", "MB", "GB", "TB"}
|
||||
|
||||
i := 0
|
||||
for ; size >= 1024 && i < 4; i++ {
|
||||
size /= 1024
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s", size, units[i])
|
||||
}
|
||||
|
||||
// Cut 裁剪字符串
|
||||
func Cut(begin, end, str string) string {
|
||||
b := utf8.RuneCountInString(str[:strings.Index(str, begin)]) + utf8.RuneCountInString(begin)
|
||||
e := utf8.RuneCountInString(str[:strings.Index(str, end)]) - b
|
||||
return string([]rune(str)[b : b+e])
|
||||
}
|
||||
|
||||
// GetNetInfo 获取网络统计信息
|
||||
func GetNetInfo() (uint64, uint64) {
|
||||
file, err := os.Open("/proc/net/dev")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
allRs := make(map[string][]string)
|
||||
lineNumber := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
lineNumber++
|
||||
if lineNumber < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
line = strings.Replace(line, ":", " ", -1)
|
||||
re := regexp.MustCompile("[ ]+")
|
||||
line = re.ReplaceAllString(line, " ")
|
||||
arr := strings.Split(line, " ")
|
||||
|
||||
if len(arr) > 0 && arr[0] != "" {
|
||||
allRs[arr[0]+strconv.Itoa(lineNumber)] = []string{arr[0], arr[1], arr[9]}
|
||||
}
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for key := range allRs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
tx := uint64(0)
|
||||
rx := uint64(0)
|
||||
|
||||
for _, key := range keys {
|
||||
if strings.Contains(key, "lo") {
|
||||
continue
|
||||
}
|
||||
val := allRs[key]
|
||||
txValue, err := strconv.ParseUint(val[2], 10, 64)
|
||||
if err == nil {
|
||||
tx += txValue
|
||||
}
|
||||
rxValue, err := strconv.ParseUint(val[1], 10, 64)
|
||||
if err == nil {
|
||||
rx += rxValue
|
||||
}
|
||||
}
|
||||
|
||||
return tx, rx
|
||||
}
|
||||
|
||||
// MonitoringInfo 监控信息
|
||||
type MonitoringInfo struct {
|
||||
CpuUse float64 `json:"cpu_use"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
UptimePercent float64 `json:"uptime_percent"`
|
||||
MemTotal float64 `json:"mem_total"`
|
||||
MemUse float64 `json:"mem_use"`
|
||||
MemUsePercent float64 `json:"mem_use_percent"`
|
||||
SwapTotal float64 `json:"swap_total"`
|
||||
SwapUse float64 `json:"swap_use"`
|
||||
SwapUsePercent float64 `json:"swap_use_percent"`
|
||||
NetTx uint64 `json:"net_tx"`
|
||||
NetRx uint64 `json:"net_rx"`
|
||||
}
|
||||
|
||||
// GetMonitoringInfo 获取监控数据
|
||||
func GetMonitoringInfo() (MonitoringInfo, error) {
|
||||
var res MonitoringInfo
|
||||
|
||||
// 网络流量
|
||||
netTx1, netRx1 := GetNetInfo()
|
||||
time.Sleep(time.Second)
|
||||
netTx2, netRx2 := GetNetInfo()
|
||||
res.NetTx = netTx2 - netTx1
|
||||
res.NetRx = netRx2 - netRx1
|
||||
|
||||
// CPU 信息
|
||||
cpuInfoRaw, err := os.ReadFile("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return MonitoringInfo{}, err
|
||||
}
|
||||
physicalArr := make(map[string]struct{})
|
||||
var siblingsSum float64
|
||||
var re = regexp.MustCompile(`\d+\.\d+`)
|
||||
uptimeOutput, err := exec.Command("uptime").Output()
|
||||
if err != nil {
|
||||
return MonitoringInfo{}, err
|
||||
}
|
||||
uptimeValues := re.FindAllString(string(uptimeOutput), -1)
|
||||
uptime1, _ := strconv.ParseFloat(uptimeValues[0], 64)
|
||||
res.Uptime = uptime1
|
||||
processors := bytes.Split(cpuInfoRaw, []byte("\nprocessor"))
|
||||
rePhysical := regexp.MustCompile(`physical id\s*:\s(.*)`)
|
||||
reSiblings := regexp.MustCompile(`siblings\s*:\s(.*)`)
|
||||
for _, v := range processors {
|
||||
physical := rePhysical.FindSubmatch(v)
|
||||
siblings := reSiblings.FindSubmatch(v)
|
||||
if len(physical) > 1 {
|
||||
pid := string(physical[1])
|
||||
if _, found := physicalArr[pid]; !found {
|
||||
if len(siblings) > 1 {
|
||||
siblingsValue, _ := strconv.ParseFloat(string(siblings[1]), 64)
|
||||
siblingsSum += siblingsValue
|
||||
}
|
||||
physicalArr[pid] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CPU 使用率
|
||||
cpuUse := 0.1
|
||||
psOutput, err := exec.Command("ps", "aux").Output()
|
||||
if err != nil {
|
||||
return MonitoringInfo{}, err
|
||||
}
|
||||
cpuRaw := strings.Split(string(psOutput), "\n")
|
||||
pid := os.Getpid()
|
||||
for _, v := range cpuRaw {
|
||||
v = strings.TrimSpace(v)
|
||||
v = regexp.MustCompile(`\s+`).ReplaceAllString(v, " ")
|
||||
values := strings.Split(v, " ")
|
||||
if len(values) > 2 {
|
||||
p, _ := strconv.Atoi(values[1])
|
||||
if p == pid {
|
||||
continue
|
||||
}
|
||||
cpu, _ := strconv.ParseFloat(values[2], 64)
|
||||
cpuUse += cpu
|
||||
}
|
||||
}
|
||||
cpuUse = cpuUse / siblingsSum
|
||||
if cpuUse > 100 {
|
||||
cpuUse = 100
|
||||
}
|
||||
res.CpuUse = cpuUse
|
||||
|
||||
// 内存使用率
|
||||
freeOutput, err := exec.Command("free", "-m").Output()
|
||||
if err != nil {
|
||||
return MonitoringInfo{}, err
|
||||
}
|
||||
memRaw := strings.Split(string(freeOutput), "\n")
|
||||
var memList, swapList string
|
||||
for _, v := range memRaw {
|
||||
if strings.Contains(v, "Mem") {
|
||||
memList = regexp.MustCompile(`\s+`).ReplaceAllString(v, " ")
|
||||
} else if strings.Contains(v, "Swap") {
|
||||
swapList = regexp.MustCompile(`\s+`).ReplaceAllString(v, " ")
|
||||
}
|
||||
}
|
||||
memArr := strings.Split(memList, " ")
|
||||
swapArr := strings.Split(swapList, " ")
|
||||
memTotal, _ := strconv.ParseFloat(memArr[1], 64)
|
||||
swapTotal, _ := strconv.ParseFloat(swapArr[1], 64)
|
||||
memUse, _ := strconv.ParseFloat(memArr[2], 64)
|
||||
swapUse, _ := strconv.ParseFloat(swapArr[2], 64)
|
||||
memUseP := (memUse / memTotal) * 100
|
||||
swapUseP := (swapUse / swapTotal) * 100
|
||||
uptime1P := uptime1 * 10
|
||||
if uptime1P > 100 {
|
||||
uptime1P = 100
|
||||
}
|
||||
|
||||
res.MemTotal = memTotal
|
||||
res.MemUse = memUse
|
||||
res.MemUsePercent = memUseP
|
||||
res.SwapTotal = swapTotal
|
||||
res.SwapUse = swapUse
|
||||
res.SwapUsePercent = swapUseP
|
||||
res.UptimePercent = uptime1P
|
||||
|
||||
return res, nil
|
||||
}
|
||||
21
plugins/openresty/http/controllers/openresty_controller.go
Normal file
21
plugins/openresty/http/controllers/openresty_controller.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
)
|
||||
|
||||
type OpenRestyController struct {
|
||||
//Dependent services
|
||||
}
|
||||
|
||||
func NewOpenrestyController() *OpenRestyController {
|
||||
return &OpenRestyController{
|
||||
//Inject services
|
||||
}
|
||||
}
|
||||
|
||||
func (r *OpenRestyController) Show(ctx http.Context) {
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
"Hello": "Goravel",
|
||||
})
|
||||
}
|
||||
13
plugins/openresty/openresty.go
Normal file
13
plugins/openresty/openresty.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package openresty
|
||||
|
||||
const (
|
||||
Name = "OpenResty"
|
||||
Author = "耗子"
|
||||
Description = "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台。"
|
||||
Slug = "openresty"
|
||||
Version = "1.21.4.1"
|
||||
)
|
||||
|
||||
func Boot() {
|
||||
Route()
|
||||
}
|
||||
22
plugins/openresty/routes.go
Normal file
22
plugins/openresty/routes.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/contracts/route"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/plugins/openresty/http/controllers"
|
||||
)
|
||||
|
||||
func Route() {
|
||||
facades.Route().Prefix("api/plugins/openresty").Group(func(route route.Route) {
|
||||
route.Get("/openresty", func(ctx http.Context) {
|
||||
ctx.Response().Json(http.StatusOK, http.Json{
|
||||
"Hello": "Openresty",
|
||||
})
|
||||
})
|
||||
|
||||
openRestyController := controllers.NewOpenrestyController()
|
||||
route.Get("/openresty/users/{id}", openRestyController.Show)
|
||||
})
|
||||
}
|
||||
30
public/.gitignore
vendored
Normal file
30
public/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.env
|
||||
pnpm-lock.yaml
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
52
public/index.html
Normal file
52
public/index.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!--
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// _ooOoo_ //
|
||||
// o8888888o //
|
||||
// 88" . "88 //
|
||||
// (| ^_^ |) //
|
||||
// O\ = /O //
|
||||
// ____/`---'\____ //
|
||||
// .' \\| |// `. //
|
||||
// / \\||| : |||// \ //
|
||||
// / _||||| -:- |||||- \ //
|
||||
// | | \\\ - /// | | //
|
||||
// | \_| ''\---/'' | | //
|
||||
// \ .-\__ `-` ___/-. / //
|
||||
// ___`. .' /--.--\ `. . ___ //
|
||||
// ."" '< `.___\_<|>_/___.' >'"". //
|
||||
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
|
||||
// \ \ `-. \_ __\ /__ _/ .-` / / //
|
||||
// ========`-.____`-.___\_____/___.-`____.-'======== //
|
||||
// `=---=' //
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
|
||||
// 佛祖保佑 永无Bug 永不宕机 //
|
||||
// Name:耗子Linux面板 Author:耗子 Date:2023-06-21 //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>加载中...</title>
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="./res/layui/css/layui.css" rel="stylesheet">
|
||||
<script src="https://cdnjs.cdn.haozi.net/ace/1.6.1/ace.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="Panel_app"></div>
|
||||
<script src="./res/layui/layui.js"></script>
|
||||
<script>
|
||||
layui.config({
|
||||
base: 'res/', // 静态资源所在路径
|
||||
version: new Date().getTime()
|
||||
}).use('index', function () {
|
||||
var layer = layui.layer
|
||||
var admin = layui.admin
|
||||
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1576
public/res/adminui/src/css/admin.css
Normal file
1576
public/res/adminui/src/css/admin.css
Normal file
File diff suppressed because it is too large
Load Diff
182
public/res/adminui/src/css/login.css
Normal file
182
public/res/adminui/src/css/login.css
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* admin.login.css
|
||||
*/
|
||||
|
||||
html, body, #Panel_app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layui-layout-body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#LAY-user-login,
|
||||
.layadmin-user-display-show {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.layadmin-user-login {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 110px 0;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.layadmin-user-login-main {
|
||||
width: 375px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.layadmin-user-login-box {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layadmin-user-login-header h2 {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
font-size: 30px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.layadmin-user-login-header p {
|
||||
font-weight: 300;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.layadmin-user-login-body .layui-form-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layadmin-user-login-icon {
|
||||
position: absolute;
|
||||
left: 1px;
|
||||
top: 1px;
|
||||
width: 38px;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
color: #d2d2d2;
|
||||
}
|
||||
|
||||
.layadmin-user-login-body .layui-form-item .layui-input {
|
||||
padding-left: 38px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-codeimg {
|
||||
max-height: 38px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.layadmin-user-login-other {
|
||||
position: relative;
|
||||
font-size: 0;
|
||||
line-height: 38px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-other > * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-other .layui-icon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-other a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.layadmin-user-jump-change {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.layadmin-user-login-footer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
line-height: 30px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
color: rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
.layadmin-user-login-footer span {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-footer a {
|
||||
padding: 0 5px;
|
||||
color: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
.layadmin-user-login-footer a:hover {
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
/* 有背景图时 */
|
||||
.layadmin-user-login-main[bgimg] {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 主题背景 */
|
||||
.ladmin-user-login-theme {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ladmin-user-login-theme ul {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.ladmin-user-login-theme ul li {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 64px;
|
||||
height: 43px;
|
||||
cursor: pointer;
|
||||
transition: all .3s;
|
||||
-webkit-transition: all .3s;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.ladmin-user-login-theme ul li:hover {
|
||||
opacity: 0.9
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.layadmin-user-login {
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-main {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.layadmin-user-login-box {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
869
public/res/adminui/src/modules/admin.js
Normal file
869
public/res/adminui/src/modules/admin.js
Normal file
@@ -0,0 +1,869 @@
|
||||
/**
|
||||
* 界面核心模块
|
||||
*/
|
||||
|
||||
layui.define('view', function (exports) {
|
||||
var $ = layui.jquery
|
||||
, laytpl = layui.laytpl
|
||||
, element = layui.element
|
||||
, table = layui.table
|
||||
, upload = layui.upload
|
||||
, setter = layui.setter
|
||||
, view = layui.view
|
||||
, device = layui.device()
|
||||
|
||||
, $win = $(window), $body = $('body')
|
||||
, container = $('#' + setter.container)
|
||||
|
||||
, SHOW = 'layui-show', HIDE = 'layui-hide', THIS = 'layui-this', DISABLED = 'layui-disabled', TEMP = 'template'
|
||||
, APP_BODY = '#Panel_app_body', APP_FLEXIBLE = 'Panel_app_flexible'
|
||||
, FILTER_TAB_TBAS = 'layadmin-layout-tabs'
|
||||
, APP_SPREAD_SM = 'layadmin-side-spread-sm', TABS_BODY = 'layadmin-tabsbody-item'
|
||||
, ICON_SHRINK = 'layui-icon-shrink-right', ICON_SPREAD = 'layui-icon-spread-left'
|
||||
, SIDE_SHRINK = 'layadmin-side-shrink', SIDE_MENU = 'LAY-system-side-menu'
|
||||
|
||||
//通用方法
|
||||
, admin = {
|
||||
v: '2.0.0'
|
||||
, mode: 'spa'
|
||||
|
||||
//数据的异步请求
|
||||
, req: view.req
|
||||
|
||||
//清除本地 token,并跳转到登入页
|
||||
, exit: view.exit
|
||||
|
||||
//xss 转义
|
||||
, escape: function (html) {
|
||||
return String(html || '').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&')
|
||||
.replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/'/g, ''').replace(/"/g, '"')
|
||||
}
|
||||
|
||||
//事件
|
||||
, on: function (events, callback) {
|
||||
return layui.onevent.call(this, setter.MOD_NAME, events, callback)
|
||||
}
|
||||
|
||||
//弹出面板
|
||||
, popup: view.popup
|
||||
|
||||
//右侧面板
|
||||
, popupRight: function (options) {
|
||||
//layer.close(admin.popup.index);
|
||||
return admin.popup.index = layer.open($.extend({
|
||||
type: 1
|
||||
, id: 'LAY_adminPopupR'
|
||||
, anim: -1
|
||||
, title: false
|
||||
, closeBtn: false
|
||||
, offset: 'r'
|
||||
, shade: 0.1
|
||||
, shadeClose: true
|
||||
, skin: 'layui-anim layui-anim-rl layui-layer-adminRight'
|
||||
, area: '300px'
|
||||
}, options))
|
||||
}
|
||||
|
||||
//发送验证码
|
||||
, sendAuthCode: function (options) {
|
||||
options = $.extend({
|
||||
seconds: 60
|
||||
, elemPhone: '#LAY_phone'
|
||||
, elemVercode: '#LAY_vercode'
|
||||
}, options)
|
||||
|
||||
var seconds = options.seconds
|
||||
, token = null
|
||||
, timer, countDown = function (loop) {
|
||||
var btn = $(options.elem)
|
||||
seconds--
|
||||
if (seconds < 0) {
|
||||
btn.removeClass(DISABLED).html('获取验证码')
|
||||
seconds = options.seconds
|
||||
clearInterval(timer)
|
||||
} else {
|
||||
btn.addClass(DISABLED).html(seconds + '秒后重获')
|
||||
}
|
||||
|
||||
if (!loop) {
|
||||
timer = setInterval(function () {
|
||||
countDown(true)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
$body.off('click', options.elem).on('click', options.elem, function () {
|
||||
options.elemPhone = $(options.elemPhone)
|
||||
options.elemVercode = $(options.elemVercode)
|
||||
|
||||
var elemPhone = options.elemPhone
|
||||
, value = elemPhone.val()
|
||||
|
||||
if (seconds !== options.seconds || $(this).hasClass(DISABLED)) return
|
||||
|
||||
if (!/^1\d{10}$/.test(value)) {
|
||||
elemPhone.focus()
|
||||
return layer.msg('请输入正确的手机号')
|
||||
}
|
||||
|
||||
if (typeof options.ajax === 'object') {
|
||||
var success = options.ajax.success
|
||||
delete options.ajax.success
|
||||
}
|
||||
|
||||
admin.req($.extend(true, {
|
||||
url: '/auth/code'
|
||||
, type: 'get'
|
||||
, data: {
|
||||
phone: value
|
||||
}
|
||||
, success: function (res) {
|
||||
layer.msg('验证码已发送至你的手机,请注意查收', {
|
||||
icon: 1
|
||||
, shade: 0
|
||||
})
|
||||
options.elemVercode.focus()
|
||||
countDown()
|
||||
success && success(res)
|
||||
}
|
||||
}, options.ajax))
|
||||
})
|
||||
}
|
||||
|
||||
//屏幕类型
|
||||
, screen: function () {
|
||||
var width = $win.width()
|
||||
if (width > 1200) {
|
||||
return 3 //大屏幕
|
||||
} else if (width > 992) {
|
||||
return 2 //中屏幕
|
||||
} else if (width > 768) {
|
||||
return 1 //小屏幕
|
||||
} else {
|
||||
return 0 //超小屏幕
|
||||
}
|
||||
}
|
||||
|
||||
//侧边伸缩
|
||||
, sideFlexible: function (status) {
|
||||
var app = container
|
||||
, iconElem = $('#' + APP_FLEXIBLE)
|
||||
, screen = admin.screen()
|
||||
|
||||
//设置状态,PC:默认展开、移动:默认收缩
|
||||
if (status === 'spread') {
|
||||
//切换到展开状态的 icon,箭头:←
|
||||
iconElem.removeClass(ICON_SPREAD).addClass(ICON_SHRINK)
|
||||
|
||||
//移动:从左到右位移;PC:清除多余选择器恢复默认
|
||||
if (screen < 2) {
|
||||
app.addClass(APP_SPREAD_SM)
|
||||
} else {
|
||||
app.removeClass(APP_SPREAD_SM)
|
||||
}
|
||||
|
||||
app.removeClass(SIDE_SHRINK)
|
||||
} else {
|
||||
//切换到搜索状态的 icon,箭头:→
|
||||
iconElem.removeClass(ICON_SHRINK).addClass(ICON_SPREAD)
|
||||
|
||||
//移动:清除多余选择器恢复默认;PC:从右往左收缩
|
||||
if (screen < 2) {
|
||||
app.removeClass(SIDE_SHRINK)
|
||||
} else {
|
||||
app.addClass(SIDE_SHRINK)
|
||||
}
|
||||
|
||||
app.removeClass(APP_SPREAD_SM)
|
||||
}
|
||||
|
||||
layui.event.call(this, setter.MOD_NAME, 'side({*})', {
|
||||
status: status
|
||||
})
|
||||
}
|
||||
|
||||
//重置主体区域表格尺寸
|
||||
, resizeTable: function (delay) {
|
||||
var that = this, runResizeTable = function () {
|
||||
that.tabsBody(admin.tabsPage.index).find('.layui-table-view').each(function () {
|
||||
var tableID = $(this).attr('lay-id')
|
||||
layui.table.resize(tableID)
|
||||
})
|
||||
}
|
||||
if (!layui.table) return
|
||||
delay ? setTimeout(runResizeTable, delay) : runResizeTable()
|
||||
}
|
||||
|
||||
//主题设置
|
||||
, theme: function (options) {
|
||||
var theme = setter.theme
|
||||
, local = layui.data(setter.tableName)
|
||||
, id = 'LAY_layadmin_theme'
|
||||
, style = document.createElement('style')
|
||||
, styleText = laytpl([
|
||||
//主题色
|
||||
'.layui-side-menu,'
|
||||
, '.layui-layer-admin .layui-layer-title,'
|
||||
, '.layadmin-side-shrink .layui-side-menu .layui-nav>.layui-nav-item>.layui-nav-child'
|
||||
, '{background-color:{{d.color.main}} !important;}'
|
||||
|
||||
//背景选中色
|
||||
, '.layadmin-pagetabs .layui-tab-title li:after,'
|
||||
, '.layadmin-pagetabs .layui-tab-title li.layui-this:after,'
|
||||
, '.layui-nav-tree .layui-this,'
|
||||
, '.layui-nav-tree .layui-this>a,'
|
||||
, '.layui-nav-tree .layui-nav-child dd.layui-this,'
|
||||
, '.layui-nav-tree .layui-nav-child dd.layui-this a,'
|
||||
, '.layui-nav-tree .layui-nav-bar'
|
||||
, '{background-color:{{d.color.selected}} !important;}'
|
||||
|
||||
//logo
|
||||
, '.layui-layout-admin .layui-logo{background-color:{{d.color.logo || d.color.main}} !important;}'
|
||||
|
||||
//文字选中色
|
||||
, '.layadmin-pagetabs .layui-tab-title li:hover,'
|
||||
, '.layadmin-pagetabs .layui-tab-title li.layui-this'
|
||||
, '{color: {{d.color.selected}} !important;}'
|
||||
|
||||
//头部色
|
||||
, '{{# if(d.color.header){ }}'
|
||||
, '.layui-layout-admin .layui-header{background-color:{{ d.color.header }};}'
|
||||
, '.layui-layout-admin .layui-header a,'
|
||||
, '.layui-layout-admin .layui-header a cite{color: #f8f8f8;}'
|
||||
, '.layui-layout-admin .layui-header a:hover{color: #fff;}'
|
||||
, '.layui-layout-admin .layui-header .layui-nav .layui-nav-more{border-top-color: #fbfbfb;}'
|
||||
, '.layui-layout-admin .layui-header .layui-nav .layui-nav-mored{border-color: transparent; border-bottom-color: #fbfbfb;}'
|
||||
, '.layui-layout-admin .layui-header .layui-nav .layui-this:after, .layui-layout-admin .layui-header .layui-nav-bar{background-color: #fff; background-color: rgba(255,255,255,.5);}'
|
||||
, '.layadmin-pagetabs .layui-tab-title li:after{display: none;}'
|
||||
, '{{# } }}'
|
||||
].join('')).render(options = $.extend({}, local.theme, options))
|
||||
, styleElem = document.getElementById(id)
|
||||
|
||||
//添加主题样式
|
||||
if ('styleSheet' in style) {
|
||||
style.setAttribute('type', 'text/css')
|
||||
style.styleSheet.cssText = styleText
|
||||
} else {
|
||||
style.innerHTML = styleText
|
||||
}
|
||||
style.id = id
|
||||
|
||||
styleElem && $body[0].removeChild(styleElem)
|
||||
$body[0].appendChild(style)
|
||||
$body.attr('layadmin-themealias', options.color.alias)
|
||||
|
||||
//本地存储记录
|
||||
local.theme = local.theme || {}
|
||||
layui.each(options, function (key, value) {
|
||||
local.theme[key] = value
|
||||
})
|
||||
layui.data(setter.tableName, {
|
||||
key: 'theme'
|
||||
, value: local.theme
|
||||
})
|
||||
}
|
||||
|
||||
//初始化主题
|
||||
, initTheme: function (index) {
|
||||
var theme = setter.theme
|
||||
index = index || 0
|
||||
if (theme.color[index]) {
|
||||
theme.color[index].index = index
|
||||
admin.theme({
|
||||
color: theme.color[index]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//记录最近一次点击的页面标签数据
|
||||
, tabsPage: {}
|
||||
|
||||
//获取标签页的头元素
|
||||
, tabsHeader: function (index) {
|
||||
return $('#Panel_app_tabsheader').children('li').eq(index || 0)
|
||||
}
|
||||
|
||||
//获取页面标签主体元素
|
||||
, tabsBody: function (index) {
|
||||
return $(APP_BODY).find('.' + TABS_BODY).eq(index || 0)
|
||||
}
|
||||
|
||||
//切换页面标签主体
|
||||
, tabsBodyChange: function (index) {
|
||||
admin.tabsHeader(index).attr('lay-attr', layui.router().href)
|
||||
admin.tabsBody(index).addClass(SHOW).siblings().removeClass(SHOW)
|
||||
events.rollPage('auto', index)
|
||||
}
|
||||
|
||||
//resize事件管理
|
||||
, resize: function (fn) {
|
||||
var router = layui.router()
|
||||
, key = router.path.join('-')
|
||||
|
||||
if (admin.resizeFn[key]) {
|
||||
$win.off('resize', admin.resizeFn[key])
|
||||
delete admin.resizeFn[key]
|
||||
}
|
||||
|
||||
if (fn === 'off') return //如果是清除 resize 事件,则终止往下执行
|
||||
|
||||
fn(), admin.resizeFn[key] = fn
|
||||
$win.on('resize', admin.resizeFn[key])
|
||||
}
|
||||
, resizeFn: {}
|
||||
, runResize: function () {
|
||||
var router = layui.router()
|
||||
, key = router.path.join('-')
|
||||
admin.resizeFn[key] && admin.resizeFn[key]()
|
||||
}
|
||||
, delResize: function () {
|
||||
this.resize('off')
|
||||
}
|
||||
|
||||
//关闭当前 pageTabs
|
||||
, closeThisTabs: function () {
|
||||
if (!admin.tabsPage.index) return
|
||||
$(TABS_HEADER).eq(admin.tabsPage.index).find('.layui-tab-close').trigger('click')
|
||||
}
|
||||
|
||||
//全屏
|
||||
, fullScreen: function () {
|
||||
var ele = document.documentElement
|
||||
, reqFullScreen = ele.requestFullScreen || ele.webkitRequestFullScreen
|
||||
|| ele.mozRequestFullScreen || ele.msRequestFullscreen
|
||||
if (typeof reqFullScreen !== 'undefined' && reqFullScreen) {
|
||||
reqFullScreen.call(ele)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//退出全屏
|
||||
, exitScreen: function () {
|
||||
var ele = document.documentElement
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
document.webkitCancelFullScreen()
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
//纠正单页路由格式
|
||||
, correctRouter: function (href) {
|
||||
if (!/^\//.test(href)) href = '/' + href
|
||||
|
||||
//纠正首尾
|
||||
return href.replace(/^(\/+)/, '/')
|
||||
.replace(new RegExp('\/' + setter.entry + '$'), '/') //过滤路由最后的默认视图文件名(如:index)
|
||||
}
|
||||
|
||||
//……
|
||||
}
|
||||
|
||||
//事件
|
||||
var events = admin.events = {
|
||||
//伸缩
|
||||
flexible: function (othis) {
|
||||
var iconElem = othis.find('#' + APP_FLEXIBLE)
|
||||
, isSpread = iconElem.hasClass(ICON_SPREAD)
|
||||
admin.sideFlexible(isSpread ? 'spread' : null) //控制伸缩
|
||||
admin.resizeTable(350)
|
||||
}
|
||||
|
||||
//刷新
|
||||
, refresh: function () {
|
||||
admin.render()
|
||||
}
|
||||
|
||||
//输入框搜索
|
||||
, serach: function (othis) {
|
||||
othis.off('keypress').on('keypress', function (e) {
|
||||
if (!this.value.replace(/\s/g, '')) return
|
||||
//回车跳转
|
||||
if (e.keyCode === 13) {
|
||||
var href = othis.attr('lay-action')
|
||||
, text = othis.attr('lay-text') || '搜索'
|
||||
|
||||
href = href + this.value
|
||||
text = text + ' <span style="color: #FF5722;">' + admin.escape(this.value) + '</span>'
|
||||
|
||||
//打开标签页
|
||||
location.hash = admin.correctRouter(href)
|
||||
|
||||
//如果搜索关键词已经打开,则刷新页面即可
|
||||
events.serach.keys || (events.serach.keys = {})
|
||||
events.serach.keys[admin.tabsPage.index] = this.value
|
||||
if (this.value === events.serach.keys[admin.tabsPage.index]) {
|
||||
events.refresh(othis)
|
||||
}
|
||||
|
||||
//清空输入框
|
||||
this.value = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//点击消息
|
||||
, message: function (othis) {
|
||||
othis.find('.layui-badge-dot').remove()
|
||||
}
|
||||
|
||||
//弹出主题面板
|
||||
, theme: function () {
|
||||
admin.popupRight({
|
||||
id: 'LAY_adminPopupTheme'
|
||||
, success: function () {
|
||||
view(this.id).render('system/theme')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//便签
|
||||
, note: function (othis) {
|
||||
var mobile = admin.screen() < 2
|
||||
, note = layui.data(setter.tableName).note
|
||||
|
||||
events.note.index = admin.popup({
|
||||
title: '便签'
|
||||
, shade: 0
|
||||
, offset: [
|
||||
'41px'
|
||||
, (mobile ? null : (othis.offset().left - 250) + 'px')
|
||||
]
|
||||
, anim: -1
|
||||
, id: 'LAY_adminNote'
|
||||
, skin: 'layadmin-note layui-anim layui-anim-upbit'
|
||||
, content: '<textarea placeholder="内容"></textarea>'
|
||||
, resize: false
|
||||
, success: function (layero, index) {
|
||||
var textarea = layero.find('textarea')
|
||||
,
|
||||
value = note === undefined ? '便签中的内容会存储在本地,这样即便你关掉了浏览器,在下次打开时,依然会读取到上一次的记录。是个非常小巧实用的本地备忘录' : note
|
||||
|
||||
textarea.val(value).focus().on('keyup', function () {
|
||||
layui.data(setter.tableName, {
|
||||
key: 'note'
|
||||
, value: this.value
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//全屏
|
||||
, fullscreen: function (othis) {
|
||||
var SCREEN_FULL = 'layui-icon-screen-full'
|
||||
, SCREEN_REST = 'layui-icon-screen-restore'
|
||||
, iconElem = othis.children('i')
|
||||
|
||||
if (iconElem.hasClass(SCREEN_FULL)) {
|
||||
admin.fullScreen()
|
||||
iconElem.addClass(SCREEN_REST).removeClass(SCREEN_FULL)
|
||||
} else {
|
||||
admin.exitScreen()
|
||||
iconElem.addClass(SCREEN_FULL).removeClass(SCREEN_REST)
|
||||
}
|
||||
}
|
||||
|
||||
//弹出关于面板
|
||||
, about: function () {
|
||||
admin.popupRight({
|
||||
id: 'LAY_adminPopupAbout'
|
||||
, success: function () {
|
||||
view(this.id).render('system/about')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//弹出更多面板
|
||||
, more: function () {
|
||||
admin.popupRight({
|
||||
id: 'LAY_adminPopupMore'
|
||||
, success: function () {
|
||||
view(this.id).render('system/more')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//返回上一页
|
||||
, back: function () {
|
||||
history.back()
|
||||
}
|
||||
|
||||
//主题设置
|
||||
, setTheme: function (othis) {
|
||||
var index = othis.data('index')
|
||||
, nextIndex = othis.siblings('.layui-this').data('index')
|
||||
|
||||
if (othis.hasClass(THIS)) return
|
||||
|
||||
othis.addClass(THIS).siblings('.layui-this').removeClass(THIS)
|
||||
admin.initTheme(index)
|
||||
}
|
||||
|
||||
//左右滚动页面标签
|
||||
, rollPage: function (type, index) {
|
||||
var tabsHeader = $('#Panel_app_tabsheader')
|
||||
, liItem = tabsHeader.children('li')
|
||||
, scrollWidth = tabsHeader.prop('scrollWidth')
|
||||
, outerWidth = tabsHeader.outerWidth()
|
||||
, tabsLeft = parseFloat(tabsHeader.css('left'))
|
||||
|
||||
//右左往右
|
||||
if (type === 'left') {
|
||||
if (!tabsLeft && tabsLeft <= 0) return
|
||||
|
||||
//当前的left减去可视宽度,用于与上一轮的页标比较
|
||||
var prefLeft = -tabsLeft - outerWidth
|
||||
|
||||
liItem.each(function (index, item) {
|
||||
var li = $(item)
|
||||
, left = li.position().left
|
||||
|
||||
if (left >= prefLeft) {
|
||||
tabsHeader.css('left', -left)
|
||||
return false
|
||||
}
|
||||
})
|
||||
} else if (type === 'auto') { //自动滚动
|
||||
(function () {
|
||||
var thisLi = liItem.eq(index), thisLeft
|
||||
|
||||
if (!thisLi[0]) return
|
||||
thisLeft = thisLi.position().left
|
||||
|
||||
//当目标标签在可视区域左侧时
|
||||
if (thisLeft < -tabsLeft) {
|
||||
return tabsHeader.css('left', -thisLeft)
|
||||
}
|
||||
|
||||
//当目标标签在可视区域右侧时
|
||||
if (thisLeft + thisLi.outerWidth() >= outerWidth - tabsLeft) {
|
||||
var subLeft = thisLeft + thisLi.outerWidth() - (outerWidth - tabsLeft)
|
||||
liItem.each(function (i, item) {
|
||||
var li = $(item)
|
||||
, left = li.position().left
|
||||
|
||||
//从当前可视区域的最左第二个节点遍历,如果减去最左节点的差 > 目标在右侧不可见的宽度,则将该节点放置可视区域最左
|
||||
if (left + tabsLeft > 0) {
|
||||
if (left - tabsLeft > subLeft) {
|
||||
tabsHeader.css('left', -left)
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}())
|
||||
} else {
|
||||
//默认向左滚动
|
||||
liItem.each(function (i, item) {
|
||||
var li = $(item)
|
||||
, left = li.position().left
|
||||
|
||||
if (left + li.outerWidth() >= outerWidth - tabsLeft) {
|
||||
tabsHeader.css('left', -left)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//向右滚动页面标签
|
||||
, leftPage: function () {
|
||||
events.rollPage('left')
|
||||
}
|
||||
|
||||
//向左滚动页面标签
|
||||
, rightPage: function () {
|
||||
events.rollPage()
|
||||
}
|
||||
|
||||
//关闭当前标签页
|
||||
, closeThisTabs: function () {
|
||||
admin.closeThisTabs()
|
||||
}
|
||||
|
||||
//关闭其它标签页
|
||||
, closeOtherTabs: function (type) {
|
||||
var TABS_REMOVE = 'LAY-system-pagetabs-remove'
|
||||
if (type === 'all') {
|
||||
$(TABS_HEADER + ':gt(0)').remove()
|
||||
$(APP_BODY).find('.' + TABS_BODY + ':gt(0)').remove()
|
||||
} else {
|
||||
$(TABS_HEADER).each(function (index, item) {
|
||||
if (index && index != admin.tabsPage.index) {
|
||||
$(item).addClass(TABS_REMOVE)
|
||||
admin.tabsBody(index).addClass(TABS_REMOVE)
|
||||
}
|
||||
})
|
||||
$('.' + TABS_REMOVE).remove()
|
||||
}
|
||||
}
|
||||
|
||||
//关闭全部标签页
|
||||
, closeAllTabs: function () {
|
||||
events.closeOtherTabs('all')
|
||||
location.hash = ''
|
||||
}
|
||||
|
||||
//遮罩
|
||||
, shade: function () {
|
||||
admin.sideFlexible()
|
||||
}
|
||||
}
|
||||
|
||||
//初始
|
||||
!function () {
|
||||
//主题初始化,本地主题记录优先,其次为 initColorIndex
|
||||
var local = layui.data(setter.tableName)
|
||||
if (local.theme) {
|
||||
admin.theme(local.theme)
|
||||
} else if (setter.theme) {
|
||||
admin.initTheme(setter.theme.initColorIndex)
|
||||
}
|
||||
|
||||
//禁止水平滚动
|
||||
$body.addClass('layui-layout-body')
|
||||
|
||||
//移动端强制不开启页面标签功能
|
||||
if (admin.screen() < 1) {
|
||||
delete setter.pageTabs
|
||||
}
|
||||
|
||||
//不开启页面标签时
|
||||
if (!setter.pageTabs) {
|
||||
container.addClass('layadmin-tabspage-none')
|
||||
}
|
||||
|
||||
//低版本IE提示
|
||||
if (device.ie && device.ie < 10) {
|
||||
view.error('IE' + device.ie + '下访问可能不佳,推荐使用:Chrome / Firefox / Edge 等高级浏览器', {
|
||||
offset: 'auto'
|
||||
, id: 'LAY_errorIE'
|
||||
})
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
//admin.prevRouter = {}; //上一个路由
|
||||
|
||||
// hash 改变侧边状态
|
||||
admin.on('hash(side)', function (router) {
|
||||
var path = router.path, getData = function (item) {
|
||||
return {
|
||||
list: item.children('.layui-nav-child')
|
||||
, name: item.data('name')
|
||||
, jump: item.data('jump')
|
||||
}
|
||||
}
|
||||
, sideMenu = $('#' + SIDE_MENU)
|
||||
, SIDE_NAV_ITEMD = 'layui-nav-itemed'
|
||||
|
||||
//捕获对应菜单
|
||||
, matchMenu = function (list) {
|
||||
var pathURL = admin.correctRouter(path.join('/'))
|
||||
list.each(function (index1, item1) {
|
||||
var othis1 = $(item1)
|
||||
, data1 = getData(othis1)
|
||||
, listChildren1 = data1.list.children('dd')
|
||||
, matched1 = path[0] == data1.name || (index1 === 0 && !path[0])
|
||||
|| (data1.jump && pathURL == admin.correctRouter(data1.jump))
|
||||
|
||||
listChildren1.each(function (index2, item2) {
|
||||
var othis2 = $(item2)
|
||||
, data2 = getData(othis2)
|
||||
, listChildren2 = data2.list.children('dd')
|
||||
, matched2 = (path[0] == data1.name && path[1] == data2.name)
|
||||
|| (data2.jump && pathURL == admin.correctRouter(data2.jump))
|
||||
|
||||
listChildren2.each(function (index3, item3) {
|
||||
var othis3 = $(item3)
|
||||
, data3 = getData(othis3)
|
||||
, matched3 = (path[0] == data1.name && path[1] == data2.name && path[2] == data3.name)
|
||||
|| (data3.jump && pathURL == admin.correctRouter(data3.jump))
|
||||
|
||||
if (matched3) {
|
||||
var selected = data3.list[0] ? SIDE_NAV_ITEMD : THIS
|
||||
othis3.addClass(selected).siblings().removeClass(selected) //标记选择器
|
||||
return false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if (matched2) {
|
||||
var selected = data2.list[0] ? SIDE_NAV_ITEMD : THIS
|
||||
othis2.addClass(selected).siblings().removeClass(selected) //标记选择器
|
||||
return false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if (matched1) {
|
||||
var selected = data1.list[0] ? SIDE_NAV_ITEMD : THIS
|
||||
othis1.addClass(selected).siblings().removeClass(selected) //标记选择器
|
||||
return false
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
//重置状态
|
||||
sideMenu.find('.' + THIS).removeClass(THIS)
|
||||
|
||||
//移动端点击菜单时自动收缩
|
||||
if (admin.screen() < 2) admin.sideFlexible()
|
||||
|
||||
//开始捕获
|
||||
matchMenu(sideMenu.children('li'))
|
||||
})
|
||||
|
||||
//侧边导航点击事件
|
||||
element.on('nav(layadmin-system-side-menu)', function (elem) {
|
||||
if (elem.siblings('.layui-nav-child')[0] && container.hasClass(SIDE_SHRINK)) {
|
||||
admin.sideFlexible('spread')
|
||||
layer.close(elem.data('index'))
|
||||
}
|
||||
|
||||
admin.tabsPage.type = 'nav'
|
||||
})
|
||||
|
||||
//选项卡的更多操作
|
||||
element.on('nav(layadmin-pagetabs-nav)', function (elem) {
|
||||
var dd = elem.parent()
|
||||
dd.removeClass(THIS)
|
||||
dd.parent().removeClass(SHOW)
|
||||
})
|
||||
|
||||
//同步路由
|
||||
var setThisRouter = function (othis) {
|
||||
var layid = othis.attr('lay-id')
|
||||
, attr = othis.attr('lay-attr')
|
||||
, index = othis.index()
|
||||
|
||||
location.hash = layid === setter.entry ? '/' : (attr || '/')
|
||||
admin.tabsBodyChange(index)
|
||||
}
|
||||
, TABS_HEADER = '#Panel_app_tabsheader>li'
|
||||
|
||||
//页面标签点击
|
||||
$body.on('click', TABS_HEADER, function () {
|
||||
var othis = $(this)
|
||||
, index = othis.index()
|
||||
|
||||
admin.tabsPage.type = 'tab'
|
||||
admin.tabsPage.index = index
|
||||
|
||||
//如果是iframe类型的标签页
|
||||
if (othis.attr('lay-attr') === 'iframe') {
|
||||
return admin.tabsBodyChange(index)
|
||||
}
|
||||
|
||||
setThisRouter(othis) //同步路由
|
||||
admin.runResize() //执行resize事件,如果存在的话
|
||||
admin.resizeTable() //重置当前主体区域的表格尺寸
|
||||
})
|
||||
|
||||
// tabspage 删除
|
||||
element.on('tabDelete(layadmin-layout-tabs)', function (obj) {
|
||||
var othis = $(TABS_HEADER + '.layui-this')
|
||||
|
||||
obj.index && admin.tabsBody(obj.index).remove()
|
||||
setThisRouter(othis)
|
||||
|
||||
//移除resize事件
|
||||
admin.delResize()
|
||||
})
|
||||
|
||||
// 页面跳转
|
||||
$body.on('click', '*[lay-href]', function () {
|
||||
var othis = $(this)
|
||||
var href = othis.attr('lay-href')
|
||||
var router = layui.router()
|
||||
|
||||
admin.tabsPage.elem = othis
|
||||
// admin.prevRouter[router.path[0]] = router.href; //记录上一次各菜单的路由信息
|
||||
|
||||
// 执行跳转
|
||||
location.hash = admin.correctRouter(href)
|
||||
|
||||
// 如果为当前页,则执行刷新
|
||||
if (setter.refreshCurrPage) {
|
||||
if (admin.correctRouter(href) === router.href) {
|
||||
admin.events.refresh()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//点击事件
|
||||
$body.on('click', '*[layadmin-event]', function () {
|
||||
var othis = $(this)
|
||||
, attrEvent = othis.attr('layadmin-event')
|
||||
events[attrEvent] && events[attrEvent].call(this, othis)
|
||||
})
|
||||
|
||||
//tips
|
||||
$body.on('mouseenter', '*[lay-tips]', function () {
|
||||
var othis = $(this)
|
||||
|
||||
if (othis.parent().hasClass('layui-nav-item') && !container.hasClass(SIDE_SHRINK)) return
|
||||
|
||||
var tips = othis.attr('lay-tips')
|
||||
, offset = othis.attr('lay-offset')
|
||||
, direction = othis.attr('lay-direction')
|
||||
, index = layer.tips(tips, this, {
|
||||
tips: direction || 1
|
||||
, time: -1
|
||||
, success: function (layero, index) {
|
||||
if (offset) {
|
||||
layero.css('margin-left', offset + 'px')
|
||||
}
|
||||
}
|
||||
})
|
||||
othis.data('index', index)
|
||||
}).on('mouseleave', '*[lay-tips]', function () {
|
||||
layer.close($(this).data('index'))
|
||||
})
|
||||
|
||||
//窗口resize事件
|
||||
var resizeSystem = layui.data.resizeSystem = function () {
|
||||
//layer.close(events.note.index);
|
||||
layer.closeAll('tips')
|
||||
|
||||
if (!resizeSystem.lock) {
|
||||
setTimeout(function () {
|
||||
admin.sideFlexible(admin.screen() < 2 ? '' : 'spread')
|
||||
delete resizeSystem.lock
|
||||
}, 100)
|
||||
}
|
||||
|
||||
resizeSystem.lock = true
|
||||
}
|
||||
$win.on('resize', layui.data.resizeSystem)
|
||||
|
||||
//设置组件全局 token
|
||||
!function () {
|
||||
var request = setter.request
|
||||
if (request.tokenName) {
|
||||
var obj = {}
|
||||
obj[request.tokenName] = layui.data(setter.tableName)[request.tokenName] || ''
|
||||
|
||||
//table
|
||||
table.set({
|
||||
headers: obj, //通过 request 头传递
|
||||
where: obj //通过参数传递
|
||||
})
|
||||
//upload
|
||||
upload.set({
|
||||
headers: obj, //通过 request 头传递
|
||||
data: obj //通过参数传递
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
//接口输出
|
||||
exports('admin', admin)
|
||||
})
|
||||
198
public/res/adminui/src/modules/index.js
Normal file
198
public/res/adminui/src/modules/index.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* 界面入口模块
|
||||
*/
|
||||
|
||||
layui.define('admin', function (exports) {
|
||||
var setter = layui.setter
|
||||
var element = layui.element
|
||||
var admin = layui.admin
|
||||
var tabsPage = admin.tabsPage
|
||||
var view = layui.view
|
||||
|
||||
//根据路由渲染页面
|
||||
var renderPage = function () {
|
||||
var router = layui.router()
|
||||
, path = router.path
|
||||
, pathURL = admin.correctRouter(router.path.join('/'))
|
||||
|
||||
//默认读取主页
|
||||
if (!path.length) path = ['']
|
||||
|
||||
//如果最后一项为空字符,则读取默认文件
|
||||
if (path[path.length - 1] === '') {
|
||||
path[path.length - 1] = setter.entry
|
||||
}
|
||||
|
||||
//重置状态
|
||||
var reset = function (type) {
|
||||
//renderPage.haveInit && layer.closeAll();
|
||||
if (renderPage.haveInit) {
|
||||
$('.layui-layer').each(function () {
|
||||
var othis = $(this),
|
||||
index = othis.attr('times')
|
||||
if (!(othis.hasClass('layui-layim') || othis.hasClass('layui-layim-chat'))) {
|
||||
layer.close(index)
|
||||
}
|
||||
})
|
||||
}
|
||||
renderPage.haveInit = true
|
||||
|
||||
$(APP_BODY).scrollTop(0)
|
||||
delete tabsPage.type //重置页面标签的来源类型
|
||||
}
|
||||
|
||||
//如果路由来自于 tab 切换,则不重新请求视图
|
||||
if (tabsPage.type === 'tab') {
|
||||
//切换到非主页、或者切换到主页且主页必须有内容。方可阻止请求
|
||||
if (pathURL !== '/' || (pathURL === '/' && admin.tabsBody().html())) {
|
||||
admin.tabsBodyChange(tabsPage.index)
|
||||
return reset(tabsPage.type)
|
||||
}
|
||||
}
|
||||
|
||||
//请求视图渲染
|
||||
view().render(path.join('/')).then(function (res) {
|
||||
|
||||
//遍历页签选项卡
|
||||
var matchTo
|
||||
, tabs = $('#Panel_app_tabsheader>li')
|
||||
|
||||
tabs.each(function (index) {
|
||||
var li = $(this)
|
||||
, layid = li.attr('lay-id')
|
||||
|
||||
if (layid === pathURL) {
|
||||
matchTo = true
|
||||
tabsPage.index = index
|
||||
}
|
||||
})
|
||||
|
||||
//如果未在选项卡中匹配到,则追加选项卡
|
||||
if (setter.pageTabs && pathURL !== '/') {
|
||||
if (!matchTo) {
|
||||
$(APP_BODY).append('<div class="layadmin-tabsbody-item layui-show"></div>')
|
||||
tabsPage.index = tabs.length
|
||||
element.tabAdd(FILTER_TAB_TBAS, {
|
||||
title: '<span>' + (res.title || '新标签页') + '</span>'
|
||||
, id: pathURL
|
||||
, attr: router.href
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.container = admin.tabsBody(tabsPage.index)
|
||||
setter.pageTabs || this.container.scrollTop(0) //如果不开启标签页,则跳转时重置滚动条
|
||||
|
||||
//定位当前tabs
|
||||
element.tabChange(FILTER_TAB_TBAS, pathURL)
|
||||
admin.tabsBodyChange(tabsPage.index)
|
||||
|
||||
}).done(function () {
|
||||
layui.use('common', layui.cache.callback.common)
|
||||
$win.on('resize', layui.data.resize)
|
||||
|
||||
element.render('breadcrumb', 'breadcrumb')
|
||||
|
||||
//容器 scroll 事件,剔除吸附层
|
||||
admin.tabsBody(tabsPage.index).on('scroll', function () {
|
||||
var othis = $(this)
|
||||
, elemDate = $('.layui-laydate')
|
||||
, layerOpen = $('.layui-layer')[0]
|
||||
|
||||
//关闭 layDate
|
||||
if (elemDate[0]) {
|
||||
elemDate.each(function () {
|
||||
var thisElemDate = $(this)
|
||||
thisElemDate.hasClass('layui-laydate-static') || thisElemDate.remove()
|
||||
})
|
||||
othis.find('input').blur()
|
||||
}
|
||||
|
||||
//关闭 Tips 层
|
||||
layerOpen && layer.closeAll('tips')
|
||||
})
|
||||
})
|
||||
|
||||
reset()
|
||||
}
|
||||
|
||||
//入口页面
|
||||
var entryPage = function (fn) {
|
||||
var router = layui.router()
|
||||
, container = view(setter.container)
|
||||
, pathURL = admin.correctRouter(router.path.join('/'))
|
||||
, isIndPage
|
||||
|
||||
//检查是否属于独立页面
|
||||
layui.each(setter.indPage, function (index, item) {
|
||||
if (pathURL === item) {
|
||||
return isIndPage = true
|
||||
}
|
||||
})
|
||||
|
||||
//将模块根路径设置为 modules 目录
|
||||
layui.config({
|
||||
base: setter.paths.base + 'modules/'
|
||||
})
|
||||
|
||||
//独立页面
|
||||
if (isIndPage || pathURL === '/user/login') { //此处单独判断登入页,是为了兼容旧版(即未在 config.js 配置 indPage 的情况)
|
||||
container.render(router.path.join('/')).done(function () {
|
||||
admin.pageType = 'alone'
|
||||
})
|
||||
} else { //后台框架页面
|
||||
|
||||
//强制拦截未登入
|
||||
if (setter.interceptor) {
|
||||
var local = layui.data(setter.tableName)
|
||||
if (!local[setter.request.tokenName]) {
|
||||
return location.hash = '/user/login/redirect=' + encodeURIComponent(pathURL) //跳转到登入页
|
||||
}
|
||||
}
|
||||
|
||||
//渲染后台结构
|
||||
if (admin.pageType === 'console') { //后台主体页
|
||||
renderPage()
|
||||
} else { //初始控制台结构
|
||||
container.render('layout').done(function () {
|
||||
renderPage()
|
||||
layui.element.render()
|
||||
|
||||
if (admin.screen() < 2) {
|
||||
admin.sideFlexible()
|
||||
}
|
||||
admin.pageType = 'console'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var APP_BODY = '#Panel_app_body'
|
||||
var FILTER_TAB_TBAS = 'layadmin-layout-tabs'
|
||||
var $ = layui.$, $win = $(window)
|
||||
|
||||
//初始主体结构
|
||||
layui.link(
|
||||
setter.paths.core + 'css/admin.css?v=' + admin.v
|
||||
, function () {
|
||||
entryPage()
|
||||
}
|
||||
, 'layuiAdmin'
|
||||
)
|
||||
|
||||
//Hash改变
|
||||
window.onhashchange = function () {
|
||||
entryPage()
|
||||
//执行 {setter.MOD_NAME}.hash 下的事件
|
||||
layui.event.call(this, setter.MOD_NAME, 'hash({*})', layui.router())
|
||||
}
|
||||
|
||||
// 对外输出
|
||||
var adminuiIndex = {
|
||||
render: renderPage
|
||||
}
|
||||
|
||||
$.extend(admin, adminuiIndex)
|
||||
exports('adminIndex', adminuiIndex)
|
||||
})
|
||||
346
public/res/adminui/src/modules/view.js
Normal file
346
public/res/adminui/src/modules/view.js
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* 界面视图模块
|
||||
*/
|
||||
|
||||
layui.define(['laytpl', 'layer'], function (exports) {
|
||||
var $ = layui.jquery
|
||||
, laytpl = layui.laytpl
|
||||
, layer = layui.layer
|
||||
, setter = layui.setter
|
||||
, device = layui.device()
|
||||
, hint = layui.hint()
|
||||
|
||||
//对外接口
|
||||
, view = function (id) {
|
||||
return new Class(id)
|
||||
}
|
||||
|
||||
, SHOW = 'layui-show', LAY_BODY = 'Panel_app_body'
|
||||
|
||||
//构造器
|
||||
, Class = function (id) {
|
||||
this.id = id
|
||||
this.container = $('#' + (id || LAY_BODY))
|
||||
}
|
||||
|
||||
//加载中
|
||||
view.loading = function (elem) {
|
||||
elem.append(
|
||||
this.elemLoad = $('<i class="layui-anim layui-anim-rotate layui-anim-loop layui-icon layui-icon-loading layadmin-loading"></i>')
|
||||
)
|
||||
}
|
||||
|
||||
//移除加载
|
||||
view.removeLoad = function () {
|
||||
this.elemLoad && this.elemLoad.remove()
|
||||
}
|
||||
|
||||
//清除 token,并跳转到登入页
|
||||
view.exit = function () {
|
||||
//清空本地记录的 token
|
||||
layui.data(setter.tableName, {
|
||||
key: setter.request.tokenName
|
||||
, remove: true
|
||||
})
|
||||
|
||||
//跳转到登入页
|
||||
location.hash = '/user/login'
|
||||
}
|
||||
|
||||
//Ajax请求
|
||||
view.req = function (options) {
|
||||
var that = this
|
||||
, success = options.success
|
||||
, error = options.error
|
||||
, request = setter.request
|
||||
, response = setter.response
|
||||
, debug = function () {
|
||||
return setter.debug
|
||||
? '<br><cite>URL:</cite>' + options.url
|
||||
: ''
|
||||
}
|
||||
|
||||
options.data = options.data || {}
|
||||
options.headers = options.headers || {}
|
||||
|
||||
if (request.tokenName) {
|
||||
var sendData = typeof options.data === 'string'
|
||||
? JSON.parse(options.data)
|
||||
: options.data
|
||||
|
||||
//自动给参数传入默认 token
|
||||
options.data[request.tokenName] = request.tokenName in sendData
|
||||
? options.data[request.tokenName]
|
||||
: (layui.data(setter.tableName)[request.tokenName] || '')
|
||||
|
||||
//自动给 Request Headers 传入 token
|
||||
options.headers[request.tokenName] = request.tokenName in options.headers
|
||||
? options.headers[request.tokenName]
|
||||
: (layui.data(setter.tableName)[request.tokenName] || '')
|
||||
}
|
||||
|
||||
delete options.success
|
||||
delete options.error
|
||||
|
||||
return $.ajax($.extend({
|
||||
type: 'get'
|
||||
, dataType: 'json'
|
||||
, success: function (res) {
|
||||
var statusCode = response.statusCode
|
||||
|
||||
//只有 response 的 code 一切正常才执行 done
|
||||
if (res[response.statusName] == statusCode.ok) {
|
||||
typeof options.done === 'function' && options.done(res)
|
||||
}
|
||||
|
||||
//登录状态失效,清除本地 access_token,并强制跳转到登入页
|
||||
else if (res[response.statusName] == statusCode.logout) {
|
||||
view.exit()
|
||||
}
|
||||
|
||||
//其它异常
|
||||
else {
|
||||
var errorText = [
|
||||
'<cite>Error:</cite> ' + (res[response.msgName] || '返回状态码异常')
|
||||
, debug()
|
||||
].join('')
|
||||
view.error(errorText)
|
||||
}
|
||||
|
||||
//只要 http 状态码正常,无论 response 的 code 是否正常都执行 success
|
||||
typeof success === 'function' && success(res)
|
||||
}
|
||||
, error: function (e, code) {
|
||||
var errorText = [
|
||||
'请求异常,请重试<br><cite>错误信息:</cite>' + code
|
||||
, debug()
|
||||
].join('')
|
||||
view.error(errorText)
|
||||
|
||||
typeof error === 'function' && error.apply(this, arguments)
|
||||
}
|
||||
}, options))
|
||||
}
|
||||
|
||||
//弹窗
|
||||
view.popup = function (options) {
|
||||
var success = options.success
|
||||
, skin = options.skin
|
||||
|
||||
delete options.success
|
||||
delete options.skin
|
||||
|
||||
return layer.open($.extend({
|
||||
type: 1
|
||||
, title: '提示'
|
||||
, content: ''
|
||||
, id: 'LAY-system-view-popup'
|
||||
, skin: 'layui-layer-admin' + (skin ? ' ' + skin : '')
|
||||
, shadeClose: true
|
||||
, closeBtn: false
|
||||
, success: function (layero, index) {
|
||||
var elemClose = $('<i class="layui-icon" close>ဆ</i>')
|
||||
layero.append(elemClose)
|
||||
elemClose.on('click', function () {
|
||||
layer.close(index)
|
||||
})
|
||||
typeof success === 'function' && success.apply(this, arguments)
|
||||
}
|
||||
}, options))
|
||||
}
|
||||
|
||||
//异常提示
|
||||
view.error = function (content, options) {
|
||||
return view.popup($.extend({
|
||||
content: content
|
||||
, maxWidth: 300
|
||||
//,shade: 0.01
|
||||
, offset: 't'
|
||||
, anim: 6
|
||||
, id: 'LAY_adminError'
|
||||
}, options))
|
||||
}
|
||||
|
||||
//请求模板文件渲染
|
||||
Class.prototype.render = function (views, params) {
|
||||
var that = this, router = layui.router()
|
||||
views = (
|
||||
setter.paths && setter.paths.views
|
||||
? setter.paths.views
|
||||
: setter.views
|
||||
) + views + setter.engine
|
||||
|
||||
$('#' + LAY_BODY).children('.layadmin-loading').remove()
|
||||
view.loading(that.container) //loading
|
||||
|
||||
//请求模板
|
||||
$.ajax({
|
||||
url: views
|
||||
, type: 'get'
|
||||
, dataType: 'html'
|
||||
, data: {
|
||||
v: layui.cache.version
|
||||
}
|
||||
, success: function (html) {
|
||||
html = '<div>' + html + '</div>'
|
||||
|
||||
var elemTitle = $(html).find('title')
|
||||
, title = elemTitle.text() || (html.match(/\<title\>([\s\S]*)\<\/title>/) || [])[1]
|
||||
|
||||
var res = {
|
||||
title: title
|
||||
, body: html
|
||||
}
|
||||
|
||||
elemTitle.remove()
|
||||
that.params = params || {} //获取参数
|
||||
|
||||
if (that.then) {
|
||||
that.then(res)
|
||||
delete that.then
|
||||
}
|
||||
|
||||
that.parse(html)
|
||||
view.removeLoad()
|
||||
|
||||
if (that.done) {
|
||||
that.done(res)
|
||||
delete that.done
|
||||
}
|
||||
|
||||
}
|
||||
, error: function (e) {
|
||||
view.removeLoad()
|
||||
|
||||
if (that.render.isError) {
|
||||
return view.error('请求视图文件异常,状态:' + e.status)
|
||||
}
|
||||
|
||||
if (e.status === 404) {
|
||||
that.render('template/tips/404')
|
||||
} else {
|
||||
that.render('template/tips/error')
|
||||
}
|
||||
|
||||
that.render.isError = true
|
||||
}
|
||||
})
|
||||
return that
|
||||
}
|
||||
|
||||
//解析模板
|
||||
Class.prototype.parse = function (html, refresh, callback) {
|
||||
var that = this
|
||||
, isScriptTpl = typeof html === 'object' //是否模板元素
|
||||
, elem = isScriptTpl ? html : $(html)
|
||||
, elemTemp = isScriptTpl ? html : elem.find('*[template]')
|
||||
, fn = function (options) {
|
||||
var tpl = laytpl(options.dataElem.html())
|
||||
, res = $.extend({
|
||||
params: router.params
|
||||
}, options.res)
|
||||
|
||||
options.dataElem.after(tpl.render(res))
|
||||
typeof callback === 'function' && callback()
|
||||
|
||||
try {
|
||||
options.done && new Function('d', options.done)(res)
|
||||
} catch (e) {
|
||||
console.error(options.dataElem[0], '\n存在错误回调脚本\n\n', e)
|
||||
}
|
||||
}
|
||||
, router = layui.router()
|
||||
|
||||
elem.find('title').remove()
|
||||
that.container[refresh ? 'after' : 'html'](elem.children())
|
||||
|
||||
router.params = that.params || {}
|
||||
|
||||
//遍历模板区块
|
||||
for (var i = elemTemp.length; i > 0; i--) {
|
||||
(function () {
|
||||
var dataElem = elemTemp.eq(i - 1)
|
||||
, layDone = dataElem.attr('lay-done') || dataElem.attr('lay-then') //获取回调
|
||||
, url = laytpl(dataElem.attr('lay-url') || '').render(router) //接口 url
|
||||
, data = laytpl(dataElem.attr('lay-data') || '').render(router) //接口参数
|
||||
, headers = laytpl(dataElem.attr('lay-headers') || '').render(router) //接口请求的头信息
|
||||
|
||||
try {
|
||||
data = new Function('return ' + data + ';')()
|
||||
} catch (e) {
|
||||
hint.error('lay-data: ' + e.message)
|
||||
data = {}
|
||||
}
|
||||
|
||||
try {
|
||||
headers = new Function('return ' + headers + ';')()
|
||||
} catch (e) {
|
||||
hint.error('lay-headers: ' + e.message)
|
||||
headers = headers || {}
|
||||
}
|
||||
|
||||
if (url) {
|
||||
view.req({
|
||||
type: dataElem.attr('lay-type') || 'get'
|
||||
, url: url
|
||||
, data: data
|
||||
, dataType: 'json'
|
||||
, headers: headers
|
||||
, success: function (res) {
|
||||
fn({
|
||||
dataElem: dataElem
|
||||
, res: res
|
||||
, done: layDone
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
fn({
|
||||
dataElem: dataElem
|
||||
, done: layDone
|
||||
})
|
||||
}
|
||||
}())
|
||||
}
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
//直接渲染字符
|
||||
Class.prototype.send = function (views, data) {
|
||||
var tpl = laytpl(views || this.container.html()).render(data || {})
|
||||
this.container.html(tpl)
|
||||
return this
|
||||
}
|
||||
|
||||
//局部刷新模板
|
||||
Class.prototype.refresh = function (callback) {
|
||||
var that = this
|
||||
, next = that.container.next()
|
||||
, templateid = next.attr('lay-templateid')
|
||||
|
||||
if (that.id != templateid) return that
|
||||
|
||||
that.parse(that.container, 'refresh', function () {
|
||||
that.container.siblings('[lay-templateid="' + that.id + '"]:last').remove()
|
||||
typeof callback === 'function' && callback()
|
||||
})
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
//视图请求成功后的回调
|
||||
Class.prototype.then = function (callback) {
|
||||
this.then = callback
|
||||
return this
|
||||
}
|
||||
|
||||
//视图渲染完毕后的回调
|
||||
Class.prototype.done = function (callback) {
|
||||
this.done = callback
|
||||
return this
|
||||
}
|
||||
|
||||
//对外接口
|
||||
exports('view', view)
|
||||
})
|
||||
154
public/res/config.js
Normal file
154
public/res/config.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* setter
|
||||
*/
|
||||
|
||||
// 初始化配置
|
||||
layui.define(['all'], function (exports) {
|
||||
exports('setter', {
|
||||
paths: { // v1.9.0 及以上版本的写法
|
||||
core: layui.cache.base + 'adminui/src/', // 核心库所在目录
|
||||
views: layui.cache.base + 'views/', // 业务视图所在目录
|
||||
modules: layui.cache.base + 'modules/', // 业务模块所在目录
|
||||
base: layui.cache.base // 记录静态资源所在基础目录
|
||||
},
|
||||
|
||||
container: 'Panel_app', // 容器ID
|
||||
entry: 'index', // 默认视图文件名
|
||||
engine: '.html', // 视图文件后缀名
|
||||
pageTabs: true, // 是否开启页面选项卡功能。单页版不推荐开启
|
||||
refreshCurrPage: true, // 当跳转页面 url 与当前页 url 相同时,是否自动执行刷新
|
||||
|
||||
name: '耗子Linux面板',
|
||||
tableName: 'HaoZiPanel', // 本地存储表名
|
||||
MOD_NAME: 'admin', // 模块事件名
|
||||
|
||||
debug: true, // 是否开启调试模式。如开启,接口异常时会抛出异常 URL 等信息
|
||||
interceptor: true, // 是否开启未登入拦截
|
||||
|
||||
// 自定义请求字段
|
||||
request: {
|
||||
tokenName: 'access_token' // 自动携带 token 的字段名。可设置 false 不携带。
|
||||
},
|
||||
|
||||
// 自定义响应字段
|
||||
response: {
|
||||
statusName: 'code', // 数据状态的字段名称
|
||||
statusCode: {
|
||||
ok: 0, // 数据状态一切正常的状态码
|
||||
logout: 1001 // 登录状态失效的状态码
|
||||
},
|
||||
msgName: 'message', // 状态信息的字段名称
|
||||
dataName: 'data' // 数据详情的字段名称
|
||||
},
|
||||
|
||||
// 独立页面路由,可随意添加(无需写参数)
|
||||
indPage: [
|
||||
'/user/login', // 登入页
|
||||
],
|
||||
|
||||
// 配置业务模块目录中的特殊模块
|
||||
extend: {
|
||||
layim: 'layim/layim' // layim
|
||||
},
|
||||
|
||||
// 主题配置
|
||||
theme: {
|
||||
// 内置主题配色方案
|
||||
color: [{
|
||||
main: '#20222A', // 主题色
|
||||
selected: '#16baaa', // 选中色
|
||||
alias: 'default' // 默认别名
|
||||
}, {
|
||||
main: '#03152A',
|
||||
selected: '#3B91FF',
|
||||
alias: 'dark-blue' // 藏蓝
|
||||
}, {
|
||||
main: '#2E241B',
|
||||
selected: '#A48566',
|
||||
alias: 'coffee' // 咖啡
|
||||
}, {
|
||||
main: '#50314F',
|
||||
selected: '#7A4D7B',
|
||||
alias: 'purple-red' // 紫红
|
||||
}, {
|
||||
main: '#344058',
|
||||
logo: '#1E9FFF',
|
||||
selected: '#1E9FFF',
|
||||
alias: 'ocean' // 海洋
|
||||
}, {
|
||||
main: '#3A3D49',
|
||||
logo: '#2F9688',
|
||||
selected: '#16b777',
|
||||
alias: 'green' // 墨绿
|
||||
}, {
|
||||
main: '#20222A',
|
||||
logo: '#F78400',
|
||||
selected: '#F78400',
|
||||
alias: 'red' // 橙色
|
||||
}, {
|
||||
main: '#28333E',
|
||||
logo: '#AA3130',
|
||||
selected: '#AA3130',
|
||||
alias: 'fashion-red' // 时尚红
|
||||
}, {
|
||||
main: '#24262F',
|
||||
logo: '#3A3D49',
|
||||
selected: '#16baaa',
|
||||
alias: 'classic-black' // 经典黑
|
||||
}, {
|
||||
logo: '#226A62',
|
||||
header: '#2F9688',
|
||||
alias: 'green-header' // 墨绿头
|
||||
}, {
|
||||
main: '#344058',
|
||||
logo: '#0085E8',
|
||||
selected: '#1E9FFF',
|
||||
header: '#1E9FFF',
|
||||
alias: 'ocean-header' // 海洋头
|
||||
}, {
|
||||
header: '#393D49',
|
||||
alias: 'classic-black-header' // 经典黑
|
||||
}, {
|
||||
main: '#50314F',
|
||||
logo: '#50314F',
|
||||
selected: '#7A4D7B',
|
||||
header: '#50314F',
|
||||
alias: 'purple-red-header' // 紫红头
|
||||
}, {
|
||||
main: '#28333E',
|
||||
logo: '#28333E',
|
||||
selected: '#AA3130',
|
||||
header: '#AA3130',
|
||||
alias: 'fashion-red-header' // 时尚红头
|
||||
}, {
|
||||
main: '#28333E',
|
||||
logo: '#16baaa',
|
||||
selected: '#16baaa',
|
||||
header: '#16baaa',
|
||||
alias: 'green-header' // 墨绿头
|
||||
}, {
|
||||
main: '#393D49',
|
||||
logo: '#393D49',
|
||||
selected: '#16baaa',
|
||||
header: '#23262E',
|
||||
alias: 'Classic-style1' // 经典风格1
|
||||
}, {
|
||||
main: '#001529',
|
||||
logo: '#001529',
|
||||
selected: '#1890FF',
|
||||
header: '#1890FF',
|
||||
alias: 'Classic-style2' // 经典风格2
|
||||
}, {
|
||||
main: '#25282A',
|
||||
logo: '#25282A',
|
||||
selected: '#35BDB2',
|
||||
header: '#35BDB2',
|
||||
alias: 'Classic-style3' // 经典风格3
|
||||
}],
|
||||
|
||||
// 初始的颜色索引,对应上面的配色方案数组索引
|
||||
// 如果本地已经有主题色记录,则以本地记录为优先,除非请求本地数据(localStorage)
|
||||
initColorIndex: 0
|
||||
}
|
||||
})
|
||||
})
|
||||
40
public/res/index.js
Normal file
40
public/res/index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 初始化主题入口模块
|
||||
*/
|
||||
|
||||
layui.extend({
|
||||
setter: 'config' // 将 config.js 扩展到 layui 模块
|
||||
}).define(['setter'], function (exports) {
|
||||
var setter = layui.setter
|
||||
|
||||
// 将核心库扩展到 layui 模块
|
||||
layui.each({
|
||||
admin: 'admin',
|
||||
view: 'view',
|
||||
adminIndex: 'index'
|
||||
}, function (modName, fileName) {
|
||||
var libs = {}
|
||||
libs[modName] = '{/}' + setter.paths.core + '/modules/' + fileName
|
||||
layui.extend(libs)
|
||||
})
|
||||
|
||||
// 指定业务模块基础目录
|
||||
layui.config({
|
||||
base: setter.paths.modules
|
||||
})
|
||||
|
||||
// 将业务模块中的特殊模块扩展到 layui 模块
|
||||
layui.each(setter.extend, function (key, value) {
|
||||
var mods = {}
|
||||
mods[key] = '{/}' + layui.cache.base + value
|
||||
layui.extend(mods)
|
||||
})
|
||||
|
||||
// 加载主题核心库入口模块
|
||||
layui.use('adminIndex', function () {
|
||||
layui.use('common') // 加载公共业务模块,如不需要可剔除
|
||||
|
||||
// 输出模块 / 模块加载完毕标志
|
||||
exports('index', layui.admin)
|
||||
})
|
||||
})
|
||||
1
public/res/layui/css/layui.css
Normal file
1
public/res/layui/css/layui.css
Normal file
File diff suppressed because one or more lines are too long
BIN
public/res/layui/font/iconfont.eot
Normal file
BIN
public/res/layui/font/iconfont.eot
Normal file
Binary file not shown.
790
public/res/layui/font/iconfont.svg
Normal file
790
public/res/layui/font/iconfont.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 329 KiB |
BIN
public/res/layui/font/iconfont.ttf
Normal file
BIN
public/res/layui/font/iconfont.ttf
Normal file
Binary file not shown.
BIN
public/res/layui/font/iconfont.woff
Normal file
BIN
public/res/layui/font/iconfont.woff
Normal file
Binary file not shown.
BIN
public/res/layui/font/iconfont.woff2
Normal file
BIN
public/res/layui/font/iconfont.woff2
Normal file
Binary file not shown.
1
public/res/layui/layui.js
Normal file
1
public/res/layui/layui.js
Normal file
File diff suppressed because one or more lines are too long
33
public/res/modules/common.js
Normal file
33
public/res/modules/common.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* common
|
||||
*/
|
||||
|
||||
layui.define(function (exports) {
|
||||
var $ = layui.$
|
||||
, layer = layui.layer
|
||||
, laytpl = layui.laytpl
|
||||
, setter = layui.setter
|
||||
, view = layui.view
|
||||
, admin = layui.admin
|
||||
|
||||
//公共业务的逻辑处理可以写在此处,切换任何页面都会执行
|
||||
//……
|
||||
|
||||
//退出
|
||||
admin.events.logout = function () {
|
||||
//执行退出接口
|
||||
admin.req({
|
||||
url: './res/json/user/logout.js'
|
||||
, type: 'get'
|
||||
, data: {}
|
||||
, done: function (res) { //这里要说明一下:done 是只有 response 的 code 正常才会执行。而 succese 则是只要 http 为 200 就会执行
|
||||
|
||||
//清空本地记录的 token,并跳转到登入页
|
||||
admin.exit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//对外暴露的接口
|
||||
exports('common', {})
|
||||
})
|
||||
330
public/res/modules/console.js
Normal file
330
public/res/modules/console.js
Normal file
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* console
|
||||
*/
|
||||
|
||||
layui.define(function (exports) {
|
||||
|
||||
/*
|
||||
下面通过 layui.use 分段加载不同的模块,实现不同区域的同时渲染,从而保证视图的快速呈现
|
||||
*/
|
||||
|
||||
//区块轮播切换
|
||||
layui.use(['admin', 'carousel'], function () {
|
||||
var $ = layui.$
|
||||
, admin = layui.admin
|
||||
, carousel = layui.carousel
|
||||
, element = layui.element
|
||||
, device = layui.device()
|
||||
|
||||
//轮播切换
|
||||
$('.layadmin-carousel').each(function () {
|
||||
var othis = $(this)
|
||||
carousel.render({
|
||||
elem: this
|
||||
, width: '100%'
|
||||
, arrow: 'none'
|
||||
, interval: othis.data('interval')
|
||||
, autoplay: othis.data('autoplay') === true
|
||||
, trigger: (device.ios || device.android) ? 'click' : 'hover'
|
||||
, anim: othis.data('anim')
|
||||
})
|
||||
})
|
||||
|
||||
element.render('progress')
|
||||
|
||||
})
|
||||
|
||||
//数据概览
|
||||
layui.use(['admin', 'carousel', 'echarts'], function () {
|
||||
var $ = layui.$
|
||||
, admin = layui.admin
|
||||
, carousel = layui.carousel
|
||||
, echarts = layui.echarts
|
||||
|
||||
var echartsApp = [], options = [
|
||||
//今日流量趋势
|
||||
{
|
||||
title: {
|
||||
text: '今日流量趋势',
|
||||
x: 'center',
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['', '']
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['06:00', '06:30', '07:00', '07:30', '08:00', '08:30', '09:00', '09:30', '10:00', '11:30', '12:00', '12:30', '13:00', '13:30', '14:00', '14:30', '15:00', '15:30', '16:00', '16:30', '17:00', '17:30', '18:00', '18:30', '19:00', '19:30', '20:00', '20:30', '21:00', '21:30', '22:00', '22:30', '23:00', '23:30']
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value'
|
||||
}],
|
||||
series: [{
|
||||
name: 'PV',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: { normal: { areaStyle: { type: 'default' } } },
|
||||
data: [111, 222, 333, 444, 555, 777, 3333, 33333, 55555, 88888, 33333, 3333, 7777, 11888, 28888, 38888, 58888, 42222, 39999, 28888, 17777, 9777, 6555, 5555, 3333, 2222, 3111, 6999, 5888, 2777, 1777, 999, 888, 777]
|
||||
}, {
|
||||
name: 'UV',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: { normal: { areaStyle: { type: 'default' } } },
|
||||
data: [11, 22, 33, 44, 55, 66, 333, 3333, 5555, 12312, 3333, 333, 777, 1188, 2777, 3888, 7777, 4222, 3999, 2888, 1777, 966, 655, 555, 333, 222, 311, 699, 588, 277, 166, 99, 88, 77]
|
||||
}]
|
||||
},
|
||||
|
||||
/*
|
||||
//访客浏览器分布
|
||||
{
|
||||
title : {
|
||||
text: '访客浏览器分布',
|
||||
x: 'center',
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip : {
|
||||
trigger: 'item',
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)"
|
||||
},
|
||||
legend: {
|
||||
orient : 'vertical',
|
||||
x : 'left',
|
||||
data:['Chrome','Firefox','IE 8.0','Safari','其它浏览器']
|
||||
},
|
||||
series : [{
|
||||
name:'访问来源',
|
||||
type:'pie',
|
||||
radius : '55%',
|
||||
center: ['50%', '50%'],
|
||||
data:[
|
||||
{value:9052, name:'Chrome'},
|
||||
{value:1610, name:'Firefox'},
|
||||
{value:3200, name:'IE 8.0'},
|
||||
{value:535, name:'Safari'},
|
||||
{value:1700, name:'其它浏览器'}
|
||||
]
|
||||
}]
|
||||
},
|
||||
*/
|
||||
|
||||
//新增的用户量
|
||||
{
|
||||
title: {
|
||||
text: '最近一周新增的用户量',
|
||||
x: 'center',
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: { //提示框
|
||||
trigger: 'axis',
|
||||
formatter: '{b}<br>新增用户:{c}'
|
||||
},
|
||||
xAxis: [{ //X轴
|
||||
type: 'category',
|
||||
data: ['11-07', '11-08', '11-09', '11-10', '11-11', '11-12', '11-13']
|
||||
}],
|
||||
yAxis: [{ //Y轴
|
||||
type: 'value'
|
||||
}],
|
||||
series: [{ //内容
|
||||
type: 'line',
|
||||
data: [200, 300, 400, 610, 150, 270, 380],
|
||||
}]
|
||||
}
|
||||
]
|
||||
, elemDataView = $('#LAY-index-dataview').children('div')
|
||||
, renderDataView = function (index) {
|
||||
echartsApp[index] = echarts.init(elemDataView[index], layui.echartsTheme)
|
||||
echartsApp[index].setOption(options[index])
|
||||
//window.onresize = echartsApp[index].resize;
|
||||
admin.resize(function () {
|
||||
echartsApp[index].resize()
|
||||
})
|
||||
}
|
||||
|
||||
//没找到DOM,终止执行
|
||||
if (!elemDataView[0]) return
|
||||
|
||||
renderDataView(0)
|
||||
|
||||
//触发数据概览轮播
|
||||
var carouselIndex = 0
|
||||
carousel.on('change(LAY-index-dataview)', function (obj) {
|
||||
renderDataView(carouselIndex = obj.index)
|
||||
})
|
||||
|
||||
//触发侧边伸缩
|
||||
layui.admin.on('side', function () {
|
||||
setTimeout(function () {
|
||||
renderDataView(carouselIndex)
|
||||
}, 300)
|
||||
})
|
||||
|
||||
//触发路由
|
||||
layui.admin.on('hash(tab)', function () {
|
||||
layui.router().path.join('') || renderDataView(carouselIndex)
|
||||
})
|
||||
})
|
||||
|
||||
//地图
|
||||
layui.use(['carousel', 'echarts'], function () {
|
||||
var $ = layui.$
|
||||
, carousel = layui.carousel
|
||||
, echarts = layui.echarts
|
||||
|
||||
var echartsApp = [], options = [
|
||||
{
|
||||
title: {
|
||||
text: '访客地区分布',
|
||||
subtext: '不完全统计'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
dataRange: {
|
||||
orient: 'horizontal',
|
||||
min: 0,
|
||||
max: 60000,
|
||||
text: ['高', '低'],
|
||||
splitNumber: 0
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '访客地区分布',
|
||||
type: 'map',
|
||||
mapType: 'china',
|
||||
selectedMode: 'multiple',
|
||||
itemStyle: {
|
||||
normal: { label: { show: true } },
|
||||
emphasis: { label: { show: true } }
|
||||
},
|
||||
data: [
|
||||
{ name: '西藏', value: 60 },
|
||||
{ name: '青海', value: 167 },
|
||||
{ name: '宁夏', value: 210 },
|
||||
{ name: '海南', value: 252 },
|
||||
{ name: '甘肃', value: 502 },
|
||||
{ name: '贵州', value: 570 },
|
||||
{ name: '新疆', value: 661 },
|
||||
{ name: '云南', value: 8890 },
|
||||
{ name: '重庆', value: 10010 },
|
||||
{ name: '吉林', value: 5056 },
|
||||
{ name: '山西', value: 2123 },
|
||||
{ name: '天津', value: 9130 },
|
||||
{ name: '江西', value: 10170 },
|
||||
{ name: '广西', value: 6172 },
|
||||
{ name: '陕西', value: 9251 },
|
||||
{ name: '黑龙江', value: 5125 },
|
||||
{ name: '内蒙古', value: 1435 },
|
||||
{ name: '安徽', value: 9530 },
|
||||
{ name: '北京', value: 51919 },
|
||||
{ name: '福建', value: 3756 },
|
||||
{ name: '上海', value: 59190 },
|
||||
{ name: '湖北', value: 37109 },
|
||||
{ name: '湖南', value: 8966 },
|
||||
{ name: '四川', value: 31020 },
|
||||
{ name: '辽宁', value: 7222 },
|
||||
{ name: '河北', value: 3451 },
|
||||
{ name: '河南', value: 9693 },
|
||||
{ name: '浙江', value: 62310 },
|
||||
{ name: '山东', value: 39231 },
|
||||
{ name: '江苏', value: 35911 },
|
||||
{ name: '广东', value: 55891 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
, elemDataView = $('#LAY-index-pagethree-home').children('div')
|
||||
, renderDataView = function (index) {
|
||||
echartsApp[index] = echarts.init(elemDataView[index], layui.echartsTheme)
|
||||
echartsApp[index].setOption(options[index])
|
||||
window.onresize = echartsApp[index].resize
|
||||
}
|
||||
//没找到DOM,终止执行
|
||||
if (!elemDataView[0]) return
|
||||
|
||||
renderDataView(0)
|
||||
})
|
||||
|
||||
//table
|
||||
layui.use('table', function () {
|
||||
var $ = layui.$
|
||||
, table = layui.table
|
||||
|
||||
//今日热搜
|
||||
table.render({
|
||||
elem: '#LAY-index-topSearch'
|
||||
, url: './res/json/console/top-search.js' //模拟接口
|
||||
, page: true
|
||||
, cols: [[
|
||||
{ type: 'numbers', fixed: 'left' }
|
||||
, {
|
||||
field: 'keywords',
|
||||
title: '关键词',
|
||||
minWidth: 300,
|
||||
templet: '<div><a href="https://www.baidu.com/s?wd={{ d.keywords }}" target="_blank" class="layui-table-link">{{ d.keywords }}</div>'
|
||||
}
|
||||
, { field: 'frequency', title: '搜索次数', minWidth: 120, sort: true }
|
||||
, { field: 'userNums', title: '用户数', sort: true }
|
||||
]]
|
||||
, skin: 'line'
|
||||
})
|
||||
|
||||
//今日热贴
|
||||
table.render({
|
||||
elem: '#LAY-index-topCard'
|
||||
, url: './res/json/console/top-card.js' //模拟接口
|
||||
, page: true
|
||||
, cellMinWidth: 120
|
||||
, cols: [[
|
||||
{ type: 'numbers', fixed: 'left' }
|
||||
, {
|
||||
field: 'title',
|
||||
title: '标题',
|
||||
minWidth: 300,
|
||||
templet: '<div><a href="{{ d.href }}" target="_blank" class="layui-table-link">{{ d.title }}</div>'
|
||||
}
|
||||
, { field: 'username', title: '发帖者' }
|
||||
, { field: 'channel', title: '类别' }
|
||||
, { field: 'crt', title: '点击率', sort: true }
|
||||
]]
|
||||
, skin: 'line'
|
||||
})
|
||||
|
||||
//项目进展
|
||||
table.render({
|
||||
elem: '#LAY-home-homepage-console'
|
||||
, url: './res/json/console/prograss.js' //模拟接口
|
||||
, cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' }
|
||||
, { field: 'prograss', title: '任务' }
|
||||
, { field: 'time', title: '所需时间' }
|
||||
, {
|
||||
field: 'complete', title: '完成情况'
|
||||
, templet: function (d) {
|
||||
if (d.complete == '已完成') {
|
||||
return '<del style="color: #16b777;">' + d.complete + '</del>'
|
||||
} else if (d.complete == '进行中') {
|
||||
return '<span style="color: #FFB800;">' + d.complete + '</span>'
|
||||
} else {
|
||||
return '<span style="color: #FF5722;">' + d.complete + '</span>'
|
||||
}
|
||||
}
|
||||
}
|
||||
]]
|
||||
, skin: 'line'
|
||||
})
|
||||
})
|
||||
|
||||
exports('console', {})
|
||||
})
|
||||
160
public/res/modules/contlist.js
Normal file
160
public/res/modules/contlist.js
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 内容系统 demo
|
||||
*/
|
||||
|
||||
layui.define(['table', 'form'], function (exports) {
|
||||
var $ = layui.$
|
||||
, admin = layui.admin
|
||||
, view = layui.view
|
||||
, table = layui.table
|
||||
, form = layui.form
|
||||
|
||||
//文章管理
|
||||
table.render({
|
||||
elem: '#LAY-app-content-list'
|
||||
, url: './res/json/content/list.js' //模拟接口
|
||||
, cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' }
|
||||
, { field: 'id', width: 100, title: '文章ID', sort: true }
|
||||
, { field: 'label', title: '文章标签', minWidth: 100 }
|
||||
, { field: 'title', title: '文章标题' }
|
||||
, { field: 'author', title: '作者' }
|
||||
, { field: 'uploadtime', title: '上传时间', sort: true }
|
||||
, { field: 'status', title: '发布状态', templet: '#buttonTpl', minWidth: 80, align: 'center' }
|
||||
, { title: '操作', minWidth: 150, align: 'center', fixed: 'right', toolbar: '#table-content-list' }
|
||||
]]
|
||||
, page: true
|
||||
, limit: 10
|
||||
, limits: [10, 15, 20, 25, 30]
|
||||
, text: '对不起,加载出现异常!'
|
||||
})
|
||||
|
||||
//工具条
|
||||
table.on('tool(LAY-app-content-list)', function (obj) {
|
||||
var data = obj.data
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('确定删除此文章?', function (index) {
|
||||
obj.del()
|
||||
layer.close(index)
|
||||
})
|
||||
} else if (obj.event === 'edit') {
|
||||
admin.popup({
|
||||
title: '编辑文章'
|
||||
, area: ['550px', '550px']
|
||||
, id: 'LAY-popup-content-edit'
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('app/content/listform', data).done(function () {
|
||||
form.render(null, 'layuiadmin-app-form-list')
|
||||
|
||||
//提交
|
||||
form.on('submit(layuiadmin-app-form-submit)', function (data) {
|
||||
var field = data.field //获取提交的字段
|
||||
|
||||
//提交 Ajax 成功后,关闭当前弹层并重载表格
|
||||
//$.ajax({});
|
||||
layui.table.reload('LAY-app-content-list') //重载表格
|
||||
layer.close(index) //执行关闭
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
//分类管理
|
||||
table.render({
|
||||
elem: '#LAY-app-content-tags'
|
||||
, url: './res/json/content/tags.js' //模拟接口
|
||||
, cols: [[
|
||||
{ type: 'numbers', fixed: 'left' }
|
||||
, { field: 'id', width: 100, title: 'ID', sort: true }
|
||||
, { field: 'tags', title: '分类名', minWidth: 100 }
|
||||
, { title: '操作', width: 150, align: 'center', fixed: 'right', toolbar: '#layuiadmin-app-cont-tagsbar' }
|
||||
]]
|
||||
, text: '对不起,加载出现异常!'
|
||||
})
|
||||
|
||||
//工具条
|
||||
table.on('tool(LAY-app-content-tags)', function (obj) {
|
||||
var data = obj.data
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('确定删除此分类?', function (index) {
|
||||
obj.del()
|
||||
layer.close(index)
|
||||
})
|
||||
} else if (obj.event === 'edit') {
|
||||
admin.popup({
|
||||
title: '编辑分类'
|
||||
, area: ['450px', '200px']
|
||||
, id: 'LAY-popup-content-tags'
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('app/content/tagsform', data).done(function () {
|
||||
form.render(null, 'layuiadmin-form-tags')
|
||||
|
||||
//提交
|
||||
form.on('submit(layuiadmin-app-tags-submit)', function (data) {
|
||||
var field = data.field //获取提交的字段
|
||||
|
||||
//提交 Ajax 成功后,关闭当前弹层并重载表格
|
||||
//$.ajax({});
|
||||
layui.table.reload('LAY-app-content-tags') //重载表格
|
||||
layer.close(index) //执行关闭
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
//评论管理
|
||||
table.render({
|
||||
elem: '#LAY-app-content-comm'
|
||||
, url: './res/json/content/comment.js' //模拟接口
|
||||
, cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' }
|
||||
, { field: 'id', width: 100, title: 'ID', sort: true }
|
||||
, { field: 'reviewers', title: '评论者', minWidth: 100 }
|
||||
, { field: 'content', title: '评论内容', minWidth: 100 }
|
||||
, { field: 'commtime', title: '评论时间', minWidth: 100, sort: true }
|
||||
, { title: '操作', width: 150, align: 'center', fixed: 'right', toolbar: '#table-content-com' }
|
||||
]]
|
||||
, page: true
|
||||
, limit: 10
|
||||
, limits: [10, 15, 20, 25, 30]
|
||||
, text: '对不起,加载出现异常!'
|
||||
})
|
||||
|
||||
//工具条
|
||||
table.on('tool(LAY-app-content-comm)', function (obj) {
|
||||
var data = obj.data
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('确定删除此条评论?', function (index) {
|
||||
obj.del()
|
||||
layer.close(index)
|
||||
})
|
||||
} else if (obj.event === 'edit') {
|
||||
admin.popup({
|
||||
title: '编辑评论'
|
||||
, area: ['450px', '300px']
|
||||
, id: 'LAY-popup-content-comm'
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('app/content/contform', data).done(function () {
|
||||
form.render(null, 'layuiadmin-form-comment')
|
||||
|
||||
//提交
|
||||
form.on('submit(layuiadmin-app-com-submit)', function (data) {
|
||||
var field = data.field //获取提交的字段
|
||||
|
||||
//提交 Ajax 成功后,关闭当前弹层并重载表格
|
||||
//$.ajax({});
|
||||
layui.table.reload('LAY-app-content-comm') //重载表格
|
||||
layer.close(index) //执行关闭
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
exports('contlist', {})
|
||||
})
|
||||
21411
public/res/modules/echarts.js
Normal file
21411
public/res/modules/echarts.js
Normal file
File diff suppressed because it is too large
Load Diff
261
public/res/modules/echartsTheme.js
Normal file
261
public/res/modules/echartsTheme.js
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Set echarts theme
|
||||
*/
|
||||
|
||||
layui.define(function (exports) {
|
||||
exports('echartsTheme', {
|
||||
// 默认色板
|
||||
color: [
|
||||
'#16baaa', '#1E9FFF', '#16b777', '#FFB980', '#D87A80',
|
||||
'#8d98b3', '#e5cf0d', '#97b552', '#95706d', '#dc69aa',
|
||||
'#07a2a4', '#9a7fd1', '#588dd5', '#f5994e', '#c05050',
|
||||
'#59678c', '#c9ab00', '#7eb00a', '#6f5553', '#c14089'
|
||||
],
|
||||
|
||||
// 图表标题
|
||||
title: {
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
color: '#5F5F5F' // 主标题文字颜色
|
||||
}
|
||||
},
|
||||
|
||||
// 值域
|
||||
dataRange: {
|
||||
itemWidth: 15,
|
||||
color: ['#16baaa', '#e0ffff']
|
||||
},
|
||||
|
||||
// 工具箱
|
||||
toolbox: {
|
||||
color: ['#1e90ff', '#1e90ff', '#1e90ff', '#1e90ff'],
|
||||
effectiveColor: '#ff4500'
|
||||
},
|
||||
|
||||
// 提示框
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(50,50,50,0.5)', // 提示背景颜色,默认为透明度为0.7的黑色
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'line', // 默认为直线,可选为:'line' | 'shadow'
|
||||
lineStyle: { // 直线指示器样式设置
|
||||
color: '#16baaa'
|
||||
},
|
||||
crossStyle: {
|
||||
color: '#008acd'
|
||||
},
|
||||
shadowStyle: { // 阴影指示器样式设置
|
||||
color: 'rgba(200,200,200,0.2)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 区域缩放控制器
|
||||
dataZoom: {
|
||||
dataBackgroundColor: '#efefff', // 数据背景颜色
|
||||
fillerColor: 'rgba(182,162,222,0.2)', // 填充颜色
|
||||
handleColor: '#008acd' // 手柄颜色
|
||||
},
|
||||
|
||||
// 网格
|
||||
grid: {
|
||||
borderColor: '#eee'
|
||||
},
|
||||
|
||||
// 类目轴 - X轴
|
||||
categoryAxis: {
|
||||
axisLine: { // 坐标轴线
|
||||
lineStyle: { // 属性lineStyle控制线条样式
|
||||
color: '#16baaa'
|
||||
}
|
||||
},
|
||||
axisTick: { //小标记
|
||||
show: false
|
||||
},
|
||||
splitLine: { // 分隔线
|
||||
lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 数值型坐标轴默认参数 - Y轴
|
||||
valueAxis: {
|
||||
axisLine: { // 坐标轴线
|
||||
lineStyle: { // 属性lineStyle控制线条样式
|
||||
color: '#16baaa'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
|
||||
}
|
||||
},
|
||||
splitLine: { // 分隔线
|
||||
lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式
|
||||
color: ['#eee']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
polar: {
|
||||
axisLine: { // 坐标轴线
|
||||
lineStyle: { // 属性lineStyle控制线条样式
|
||||
color: '#ddd'
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(250,250,250,0.2)', 'rgba(200,200,200,0.2)']
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#ddd'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: '#16baaa'
|
||||
},
|
||||
controlStyle: {
|
||||
normal: { color: '#16baaa' },
|
||||
emphasis: { color: '#16baaa' }
|
||||
},
|
||||
symbol: 'emptyCircle',
|
||||
symbolSize: 3
|
||||
},
|
||||
|
||||
// 柱形图默认参数
|
||||
bar: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderRadius: 2
|
||||
},
|
||||
emphasis: {
|
||||
barBorderRadius: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 折线图默认参数
|
||||
line: {
|
||||
smooth: true,
|
||||
symbol: 'emptyCircle', // 拐点图形类型
|
||||
symbolSize: 3 // 拐点图形大小
|
||||
},
|
||||
|
||||
// K线图默认参数
|
||||
k: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#d87a80', // 阳线填充颜色
|
||||
color0: '#2ec7c9', // 阴线填充颜色
|
||||
lineStyle: {
|
||||
color: '#d87a80', // 阳线边框颜色
|
||||
color0: '#2ec7c9' // 阴线边框颜色
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 散点图默认参数
|
||||
scatter: {
|
||||
symbol: 'circle', // 图形类型
|
||||
symbolSize: 4 // 图形大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2
|
||||
},
|
||||
|
||||
// 雷达图默认参数
|
||||
radar: {
|
||||
symbol: 'emptyCircle', // 图形类型
|
||||
symbolSize: 3
|
||||
//symbol: null, // 拐点图形类型
|
||||
//symbolRotate : null, // 图形旋转控制
|
||||
},
|
||||
|
||||
map: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaStyle: {
|
||||
color: '#ddd'
|
||||
},
|
||||
label: {
|
||||
textStyle: {
|
||||
color: '#d87a80'
|
||||
}
|
||||
}
|
||||
},
|
||||
emphasis: { // 也是选中样式
|
||||
areaStyle: {
|
||||
color: '#fe994e'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
force: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
linkStyle: {
|
||||
color: '#1e90ff'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
chord: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)',
|
||||
chordStyle: {
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
}
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(128, 128, 128, 0.5)',
|
||||
chordStyle: {
|
||||
lineStyle: {
|
||||
color: 'rgba(128, 128, 128, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
gauge: {
|
||||
axisLine: { // 坐标轴线
|
||||
lineStyle: { // 属性lineStyle控制线条样式
|
||||
color: [[0.2, '#2ec7c9'], [0.8, '#5ab1ef'], [1, '#d87a80']],
|
||||
width: 10
|
||||
}
|
||||
},
|
||||
axisTick: { // 坐标轴小标记
|
||||
splitNumber: 10, // 每份split细分多少段
|
||||
length: 15, // 属性length控制线长
|
||||
lineStyle: { // 属性lineStyle控制线条样式
|
||||
color: 'auto'
|
||||
}
|
||||
},
|
||||
splitLine: { // 分隔线
|
||||
length: 22, // 属性length控制线长
|
||||
lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式
|
||||
color: 'auto'
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
width: 5
|
||||
}
|
||||
},
|
||||
|
||||
textStyle: {
|
||||
fontFamily: '微软雅黑, Arial, Verdana, sans-serif'
|
||||
}
|
||||
})
|
||||
})
|
||||
119
public/res/modules/forum.js
Normal file
119
public/res/modules/forum.js
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* forum demo
|
||||
*/
|
||||
|
||||
layui.define(['table', 'form'], function (exports) {
|
||||
var $ = layui.$
|
||||
, admin = layui.admin
|
||||
, view = layui.view
|
||||
, table = layui.table
|
||||
, form = layui.form
|
||||
|
||||
//帖子管理
|
||||
table.render({
|
||||
elem: '#LAY-app-forum-list'
|
||||
, url: './res/json/forum/list.js' //模拟接口
|
||||
, cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' }
|
||||
, { field: 'id', width: 100, title: 'ID', sort: true }
|
||||
, { field: 'poster', title: '发帖人' }
|
||||
, { field: 'avatar', title: '头像', width: 100, templet: '#imgTpl' }
|
||||
, { field: 'content', title: '发帖内容' }
|
||||
, { field: 'posttime', title: '发帖时间', sort: true }
|
||||
, { field: 'top', title: '置顶', templet: '#buttonTpl', minWidth: 80, align: 'center' }
|
||||
, { title: '操作', width: 150, align: 'center', fixed: 'right', toolbar: '#table-forum-list' }
|
||||
]]
|
||||
, page: true
|
||||
, limit: 10
|
||||
, limits: [10, 15, 20, 25, 30]
|
||||
, text: '对不起,加载出现异常!'
|
||||
})
|
||||
|
||||
//工具条
|
||||
table.on('tool(LAY-app-forum-list)', function (obj) {
|
||||
var data = obj.data
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('确定删除此条帖子?', function (index) {
|
||||
obj.del()
|
||||
layer.close(index)
|
||||
})
|
||||
} else if (obj.event === 'edit') {
|
||||
admin.popup({
|
||||
title: '编辑帖子'
|
||||
, area: ['550px', '450px']
|
||||
, id: 'LAY-popup-forum-edit'
|
||||
, resize: false
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('app/forum/listform', data).done(function () {
|
||||
form.render(null, 'layuiadmin-form-list')
|
||||
|
||||
//提交
|
||||
form.on('submit(layuiadmin-app-forum-submit)', function (data) {
|
||||
var field = data.field //获取提交的字段
|
||||
|
||||
//提交 Ajax 成功后,关闭当前弹层并重载表格
|
||||
//$.ajax({});
|
||||
layui.table.reload('LAY-app-forum-list') //重载表格
|
||||
layer.close(index) //执行关闭
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
//回帖管理
|
||||
table.render({
|
||||
elem: '#LAY-app-forumreply-list'
|
||||
, url: './res/json/forum/replys.js' //模拟接口
|
||||
, cols: [[
|
||||
{ type: 'checkbox', fixed: 'left' }
|
||||
, { field: 'id', width: 100, title: 'ID', sort: true }
|
||||
, { field: 'replyer', title: '回帖人' }
|
||||
, { field: 'cardid', title: '回帖ID', sort: true }
|
||||
, { field: 'avatar', title: '头像', width: 100, templet: '#imgTpl' }
|
||||
, { field: 'content', title: '回帖内容', width: 200 }
|
||||
, { field: 'replytime', title: '回帖时间', sort: true }
|
||||
, { title: '操作', width: 150, align: 'center', fixed: 'right', toolbar: '#table-forum-replys' }
|
||||
]]
|
||||
, page: true
|
||||
, limit: 10
|
||||
, limits: [10, 15, 20, 25, 30]
|
||||
, text: '对不起,加载出现异常!'
|
||||
})
|
||||
|
||||
//工具条
|
||||
table.on('tool(LAY-app-forumreply-list)', function (obj) {
|
||||
var data = obj.data
|
||||
if (obj.event === 'del') {
|
||||
layer.confirm('确定删除此条评论?', function (index) {
|
||||
obj.del()
|
||||
layer.close(index)
|
||||
})
|
||||
} else if (obj.event === 'edit') {
|
||||
admin.popup({
|
||||
title: '编辑回帖'
|
||||
, area: ['550px', '400px']
|
||||
, id: 'LAY-popup-forum-edit'
|
||||
, resize: false
|
||||
, success: function (layero, index) {
|
||||
view(this.id).render('app/forum/replysform', data).done(function () {
|
||||
form.render(null, 'layuiadmin-app-forum-reply')
|
||||
|
||||
//提交
|
||||
form.on('submit(layuiadmin-app-forumreply-submit)', function (data) {
|
||||
var field = data.field //获取提交的字段
|
||||
|
||||
//提交 Ajax 成功后,关闭当前弹层并重载表格
|
||||
//$.ajax({});
|
||||
layui.table.reload('LAY-app-forumreply-list') //重载表格
|
||||
layer.close(index) //执行关闭
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
exports('forum', {})
|
||||
})
|
||||
1
public/res/modules/layim/layim.js
Normal file
1
public/res/modules/layim/layim.js
Normal file
File diff suppressed because one or more lines are too long
101
public/res/modules/layim/res/html/chatlog.html
Normal file
101
public/res/modules/layim/res/html/chatlog.html
Normal file
@@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>演示聊天记录模板</title>
|
||||
|
||||
<link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
|
||||
<style>
|
||||
html {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
body .layim-chat-main {
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="layim-chat-main">
|
||||
<ul id="LAY_view"></ul>
|
||||
</div>
|
||||
|
||||
<div id="LAY_page" style="margin: 0 10px;"></div>
|
||||
|
||||
|
||||
<textarea title="消息模版" id="LAY_tpl" style="display:none;">
|
||||
{{# layui.each(d.data, function(index, item){
|
||||
if(item.id == layui.layim.cache().mine.id){ }}
|
||||
<li class="layim-chat-mine"><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite><i>{{ layui.data.date(item.timestamp) }}</i>{{ item.username }}</cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
|
||||
{{# } else { }}
|
||||
<li><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite>{{ item.username }}<i>{{ layui.data.date(item.timestamp) }}</i></cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
|
||||
{{# }
|
||||
}); }}
|
||||
</textarea>
|
||||
|
||||
<!--
|
||||
上述模版采用了 laytpl 语法
|
||||
-->
|
||||
|
||||
|
||||
<script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script>
|
||||
<script>
|
||||
layui.link('../layim.css', 'skinlayimcss') //加载 css
|
||||
layui.config({
|
||||
layimPath: '../../' //配置 layim.js 所在目录
|
||||
, layimResPath: '../' //layim 资源文件所在目录
|
||||
}).use(['jquery'], function () {
|
||||
var layim = parent.layui.layim
|
||||
, laytpl = parent.layui.laytpl
|
||||
, $ = layui.jquery
|
||||
, laypage = parent.layui.laypage
|
||||
|
||||
//聊天记录的分页此处不做演示,你可以采用 laypage
|
||||
|
||||
//开始请求聊天记录
|
||||
var param = location.search //获得URL参数。该窗口url会携带会话id和type,他们是你请求聊天记录的重要凭据
|
||||
|
||||
//实际使用时,下述的res一般是通过Ajax获得,而此处仅仅只是演示数据格式
|
||||
, res = {
|
||||
code: 0
|
||||
, msg: ''
|
||||
, data: [{
|
||||
username: '我'
|
||||
, id: 100000
|
||||
, avatar: '' || layui.cache.layimResPath + 'images/default.png'
|
||||
, timestamp: 1480897882000
|
||||
, content: '我方模拟记录 111'
|
||||
}, {
|
||||
username: 'test123'
|
||||
, id: 108101
|
||||
, avatar: '' || layui.cache.layimResPath + 'images/default.png'
|
||||
, timestamp: 1480897892000
|
||||
, content: '对方模拟记录 111'
|
||||
}, {
|
||||
username: 'test123'
|
||||
, id: 108101
|
||||
, avatar: '' || layui.cache.layimResPath + 'images/default.png'
|
||||
, timestamp: 1480897898000
|
||||
, content: '对方模拟记录 222'
|
||||
}, {
|
||||
username: 'test123'
|
||||
, id: 108101
|
||||
, avatar: '' || layui.cache.layimResPath + 'images/default.png'
|
||||
, timestamp: 1480897908000
|
||||
, content: '注意:该页面为 chatLog 参数指向的自定义页面。此页仅为聊天记录的模拟数据,实际使用时请进行相应修改。'
|
||||
}]
|
||||
}
|
||||
|
||||
//console.log(param)
|
||||
|
||||
var html = laytpl(LAY_tpl.value).render({
|
||||
data: res.data
|
||||
})
|
||||
$('#LAY_view').html(html)
|
||||
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
41
public/res/modules/layim/res/html/find.html
Normal file
41
public/res/modules/layim/res/html/find.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>发现</title>
|
||||
|
||||
<link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div style="margin: 15px;">
|
||||
<blockquote class="layui-elem-quote">
|
||||
通过 find 参数指向的自定义页面
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script>
|
||||
<script>
|
||||
layui.config({
|
||||
layimPath: '../../' //配置 layim.js 所在目录
|
||||
, layimResPath: '../' //layim 资源文件所在目录
|
||||
}).extend({
|
||||
layim: layui.cache.layimPath + 'layim' //配置 layim 组件所在的路径
|
||||
}).use(['layim', 'laypage'], function () {
|
||||
var layim = layui.layim
|
||||
, layer = layui.layer
|
||||
, laytpl = layui.laytpl
|
||||
, $ = layui.jquery
|
||||
, laypage = layui.laypage
|
||||
|
||||
//一些添加好友请求之类的交互参见文档
|
||||
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
72
public/res/modules/layim/res/html/getmsg.json
Normal file
72
public/res/modules/layim/res/html/getmsg.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"code": 0,
|
||||
"pages": 1,
|
||||
"data": [
|
||||
{
|
||||
"id": 76,
|
||||
"content": "申请添加你为好友",
|
||||
"uid": 168,
|
||||
"from": 166488,
|
||||
"from_group": 0,
|
||||
"type": 1,
|
||||
"remark": "test1",
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "刚刚",
|
||||
"user": {
|
||||
"id": 166488,
|
||||
"avatar": "http://q.qlogo.cn/qqapp/101235792/B704597964F9BD0DB648292D1B09F7E8/100",
|
||||
"username": "测试A",
|
||||
"sign": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"content": "申请添加你为好友",
|
||||
"uid": 168,
|
||||
"from": 347592,
|
||||
"from_group": 0,
|
||||
"type": 1,
|
||||
"remark": "test2",
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "刚刚",
|
||||
"user": {
|
||||
"id": 347592,
|
||||
"avatar": "http://q.qlogo.cn/qqapp/101235792/B78751375E0531675B1272AD994BA875/100",
|
||||
"username": "测试B",
|
||||
"sign": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"content": "测试C 拒绝了你的好友申请",
|
||||
"uid": 168,
|
||||
"from": null,
|
||||
"from_group": null,
|
||||
"type": 1,
|
||||
"remark": null,
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "10天前",
|
||||
"user": {
|
||||
"id": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"content": "测试D 已经同意你的好友申请",
|
||||
"uid": 168,
|
||||
"from": null,
|
||||
"from_group": null,
|
||||
"type": 1,
|
||||
"remark": null,
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "10天前",
|
||||
"user": {
|
||||
"id": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
267
public/res/modules/layim/res/html/msgbox.html
Normal file
267
public/res/modules/layim/res/html/msgbox.html
Normal file
@@ -0,0 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>消息盒子</title>
|
||||
|
||||
<link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
|
||||
<style>
|
||||
.layim-msgbox {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.layim-msgbox li {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
padding: 0 130px 10px 60px;
|
||||
padding-bottom: 10px;
|
||||
line-height: 22px;
|
||||
border-bottom: 1px dotted #e2e2e2;
|
||||
}
|
||||
|
||||
.layim-msgbox .layim-msgbox-tips {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.layim-msgbox .layim-msgbox-system {
|
||||
padding: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.layim-msgbox li p span {
|
||||
padding-left: 5px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.layim-msgbox li p em {
|
||||
font-style: normal;
|
||||
color: #FF5722;
|
||||
}
|
||||
|
||||
.layim-msgbox-avatar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.layim-msgbox-user {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.layim-msgbox-content {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.layim-msgbox .layui-btn-small {
|
||||
padding: 0 15px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.layim-msgbox-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<ul class="layim-msgbox" id="LAY_view"></ul>
|
||||
|
||||
<div style="margin: 0 15px;">
|
||||
<blockquote class="layui-elem-quote">
|
||||
注意:该页面为 msgbox 参数指向的自定义页面。
|
||||
<br> 此页为消息盒子的模拟数据,实际使用时请进行相应修改。
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<textarea title="消息模版" id="LAY_tpl" style="display:none;">
|
||||
{{# layui.each(d.data, function(index, item){
|
||||
if(item.from){ }}
|
||||
<li data-uid="{{ item.from }}" data-fromGroup="{{ item.from_group }}">
|
||||
<a href="/u/{{ item.from }}/" target="_blank">
|
||||
<img src="{{ item.user.avatar }}" class="layui-circle layim-msgbox-avatar">
|
||||
</a>
|
||||
<p class="layim-msgbox-user">
|
||||
<a href="/u/{{ item.from }}/" target="_blank">{{ item.user.username||'' }}</a>
|
||||
<span>{{ item.time }}</span>
|
||||
</p>
|
||||
<p class="layim-msgbox-content">
|
||||
{{ item.content }}
|
||||
<span>{{ item.remark ? '附言: '+item.remark : '' }}</span>
|
||||
</p>
|
||||
<p class="layim-msgbox-btn">
|
||||
<button class="layui-btn layui-btn-small" data-type="agree">同意</button>
|
||||
<button class="layui-btn layui-btn-small layui-btn-primary" data-type="refuse">拒绝</button>
|
||||
</p>
|
||||
</li>
|
||||
{{# } else { }}
|
||||
<li class="layim-msgbox-system">
|
||||
<p><em>系统:</em>{{ item.content }}<span>{{ item.time }}</span></p>
|
||||
</li>
|
||||
{{# }
|
||||
}); }}
|
||||
</textarea>
|
||||
|
||||
<!--
|
||||
上述模版采用了 laytpl 语法
|
||||
-->
|
||||
|
||||
|
||||
<script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script>
|
||||
<script>
|
||||
layui.config({
|
||||
layimPath: '../../' //配置 layim.js 所在目录
|
||||
, layimResPath: '../' //layim 资源文件所在目录
|
||||
}).extend({
|
||||
layim: layui.cache.layimPath + 'layim' //配置 layim 组件所在的路径
|
||||
}).use(['layim', 'flow'], function () {
|
||||
var layim = layui.layim
|
||||
, layer = layui.layer
|
||||
, laytpl = layui.laytpl
|
||||
, $ = layui.jquery
|
||||
, flow = layui.flow
|
||||
|
||||
var cache = {} //用于临时记录请求到的数据
|
||||
|
||||
//请求消息
|
||||
var renderMsg = function (page, callback) {
|
||||
|
||||
//实际部署时,请将下述 getmsg.json 改为你的接口地址
|
||||
|
||||
$.get('getmsg.json', {
|
||||
page: page || 1
|
||||
}, function (res) {
|
||||
if (res.code != 0) {
|
||||
return layer.msg(res.msg)
|
||||
}
|
||||
|
||||
//记录来源用户信息
|
||||
layui.each(res.data, function (index, item) {
|
||||
cache[item.from] = item.user
|
||||
})
|
||||
|
||||
callback && callback(res.data, res.pages)
|
||||
})
|
||||
}
|
||||
|
||||
//消息信息流
|
||||
flow.load({
|
||||
elem: '#LAY_view' //流加载容器
|
||||
, isAuto: false
|
||||
, end: '<li class="layim-msgbox-tips">暂无更多新消息</li>'
|
||||
, done: function (page, next) { //加载下一页
|
||||
renderMsg(page, function (data, pages) {
|
||||
var html = laytpl(LAY_tpl.value).render({
|
||||
data: data
|
||||
, page: page
|
||||
})
|
||||
next(html, page < pages)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
//打开页面即把消息标记为已读
|
||||
/*
|
||||
$.post('/message/read', {
|
||||
type: 1
|
||||
});
|
||||
*/
|
||||
|
||||
//操作
|
||||
var active = {
|
||||
//同意
|
||||
agree: function (othis) {
|
||||
var li = othis.parents('li')
|
||||
, uid = li.data('uid')
|
||||
, from_group = li.data('fromGroup')
|
||||
, user = cache[uid]
|
||||
|
||||
//选择分组
|
||||
parent.layui.layim.setFriendGroup({
|
||||
type: 'friend'
|
||||
, username: user.username
|
||||
, avatar: user.avatar
|
||||
, group: parent.layui.layim.cache().friend //获取好友分组数据
|
||||
, submit: function (group, index) {
|
||||
|
||||
//将好友追加到主面板
|
||||
parent.layui.layim.addList({
|
||||
type: 'friend'
|
||||
, avatar: user.avatar //好友头像
|
||||
, username: user.username //好友昵称
|
||||
, groupid: group //所在的分组id
|
||||
, id: uid //好友ID
|
||||
, sign: user.sign //好友签名
|
||||
})
|
||||
parent.layer.close(index)
|
||||
othis.parent().html('已同意')
|
||||
|
||||
//实际部署时,请开启下述注释,并改成你的接口地址
|
||||
/*
|
||||
$.post('/im/agreeFriend', {
|
||||
uid: uid //对方用户ID
|
||||
,from_group: from_group //对方设定的好友分组
|
||||
,group: group //我设定的好友分组
|
||||
}, function(res){
|
||||
if(res.code != 0){
|
||||
return layer.msg(res.msg);
|
||||
}
|
||||
|
||||
//将好友追加到主面板
|
||||
parent.layui.layim.addList({
|
||||
type: 'friend'
|
||||
,avatar: user.avatar //好友头像
|
||||
,username: user.username //好友昵称
|
||||
,groupid: group //所在的分组id
|
||||
,id: uid //好友ID
|
||||
,sign: user.sign //好友签名
|
||||
});
|
||||
parent.layer.close(index);
|
||||
othis.parent().html('已同意');
|
||||
});
|
||||
*/
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//拒绝
|
||||
, refuse: function (othis) {
|
||||
var li = othis.parents('li')
|
||||
, uid = li.data('uid')
|
||||
|
||||
layer.confirm('确定拒绝吗?', function (index) {
|
||||
layer.close(index)
|
||||
othis.parent().html('<em>已拒绝</em>')
|
||||
|
||||
/*
|
||||
$.post('/im/refuseFriend', {
|
||||
uid: uid //对方用户ID
|
||||
}, function(res){
|
||||
if(res.code != 0){
|
||||
return layer.msg(res.msg);
|
||||
}
|
||||
layer.close(index);
|
||||
othis.parent().html('<em>已拒绝</em>');
|
||||
});
|
||||
*/
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$('body').on('click', '.layui-btn', function () {
|
||||
var othis = $(this), type = othis.data('type')
|
||||
active[type] ? active[type].call(this, othis) : ''
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/res/modules/layim/res/images/default.png
Normal file
BIN
public/res/modules/layim/res/images/default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
1
public/res/modules/layim/res/layim.css
Normal file
1
public/res/modules/layim/res/layim.css
Normal file
File diff suppressed because one or more lines are too long
1
public/res/modules/layim/res/mobile.css
Normal file
1
public/res/modules/layim/res/mobile.css
Normal file
File diff suppressed because one or more lines are too long
BIN
public/res/modules/layim/res/skin/1.jpg
Normal file
BIN
public/res/modules/layim/res/skin/1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
public/res/modules/layim/res/skin/2.jpg
Normal file
BIN
public/res/modules/layim/res/skin/2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
public/res/modules/layim/res/skin/3.jpg
Normal file
BIN
public/res/modules/layim/res/skin/3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user