mirror of
https://github.com/xinliangnote/go-gin-api.git
synced 2024-04-21 12:31:46 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fd9a005d1 | ||
|
|
4530dab87b | ||
|
|
bf4a413bd0 | ||
|
|
79aa86f278 | ||
|
|
866bed5522 | ||
|
|
61cc349cb4 | ||
|
|
6825b4e5b1 | ||
|
|
62d7ac5cd9 | ||
|
|
b630456b90 | ||
|
|
bbe181817c | ||
|
|
15e99398cb | ||
|
|
dd19eab5d5 | ||
|
|
7cdb8ee6b4 | ||
|
|
400e0bb3f2 | ||
|
|
14f6853c2b | ||
|
|
4c37a7e6b5 | ||
|
|
fc7e58db04 | ||
|
|
4e3c2afafd | ||
|
|
63499a432e | ||
|
|
498033753e | ||
|
|
b05ed0b59d | ||
|
|
8ed27cdce1 | ||
|
|
9aa0067e07 | ||
|
|
e2cecae7b1 | ||
|
|
e22593b708 | ||
|
|
05f337088e | ||
|
|
17be120c06 | ||
|
|
77e8d58298 | ||
|
|
f47f57ef3c | ||
|
|
f17f6a74d2 | ||
|
|
382c7d3c8f | ||
|
|
b382919cb5 | ||
|
|
22bfb9ad10 | ||
|
|
4254184098 | ||
|
|
bb09c70a60 | ||
|
|
65873cbfaa | ||
|
|
6af343d0b1 | ||
|
|
62367a041a | ||
|
|
1b013e2023 | ||
|
|
eba8929b4d | ||
|
|
44ca2698cd | ||
|
|
2c5683cd14 | ||
|
|
8ca47f743d | ||
|
|
a3607106d3 | ||
|
|
6d9a07f5dd | ||
|
|
a6947b59ac | ||
|
|
ec3c9f0ccf | ||
|
|
8fbcb5d717 | ||
|
|
6f71852686 | ||
|
|
9cace3337b | ||
|
|
0dbd4b0935 | ||
|
|
1f85d8df61 | ||
|
|
3e1ef9c658 | ||
|
|
8a6a34348f | ||
|
|
66a5e29c9c | ||
|
|
84cc0c9cbc | ||
|
|
3e918fce59 | ||
|
|
36ef904211 | ||
|
|
49ccaf9bec | ||
|
|
4381a9a6f6 | ||
|
|
5e5db8917d | ||
|
|
f7b3dbae5e | ||
|
|
5d7975056c | ||
|
|
499e171d63 | ||
|
|
8d6cd2174e | ||
|
|
3823eee2ca | ||
|
|
0b6b9dd69f | ||
|
|
7327275b7a | ||
|
|
9b5028e9de | ||
|
|
cc8012b09d | ||
|
|
334bfbb4aa | ||
|
|
a4701309cb | ||
|
|
e5ea279f2e | ||
|
|
ad8639fea8 | ||
|
|
b346216f79 | ||
|
|
e1314cf139 | ||
|
|
a1a9208a27 | ||
|
|
4ed8b81c57 | ||
|
|
106d5b65d7 | ||
|
|
d17c58ffe1 | ||
|
|
845ef70a77 | ||
|
|
f32a8a1fb5 | ||
|
|
2466dfcbfc | ||
|
|
f96d33fdef | ||
|
|
f2cd04993f | ||
|
|
f4072dc5fb | ||
|
|
458ebe10c6 | ||
|
|
aa5dec6eeb | ||
|
|
ef8989fe33 | ||
|
|
34f85ad14b | ||
|
|
155f5fca1b | ||
|
|
f8cfc2f16c | ||
|
|
a017831aba |
+19
-1
@@ -1 +1,19 @@
|
||||
.idea
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.idea
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Project runtime log
|
||||
logs/*.log
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# FROM 基于 golang:1.16-alpine
|
||||
FROM golang:1.16-alpine AS builder
|
||||
|
||||
# ENV 设置环境变量
|
||||
ENV GOPATH=/opt/repo
|
||||
ENV GO111MODULE=on
|
||||
ENV GOPROXY=https://goproxy.io,direct
|
||||
|
||||
# COPY 源路径 目标路径
|
||||
COPY . $GOPATH/src/github.com/xinliangnote/go-gin-api
|
||||
|
||||
# RUN 执行 go build .
|
||||
RUN cd $GOPATH/src/github.com/xinliangnote/go-gin-api && go build .
|
||||
|
||||
# FROM 基于 alpine:latest
|
||||
FROM alpine:latest
|
||||
|
||||
# RUN 设置代理镜像
|
||||
RUN echo -e http://mirrors.ustc.edu.cn/alpine/v3.13/main/ > /etc/apk/repositories
|
||||
|
||||
# RUN 设置 Asia/Shanghai 时区
|
||||
RUN apk --no-cache add tzdata && \
|
||||
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
||||
echo "Asia/Shanghai" > /etc/timezone
|
||||
|
||||
# COPY 源路径 目标路径 从镜像中 COPY
|
||||
COPY --from=builder /opt/repo/src/github.com/xinliangnote/go-gin-api /opt
|
||||
|
||||
# EXPOSE 设置端口映射
|
||||
EXPOSE 9999/tcp
|
||||
|
||||
# WORKDIR 设置工作目录
|
||||
WORKDIR /opt
|
||||
|
||||
# CMD 设置启动命令
|
||||
CMD ["./go-gin-api", "-env", "fat"]
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Gin-Gonic
|
||||
Copyright (c) 2020 新亮笔记
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -1,161 +1,51 @@
|
||||

|
||||
## 关于
|
||||
|
||||
## go-gin-api
|
||||
`go-gin-api` 是基于 [Gin](https://github.com/gin-gonic/gin) 进行模块化设计的 API 框架,封装了常用的功能,使用简单,致力于进行快速的业务研发,同时增加了更多限制,约束项目组开发成员,规避混乱无序及自由随意的编码。
|
||||
|
||||
基于 [Gin](https://github.com/gin-gonic/gin) 进行模块化设计的 API 框架,封装了常用的功能,使用简单,致力于进行快速的业务研发。
|
||||
供参考学习,线上使用请谨慎!
|
||||
|
||||
持续更新...
|
||||
集成组件:
|
||||
|
||||
## Features
|
||||
1. 支持 [rate](https://golang.org/x/time/rate) 接口限流
|
||||
1. 支持 panic 异常时邮件通知
|
||||
1. 支持 [cors](https://github.com/rs/cors) 接口跨域
|
||||
1. 支持 [Prometheus](https://github.com/prometheus/client_golang) 指标记录
|
||||
1. 支持 [Swagger](https://github.com/swaggo/gin-swagger) 接口文档生成
|
||||
1. 支持 [GraphQL](https://github.com/99designs/gqlgen) 查询语言
|
||||
1. 支持 trace 项目内部链路追踪
|
||||
1. 支持 [pprof](https://github.com/gin-contrib/pprof) 性能剖析
|
||||
1. 支持 errno 统一定义错误码
|
||||
1. 支持 [zap](https://go.uber.org/zap) 日志收集
|
||||
1. 支持 [viper](https://github.com/spf13/viper) 配置文件解析
|
||||
1. 支持 [gorm](https://gorm.io/gorm) 数据库组件
|
||||
1. 支持 [go-redis](https://github.com/go-redis/redis/v7) 组件
|
||||
1. 支持 RESTful API 返回值规范
|
||||
1. 支持 生成数据表 CURD、控制器方法 等代码生成器
|
||||
1. 支持 [cron](https://github.com/jakecoffman/cron) 定时任务,在后台可界面配置
|
||||
1. 支持 [websocket](https://github.com/gorilla/websocket) 实时通讯,在后台有界面演示
|
||||
1. 支持 web 界面,使用的 [Light Year Admin 模板](https://gitee.com/yinqi/Light-Year-Admin-Using-Iframe)
|
||||
|
||||
- [x] 使用 go modules 初始化项目
|
||||
- [x] 安装 Gin 框架
|
||||
- [x] 性能分析工具(pprof)
|
||||
- [x] 支持优雅地重启或停止
|
||||
- [x] 规划项目目录
|
||||
- [x] 参数验证(validator.v9)
|
||||
- [x] 模型绑定和验证
|
||||
- [x] 自定义验证器
|
||||
- [x] 路由中间件
|
||||
- [x] 签名验证
|
||||
- [x] MD5 组合加密
|
||||
- [x] AES 对称加密
|
||||
- [x] RSA 非对称加密
|
||||
- [x] 日志记录
|
||||
- [x] 异常捕获
|
||||
- [x] 链路追踪(Jaeger)
|
||||
- [x] 限流
|
||||
- [x] 自定义告警
|
||||
- [x] 邮件(gomail)
|
||||
- [ ] 微信
|
||||
- [ ] 短信
|
||||
- [ ] 钉钉
|
||||
- [ ] 存储
|
||||
- [ ] MySQL
|
||||
- [ ] Redis
|
||||
- [ ] MongoDB
|
||||
- [ ] gRPC
|
||||
- [ ] ...
|
||||
|
||||
## Download
|
||||
## 文档索引(可加入交流群)
|
||||
|
||||
```
|
||||
git clone https://github.com/xinliangnote/go-gin-api.git
|
||||
```
|
||||
- 中文文档:[go-gin-api - 语雀](https://www.yuque.com/xinliangnote/go-gin-api/ngc3x5)
|
||||
- English Document:[en.md](https://github.com/xinliangnote/go-gin-api/blob/master/en.md)
|
||||
|
||||
## Quick start
|
||||
## 轻量版
|
||||
|
||||
#### Requirements
|
||||
为了满足开发者对于简单、轻量级 API 框架的需求,开发了 gin-api-mono,旨在提供更便捷的业务开发体验。
|
||||
|
||||
- Go version >= 1.12
|
||||
- Global environment configure (Linux/Mac)
|
||||
相比于 go-gin-api,首先 gin-api-mono 去掉了一些集成的功能和界面,使得整个框架更加简洁、轻量。其次 gin-api-mono 对框架代码进行了升级,以确保其在性能和稳定性方面的优势。这样,开发者就可以更灵活地选择所需的功能,并获得更好的性能和稳定性。
|
||||
|
||||
```
|
||||
export GO111MODULE=on
|
||||
export GOPROXY=https://goproxy.io
|
||||
```
|
||||
详见链接:https://xiaobot.net/post/e9f7ef4c-81b1-4ffc-9053-bec55c3abb12
|
||||
|
||||
#### Build & Run
|
||||
## 其他
|
||||
|
||||
```
|
||||
cd go-gin-api
|
||||
查看 Jaeger 链路追踪 Demo 代码,请查看 [v1.0 版](https://github.com/xinliangnote/go-gin-api/releases/tag/v1.0) ,链接地址:http://127.0.0.1:9999/jaeger_test
|
||||
|
||||
go run main.go
|
||||
调用的其他服务端 Demo 代码为 [https://github.com/xinliangnote/go-jaeger-demo](https://github.com/xinliangnote/go-jaeger-demo)
|
||||
|
||||
输出如下,表示 Http Server 启动成功。
|
||||
|-----------------------------------|
|
||||
| go-gin-api |
|
||||
|-----------------------------------|
|
||||
| Go Http Server Start Successful |
|
||||
| Port:9999 Pid:xxxxx |
|
||||
|-----------------------------------|
|
||||
```
|
||||
## 联系作者
|
||||
|
||||
#### HTTP Demo
|
||||
|
||||
```
|
||||
curl -X POST http://127.0.0.1:9999/product
|
||||
```
|
||||
|
||||
#### Jaeger Demo
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:9999/jaeger_test
|
||||
```
|
||||
|
||||
服务端测试代码:
|
||||
|
||||
- [https://github.com/xinliangnote/go-jaeger-demo](https://github.com/xinliangnote/go-jaeger-demo)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### pprof
|
||||
|
||||
```go
|
||||
http://127.0.0.1:9999/debug/pprof
|
||||
```
|
||||
|
||||
说明文档:
|
||||
|
||||
```go
|
||||
// 查看 CPU 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/profile
|
||||
...
|
||||
(pprof)
|
||||
|
||||
//输入 web,生成 svg 文件。
|
||||
//输入 png,生成 png 文件。
|
||||
//输入 top,查看排名前 20 的信息。
|
||||
//查看更多命令,请执行 pprof help。
|
||||
```
|
||||
|
||||
其他同理,比如:
|
||||
|
||||
```go
|
||||
// 查看 内存 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/heap
|
||||
|
||||
// 查看 协程 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/goroutine
|
||||
|
||||
// 查看 锁 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/mutex
|
||||
```
|
||||
如果还想查看火焰图,请执行如下命令:
|
||||
|
||||
```go
|
||||
// 1.下载 pprof 工具
|
||||
go get -u github.com/google/pprof
|
||||
|
||||
// 2.启动可视化界面
|
||||
pprof -http=:9998 xxx.cpu.prof
|
||||
|
||||
// 3.查看可视化界面
|
||||
http://127.0.0.1:9998/ui/
|
||||
```
|
||||
|
||||
## Dependence
|
||||
|
||||
- WEB 框架:github.com/gin-gonic/gin
|
||||
- 链路追踪:github.com/jaegertracing/jaeger-client-go
|
||||
- 限流:golang.org/x/time/rate
|
||||
- 工具包:github.com/xinliangnote/go-util
|
||||
|
||||
## Document
|
||||
|
||||
- [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A)
|
||||
- [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA)
|
||||
- [3. 路由中间件 - 日志记录](https://mp.weixin.qq.com/s/eTygPXnrYM2xfrRQyfn8Tg)
|
||||
- [4. 路由中间件 - 捕获异常](https://mp.weixin.qq.com/s/SconDXB_x7Gan6T0Awdh9A)
|
||||
- [5. 路由中间件 - Jaeger 链路追踪(理论篇)](https://mp.weixin.qq.com/s/28UBEsLOAHDv530ePilKQA)
|
||||
- [6. 路由中间件 - Jaeger 链路追踪(实战篇)](https://mp.weixin.qq.com/s/Ea28475_UTNaM9RNfgPqJA)
|
||||
- [7. 路由中间件 - 签名验证](https://mp.weixin.qq.com/s/0cozELotcpX3Gd6WPJiBbQ)
|
||||
|
||||
## Learning together
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package config
|
||||
|
||||
var (
|
||||
ApiAuthConfig = map[string] map[string]string {
|
||||
|
||||
// 调用方
|
||||
"DEMO" : {
|
||||
"md5" : "IgkibX71IEf382PT",
|
||||
"aes" : "IgkibX71IEf382PT",
|
||||
"rsa" : "rsa/public.pem",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
AppMode = "release" //debug or release
|
||||
AppPort = ":9999"
|
||||
AppName = "go-gin-api"
|
||||
|
||||
// 签名超时时间
|
||||
AppSignExpiry = "120"
|
||||
|
||||
// RSA Private File
|
||||
AppRsaPrivateFile = "rsa/private.pem"
|
||||
|
||||
// 超时时间
|
||||
AppReadTimeout = 120
|
||||
AppWriteTimeout = 120
|
||||
|
||||
// 日志文件
|
||||
AppAccessLogName = "log/" + AppName + "-access.log"
|
||||
AppErrorLogName = "log/" + AppName + "-error.log"
|
||||
AppGrpcLogName = "log/" + AppName + "-grpc.log"
|
||||
|
||||
// 系统告警邮箱信息
|
||||
SystemEmailUser = "xinliangnote@163.com"
|
||||
SystemEmailPass = "" //密码或授权码
|
||||
SystemEmailHost = "smtp.163.com"
|
||||
SystemEmailPort = 465
|
||||
|
||||
// 告警接收人
|
||||
ErrorNotifyUser = "xinliangnote@163.com"
|
||||
|
||||
// 告警开关 1=开通 -1=关闭
|
||||
ErrorNotifyOpen = -1
|
||||
|
||||
// Jaeger 配置信息
|
||||
JaegerHostPort = "127.0.0.1:6831"
|
||||
|
||||
// Jaeger 配置开关 1=开通 -1=关闭
|
||||
JaegerOpen = 1
|
||||
)
|
||||
@@ -1,57 +0,0 @@
|
||||
package jaeger_conn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/model/proto/listen"
|
||||
"go-gin-api/app/model/proto/read"
|
||||
"go-gin-api/app/model/proto/speak"
|
||||
"go-gin-api/app/model/proto/write"
|
||||
"go-gin-api/app/util/grpc_client"
|
||||
"go-gin-api/app/util/request"
|
||||
"go-gin-api/app/util/response"
|
||||
)
|
||||
|
||||
func JaegerTest(c *gin.Context) {
|
||||
|
||||
// 调用 gRPC 服务
|
||||
conn := grpc_client.CreateServiceListenConn(c)
|
||||
grpcListenClient := listen.NewListenClient(conn)
|
||||
resListen, _ := grpcListenClient.ListenData(context.Background(), &listen.Request{Name: "listen"})
|
||||
|
||||
// 调用 gRPC 服务
|
||||
conn = grpc_client.CreateServiceSpeakConn(c)
|
||||
grpcSpeakClient := speak.NewSpeakClient(conn)
|
||||
resSpeak, _ := grpcSpeakClient.SpeakData(context.Background(), &speak.Request{Name: "speak"})
|
||||
|
||||
// 调用 gRPC 服务
|
||||
conn = grpc_client.CreateServiceReadConn(c)
|
||||
grpcReadClient := read.NewReadClient(conn)
|
||||
resRead, _ := grpcReadClient.ReadData(context.Background(), &read.Request{Name: "read"})
|
||||
|
||||
// 调用 gRPC 服务
|
||||
conn = grpc_client.CreateServiceWriteConn(c)
|
||||
grpcWriteClient := write.NewWriteClient(conn)
|
||||
resWrite, _ := grpcWriteClient.WriteData(context.Background(), &write.Request{Name: "write"})
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
// 调用 HTTP 服务
|
||||
resHttpGet := ""
|
||||
_, err := request.HttpGet("http://localhost:9905/sing", c)
|
||||
if err == nil {
|
||||
resHttpGet = "[HttpGetOk]"
|
||||
}
|
||||
|
||||
// 业务处理...
|
||||
|
||||
msg := resListen.Message + "-" +
|
||||
resSpeak.Message + "-" +
|
||||
resRead.Message + "-" +
|
||||
resWrite.Message + "-" +
|
||||
resHttpGet
|
||||
|
||||
|
||||
utilGin := response.Gin{Ctx:c}
|
||||
utilGin.Response(1, msg, nil)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package param_bind
|
||||
|
||||
type ProductAdd struct {
|
||||
Name string `form:"name" json:"name" validate:"required,NameValid"`
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package param_verify
|
||||
|
||||
import (
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
func NameValid(fl validator.FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
if val == "admin" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/controller/param_bind"
|
||||
"go-gin-api/app/controller/param_verify"
|
||||
"go-gin-api/app/util/bind"
|
||||
"go-gin-api/app/util/response"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
// 新增
|
||||
func Add(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
|
||||
// 参数绑定
|
||||
s, e := bind.Bind(¶m_bind.ProductAdd{}, c)
|
||||
if e != nil {
|
||||
utilGin.Response(-1, e.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
validate := validator.New()
|
||||
|
||||
// 注册自定义验证
|
||||
_ = validate.RegisterValidation("NameValid", param_verify.NameValid)
|
||||
|
||||
if err := validate.Struct(s); err != nil {
|
||||
utilGin.Response(-1, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 业务处理...
|
||||
|
||||
utilGin.Response(1, "success", nil)
|
||||
}
|
||||
|
||||
// 编辑
|
||||
func Edit(c *gin.Context) {
|
||||
fmt.Println(c.Request.RequestURI)
|
||||
}
|
||||
|
||||
// 删除
|
||||
func Delete(c *gin.Context) {
|
||||
fmt.Println(c.Request.RequestURI)
|
||||
}
|
||||
|
||||
// 详情
|
||||
|
||||
func Detail(c *gin.Context) {
|
||||
fmt.Println(c.Request.RequestURI)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/aes"
|
||||
"github.com/xinliangnote/go-util/md5"
|
||||
"github.com/xinliangnote/go-util/rsa"
|
||||
"go-gin-api/app/util/response"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Md5Test(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
appSecret := "IgkibX71IEf382PT"
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 1000000
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
md5.MD5(appSecret + encryptStr + appSecret)
|
||||
|
||||
// 验证签名
|
||||
md5.MD5(appSecret + encryptStr + appSecret)
|
||||
}
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
|
||||
}
|
||||
|
||||
func AesTest(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
appSecret := "IgkibX71IEf382PT"
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 1000000
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
sn, _ := aes.Encrypt(encryptStr, []byte(appSecret), appSecret)
|
||||
|
||||
// 验证签名
|
||||
aes.Decrypt(sn, []byte(appSecret), appSecret)
|
||||
}
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
|
||||
}
|
||||
|
||||
func RsaTest(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 500
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
sn, _ := rsa.PublicEncrypt(encryptStr, "rsa/public.pem")
|
||||
|
||||
// 验证签名
|
||||
rsa.PrivateDecrypt(sn, "rsa/private.pem")
|
||||
}
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: listen.proto
|
||||
|
||||
package listen
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Request 请求结构
|
||||
type Request struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_f05da38a4a3e5177, []int{0}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
func (m *Request) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Response 响应结构
|
||||
type Response struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_f05da38a4a3e5177, []int{1}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "listen.Request")
|
||||
proto.RegisterType((*Response)(nil), "listen.Response")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("listen.proto", fileDescriptor_f05da38a4a3e5177) }
|
||||
|
||||
var fileDescriptor_f05da38a4a3e5177 = []byte{
|
||||
// 133 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xc9, 0xc9, 0x2c, 0x2e,
|
||||
0x49, 0xcd, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0xf0, 0x94, 0x64, 0xb9, 0xd8,
|
||||
0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25,
|
||||
0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82,
|
||||
0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12,
|
||||
0x18, 0xd7, 0xc8, 0x9a, 0x8b, 0xcd, 0x07, 0x6c, 0x9c, 0x90, 0x21, 0x17, 0x17, 0x84, 0xe5, 0x92,
|
||||
0x58, 0x92, 0x28, 0xc4, 0xaf, 0x07, 0xb5, 0x13, 0x6a, 0x85, 0x94, 0x00, 0x42, 0x00, 0x62, 0xa8,
|
||||
0x12, 0x43, 0x12, 0x1b, 0xd8, 0x41, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xbf, 0x9d,
|
||||
0x0d, 0xa0, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// ListenClient is the client API for Listen service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type ListenClient interface {
|
||||
// 定义 ListenData 方法
|
||||
ListenData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
}
|
||||
|
||||
type listenClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewListenClient(cc *grpc.ClientConn) ListenClient {
|
||||
return &listenClient{cc}
|
||||
}
|
||||
|
||||
func (c *listenClient) ListenData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/listen.Listen/ListenData", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ListenServer is the server API for Listen service.
|
||||
type ListenServer interface {
|
||||
// 定义 ListenData 方法
|
||||
ListenData(context.Context, *Request) (*Response, error)
|
||||
}
|
||||
|
||||
// UnimplementedListenServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedListenServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedListenServer) ListenData(ctx context.Context, req *Request) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListenData not implemented")
|
||||
}
|
||||
|
||||
func RegisterListenServer(s *grpc.Server, srv ListenServer) {
|
||||
s.RegisterService(&_Listen_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Listen_ListenData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ListenServer).ListenData(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/listen.Listen/ListenData",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ListenServer).ListenData(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Listen_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "listen.Listen",
|
||||
HandlerType: (*ListenServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ListenData",
|
||||
Handler: _Listen_ListenData_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "listen.proto",
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: read.proto
|
||||
|
||||
package read
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Request 请求结构
|
||||
type Request struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7b10ec61df6818dd, []int{0}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
func (m *Request) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Response 响应结构
|
||||
type Response struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7b10ec61df6818dd, []int{1}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "read.Request")
|
||||
proto.RegisterType((*Response)(nil), "read.Response")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("read.proto", fileDescriptor_7b10ec61df6818dd) }
|
||||
|
||||
var fileDescriptor_7b10ec61df6818dd = []byte{
|
||||
// 132 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x4a, 0x4d, 0x4c,
|
||||
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0x64, 0xb9, 0xd8, 0x83, 0x52,
|
||||
0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15,
|
||||
0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc, 0xbc,
|
||||
0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18, 0xd7,
|
||||
0xc8, 0x98, 0x8b, 0x25, 0x28, 0x35, 0x31, 0x45, 0x48, 0x1b, 0xa4, 0x3a, 0x31, 0xc5, 0x25, 0xb1,
|
||||
0x24, 0x51, 0x88, 0x57, 0x0f, 0x6c, 0x17, 0xd4, 0x70, 0x29, 0x3e, 0x18, 0x17, 0x62, 0x98, 0x12,
|
||||
0x43, 0x12, 0x1b, 0xd8, 0x19, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x42, 0x4a, 0x9b, 0x2d,
|
||||
0x94, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// ReadClient is the client API for Read service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type ReadClient interface {
|
||||
// 定义方法
|
||||
ReadData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
}
|
||||
|
||||
type readClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewReadClient(cc *grpc.ClientConn) ReadClient {
|
||||
return &readClient{cc}
|
||||
}
|
||||
|
||||
func (c *readClient) ReadData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/read.Read/ReadData", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ReadServer is the server API for Read service.
|
||||
type ReadServer interface {
|
||||
// 定义方法
|
||||
ReadData(context.Context, *Request) (*Response, error)
|
||||
}
|
||||
|
||||
// UnimplementedReadServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedReadServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedReadServer) ReadData(ctx context.Context, req *Request) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ReadData not implemented")
|
||||
}
|
||||
|
||||
func RegisterReadServer(s *grpc.Server, srv ReadServer) {
|
||||
s.RegisterService(&_Read_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Read_ReadData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ReadServer).ReadData(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/read.Read/ReadData",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ReadServer).ReadData(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Read_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "read.Read",
|
||||
HandlerType: (*ReadServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ReadData",
|
||||
Handler: _Read_ReadData_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "read.proto",
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: speak.proto
|
||||
|
||||
package speak
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Request 请求结构
|
||||
type Request struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_3c7d3e2f29338937, []int{0}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
func (m *Request) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Response 响应结构
|
||||
type Response struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_3c7d3e2f29338937, []int{1}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "speak.Request")
|
||||
proto.RegisterType((*Response)(nil), "speak.Response")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("speak.proto", fileDescriptor_3c7d3e2f29338937) }
|
||||
|
||||
var fileDescriptor_3c7d3e2f29338937 = []byte{
|
||||
// 132 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x2e, 0x48, 0x4d,
|
||||
0xcc, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x64, 0xb9, 0xd8, 0x83,
|
||||
0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18,
|
||||
0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc,
|
||||
0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18,
|
||||
0xd7, 0xc8, 0x9c, 0x8b, 0x35, 0x18, 0x64, 0x9a, 0x90, 0x1e, 0x17, 0x27, 0x98, 0xe1, 0x92, 0x58,
|
||||
0x92, 0x28, 0xc4, 0xa7, 0x07, 0xb1, 0x0f, 0x6a, 0xbe, 0x14, 0x3f, 0x9c, 0x0f, 0x31, 0x50, 0x89,
|
||||
0x21, 0x89, 0x0d, 0xec, 0x16, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4d, 0x04, 0x6f, 0x05,
|
||||
0x9a, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// SpeakClient is the client API for Speak service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type SpeakClient interface {
|
||||
// 定义方法
|
||||
SpeakData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
}
|
||||
|
||||
type speakClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewSpeakClient(cc *grpc.ClientConn) SpeakClient {
|
||||
return &speakClient{cc}
|
||||
}
|
||||
|
||||
func (c *speakClient) SpeakData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/speak.Speak/SpeakData", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SpeakServer is the server API for Speak service.
|
||||
type SpeakServer interface {
|
||||
// 定义方法
|
||||
SpeakData(context.Context, *Request) (*Response, error)
|
||||
}
|
||||
|
||||
// UnimplementedSpeakServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedSpeakServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedSpeakServer) SpeakData(ctx context.Context, req *Request) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SpeakData not implemented")
|
||||
}
|
||||
|
||||
func RegisterSpeakServer(s *grpc.Server, srv SpeakServer) {
|
||||
s.RegisterService(&_Speak_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Speak_SpeakData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SpeakServer).SpeakData(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/speak.Speak/SpeakData",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SpeakServer).SpeakData(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Speak_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "speak.Speak",
|
||||
HandlerType: (*SpeakServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SpeakData",
|
||||
Handler: _Speak_SpeakData_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "speak.proto",
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: write.proto
|
||||
|
||||
package write
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Request 请求结构
|
||||
type Request struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_67966b2b12a73214, []int{0}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
func (m *Request) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Response 响应结构
|
||||
type Response struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_67966b2b12a73214, []int{1}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "write.Request")
|
||||
proto.RegisterType((*Response)(nil), "write.Response")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("write.proto", fileDescriptor_67966b2b12a73214) }
|
||||
|
||||
var fileDescriptor_67966b2b12a73214 = []byte{
|
||||
// 132 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x2f, 0xca, 0x2c,
|
||||
0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x64, 0xb9, 0xd8, 0x83,
|
||||
0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18,
|
||||
0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc,
|
||||
0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18,
|
||||
0xd7, 0xc8, 0x9c, 0x8b, 0x35, 0x1c, 0x64, 0x9a, 0x90, 0x1e, 0x17, 0x27, 0x98, 0xe1, 0x92, 0x58,
|
||||
0x92, 0x28, 0xc4, 0xa7, 0x07, 0xb1, 0x0f, 0x6a, 0xbe, 0x14, 0x3f, 0x9c, 0x0f, 0x31, 0x50, 0x89,
|
||||
0x21, 0x89, 0x0d, 0xec, 0x16, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb3, 0x7d, 0xc4, 0x7d,
|
||||
0x9a, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// WriteClient is the client API for Write service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type WriteClient interface {
|
||||
// 定义方法
|
||||
WriteData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
|
||||
}
|
||||
|
||||
type writeClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewWriteClient(cc *grpc.ClientConn) WriteClient {
|
||||
return &writeClient{cc}
|
||||
}
|
||||
|
||||
func (c *writeClient) WriteData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
|
||||
out := new(Response)
|
||||
err := c.cc.Invoke(ctx, "/write.Write/WriteData", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// WriteServer is the server API for Write service.
|
||||
type WriteServer interface {
|
||||
// 定义方法
|
||||
WriteData(context.Context, *Request) (*Response, error)
|
||||
}
|
||||
|
||||
// UnimplementedWriteServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedWriteServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedWriteServer) WriteData(ctx context.Context, req *Request) (*Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method WriteData not implemented")
|
||||
}
|
||||
|
||||
func RegisterWriteServer(s *grpc.Server, srv WriteServer) {
|
||||
s.RegisterService(&_Write_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Write_WriteData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Request)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(WriteServer).WriteData(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/write.Write/WriteData",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WriteServer).WriteData(ctx, req.(*Request))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Write_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "write.Write",
|
||||
HandlerType: (*WriteServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "WriteData",
|
||||
Handler: _Write_WriteData_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "write.proto",
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package exception
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/mail"
|
||||
"github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
|
||||
DebugStack := ""
|
||||
for _, v := range strings.Split(string(debug.Stack()), "\n") {
|
||||
DebugStack += v + "<br>"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("【重要错误】%s 项目出错了!", config.AppName)
|
||||
|
||||
body := strings.ReplaceAll(MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", err))
|
||||
body = strings.ReplaceAll(body, "{RequestTime}", time.GetCurrentDate())
|
||||
body = strings.ReplaceAll(body, "{RequestURL}", c.Request.Method + " " + c.Request.Host + c.Request.RequestURI)
|
||||
body = strings.ReplaceAll(body, "{RequestUA}", c.Request.UserAgent())
|
||||
body = strings.ReplaceAll(body, "{RequestIP}", c.ClientIP())
|
||||
body = strings.ReplaceAll(body, "{DebugStack}", DebugStack)
|
||||
|
||||
options := &mail.Options{
|
||||
MailHost : config.SystemEmailHost,
|
||||
MailPort : config.SystemEmailPort,
|
||||
MailUser : config.SystemEmailUser,
|
||||
MailPass : config.SystemEmailPass,
|
||||
MailTo : config.ErrorNotifyUser,
|
||||
Subject : subject,
|
||||
Body : body,
|
||||
}
|
||||
_ = mail.Send(options)
|
||||
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(500, "系统异常,请联系管理员!", nil)
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -1,946 +0,0 @@
|
||||
package exception
|
||||
|
||||
var MailTemplate = `<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="content-wrap" style="margin: 0px auto; overflow: hidden; padding-top: 15px; background-color: rgb(255, 255, 255); width: 600px;">
|
||||
<!---->
|
||||
<div>
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(62, 207, 88); background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: center; color: rgb(255, 255, 255); font-size: 16px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">系统告警</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
|
||||
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="padding-top: 40px; width: 600px; background-image: url(""); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(255, 255, 255); background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
|
||||
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
<td style="width: 20%; max-width: 20%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 120px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 120px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 13px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: center; color: rgb(51, 51, 51); font-size: 16px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">告警</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
|
||||
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 6px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(188, 12, 39); font-size: 21px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">{ErrorMsg}</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
|
||||
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="padding-top: 15px; width: 600px; background-image: url(""); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(255, 255, 255); background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
|
||||
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
<td style="width: 20%; max-width: 20%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 120px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 120px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 13px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: center; color: rgb(51, 51, 51); font-size: 16px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">详情</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
|
||||
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(241, 245, 240); background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div columnnumber="3">
|
||||
<div>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; border: 0px;"><a target="_blank" href="javascript:;" style="cursor: default;">
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">请求时间:</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">{RequestTime}</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">请求地址:</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">{RequestURL}</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">请求 UA:</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">{RequestUA}</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">请求 IP:</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">{RequestIP}</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">DebugStack:</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td border="0">
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">{DebugStack}</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
|
||||
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="padding-top: 15px; width: 600px; background-image: url(""); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(65, 207, 88); background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 0px; word-break: break-word;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="full">
|
||||
<div style="margin: 0px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(""); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
|
||||
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size: 0px; padding: 20px 10px; word-break: break-word;">
|
||||
<div class="text" style="margin: 0px; text-align: left; color: rgb(255, 255, 255); font-size: 12px;">
|
||||
<div>
|
||||
<p style="line-height: 20px; margin: 0px;">请注意,该邮件地址不接收回复邮件。</p>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
@@ -1,39 +0,0 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/jaeger_trace"
|
||||
)
|
||||
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
if config.JaegerOpen == 1 {
|
||||
|
||||
var parentSpan opentracing.Span
|
||||
|
||||
tracer, closer := jaeger_trace.NewJaegerTracer(config.AppName, config.JaegerHostPort)
|
||||
defer closer.Close()
|
||||
|
||||
spCtx, err := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
|
||||
if err != nil {
|
||||
parentSpan = tracer.StartSpan(c.Request.URL.Path)
|
||||
defer parentSpan.Finish()
|
||||
} else {
|
||||
parentSpan = opentracing.StartSpan(
|
||||
c.Request.URL.Path,
|
||||
opentracing.ChildOf(spCtx),
|
||||
opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
|
||||
ext.SpanKindRPCServer,
|
||||
)
|
||||
defer parentSpan.Finish()
|
||||
}
|
||||
c.Set("Tracer", tracer)
|
||||
c.Set("ParentSpanContext", parentSpan.Context())
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/util/response"
|
||||
"golang.org/x/time/rate"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetUp (maxBurstSize int) gin.HandlerFunc {
|
||||
|
||||
limiter := rate.NewLimiter(rate.Every(time.Second*1), maxBurstSize)
|
||||
return func(c *gin.Context) {
|
||||
if limiter.Allow() {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
fmt.Println("Too many requests")
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(-1, "Too many requests", nil)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
jsonUtil "github.com/xinliangnote/go-util/json"
|
||||
"github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type bodyLogWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
var accessChannel = make(chan string, 100)
|
||||
|
||||
func (w bodyLogWriter) Write(b []byte) (int, error) {
|
||||
w.body.Write(b)
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (w bodyLogWriter) WriteString(s string) (int, error) {
|
||||
w.body.WriteString(s)
|
||||
return w.ResponseWriter.WriteString(s)
|
||||
}
|
||||
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
go handleAccessChannel()
|
||||
|
||||
return func(c *gin.Context) {
|
||||
bodyLogWriter := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
|
||||
c.Writer = bodyLogWriter
|
||||
|
||||
// 开始时间
|
||||
startTime := time.GetCurrentMilliUnix()
|
||||
|
||||
// 处理请求
|
||||
c.Next()
|
||||
|
||||
responseBody := bodyLogWriter.body.String()
|
||||
|
||||
var responseCode int
|
||||
var responseMsg string
|
||||
var responseData interface{}
|
||||
|
||||
if responseBody != "" {
|
||||
res := response.Response{}
|
||||
err := json.Unmarshal([]byte(responseBody), &res)
|
||||
if err == nil {
|
||||
responseCode = res.Code
|
||||
responseMsg = res.Message
|
||||
responseData = res.Data
|
||||
}
|
||||
}
|
||||
|
||||
// 结束时间
|
||||
endTime := time.GetCurrentMilliUnix()
|
||||
|
||||
if c.Request.Method == "POST" {
|
||||
_ = c.Request.ParseForm()
|
||||
}
|
||||
|
||||
// 日志格式
|
||||
accessLogMap := make(map[string]interface{})
|
||||
|
||||
accessLogMap["request_time"] = startTime
|
||||
accessLogMap["request_method"] = c.Request.Method
|
||||
accessLogMap["request_uri"] = c.Request.RequestURI
|
||||
accessLogMap["request_proto"] = c.Request.Proto
|
||||
accessLogMap["request_ua"] = c.Request.UserAgent()
|
||||
accessLogMap["request_referer"] = c.Request.Referer()
|
||||
accessLogMap["request_post_data"] = c.Request.PostForm.Encode()
|
||||
accessLogMap["request_client_ip"] = c.ClientIP()
|
||||
|
||||
accessLogMap["response_time"] = endTime
|
||||
accessLogMap["response_code"] = responseCode
|
||||
accessLogMap["response_msg"] = responseMsg
|
||||
accessLogMap["response_data"] = responseData
|
||||
|
||||
accessLogMap["cost_time"] = fmt.Sprintf("%vms", endTime-startTime)
|
||||
|
||||
accessLogJson, _ := jsonUtil.Encode(accessLogMap)
|
||||
accessChannel <- accessLogJson
|
||||
}
|
||||
}
|
||||
|
||||
func handleAccessChannel() {
|
||||
if f, err := os.OpenFile(config.AppAccessLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
for accessLog := range accessChannel {
|
||||
_, _ = f.WriteString(accessLog + "\n")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package requestid
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/uuid"
|
||||
)
|
||||
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
requestId := c.Request.Header.Get("X-Request-Id")
|
||||
if requestId == "" {
|
||||
requestId = uuid.GenUUID()
|
||||
}
|
||||
c.Set("X-Request-Id", requestId)
|
||||
c.Writer.Header().Set("X-Request-Id", requestId)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package sign_aes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/aes"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppSecret string
|
||||
|
||||
// AES 对称加密
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
|
||||
sign, err := verifySign(c)
|
||||
|
||||
if sign != nil {
|
||||
utilGin.Response(-1, "Debug Sign", sign)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utilGin.Response(-1, err.Error(), sign)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
func verifySign(c *gin.Context) (map[string]string, error) {
|
||||
_ = c.Request.ParseForm()
|
||||
req := c.Request.Form
|
||||
debug := strings.Join(c.Request.Form["debug"], "")
|
||||
ak := strings.Join(c.Request.Form["ak"], "")
|
||||
sn := strings.Join(c.Request.Form["sn"], "")
|
||||
ts := strings.Join(c.Request.Form["ts"], "")
|
||||
|
||||
// 验证来源
|
||||
value, ok := config.ApiAuthConfig[ak]
|
||||
if ok {
|
||||
AppSecret = value["aes"]
|
||||
} else {
|
||||
return nil, errors.New("ak Error")
|
||||
}
|
||||
|
||||
if debug == "1" {
|
||||
currentUnix := timeUtil.GetCurrentUnix()
|
||||
req.Set("ts", strconv.FormatInt(currentUnix, 10))
|
||||
|
||||
sn, err := createSign(req)
|
||||
if err != nil {
|
||||
return nil, errors.New("sn Exception")
|
||||
}
|
||||
|
||||
res := map[string]string{
|
||||
"ts": strconv.FormatInt(currentUnix, 10),
|
||||
"sn": sn,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 验证过期时间
|
||||
timestamp := time.Now().Unix()
|
||||
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
|
||||
tsInt, _ := strconv.ParseInt(ts, 10, 64)
|
||||
if tsInt > timestamp || timestamp - tsInt >= exp {
|
||||
return nil, errors.New("ts Error")
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if sn == "" {
|
||||
return nil, errors.New("sn Error")
|
||||
}
|
||||
|
||||
decryptStr, decryptErr := aes.Decrypt(sn, []byte(AppSecret), AppSecret)
|
||||
if decryptErr != nil {
|
||||
return nil, errors.New(decryptErr.Error())
|
||||
}
|
||||
if decryptStr != createEncryptStr(req) {
|
||||
return nil, errors.New("sn Error")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 创建签名
|
||||
func createSign(params url.Values) (string, error) {
|
||||
return aes.Encrypt(createEncryptStr(params), []byte(AppSecret), AppSecret)
|
||||
}
|
||||
|
||||
func createEncryptStr(params url.Values) string {
|
||||
var key []string
|
||||
var str = ""
|
||||
for k := range params {
|
||||
if k != "sn" && k != "debug" {
|
||||
key = append(key, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(key)
|
||||
for i := 0; i < len(key); i++ {
|
||||
if i == 0 {
|
||||
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
|
||||
} else {
|
||||
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package sign_md5
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/md5"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppSecret string
|
||||
|
||||
// MD5 组合加密
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
|
||||
sign, err := verifySign(c)
|
||||
|
||||
if sign != nil {
|
||||
utilGin.Response(-1, "Debug Sign", sign)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utilGin.Response(-1, err.Error(), sign)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
func verifySign(c *gin.Context) (map[string]string, error) {
|
||||
_ = c.Request.ParseForm()
|
||||
req := c.Request.Form
|
||||
debug := strings.Join(c.Request.Form["debug"], "")
|
||||
ak := strings.Join(c.Request.Form["ak"], "")
|
||||
sn := strings.Join(c.Request.Form["sn"], "")
|
||||
ts := strings.Join(c.Request.Form["ts"], "")
|
||||
|
||||
// 验证来源
|
||||
value, ok := config.ApiAuthConfig[ak]
|
||||
if ok {
|
||||
AppSecret = value["md5"]
|
||||
} else {
|
||||
return nil, errors.New("ak Error")
|
||||
}
|
||||
|
||||
if debug == "1" {
|
||||
currentUnix := timeUtil.GetCurrentUnix()
|
||||
req.Set("ts", strconv.FormatInt(currentUnix, 10))
|
||||
res := map[string]string{
|
||||
"ts": strconv.FormatInt(currentUnix, 10),
|
||||
"sn": createSign(req),
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 验证过期时间
|
||||
timestamp := time.Now().Unix()
|
||||
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
|
||||
tsInt, _ := strconv.ParseInt(ts, 10, 64)
|
||||
if tsInt > timestamp || timestamp - tsInt >= exp {
|
||||
return nil, errors.New("ts Error")
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if sn == "" || sn != createSign(req) {
|
||||
return nil, errors.New("sn Error")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 创建签名
|
||||
func createSign(params url.Values) string {
|
||||
// 自定义 MD5 组合
|
||||
return md5.MD5(AppSecret + createEncryptStr(params) + AppSecret)
|
||||
}
|
||||
|
||||
func createEncryptStr(params url.Values) string {
|
||||
var key []string
|
||||
var str = ""
|
||||
for k := range params {
|
||||
if k != "sn" && k != "debug" {
|
||||
key = append(key, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(key)
|
||||
for i := 0; i < len(key); i++ {
|
||||
if i == 0 {
|
||||
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
|
||||
} else {
|
||||
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package sign_rsa
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/rsa"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppSecret string
|
||||
|
||||
// RSA 非对称加密
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
|
||||
sign, err := verifySign(c)
|
||||
|
||||
if sign != nil {
|
||||
utilGin.Response(-1, "Debug Sign", sign)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utilGin.Response(-1, err.Error(), sign)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
func verifySign(c *gin.Context) (map[string]string, error) {
|
||||
_ = c.Request.ParseForm()
|
||||
req := c.Request.Form
|
||||
debug := strings.Join(c.Request.Form["debug"], "")
|
||||
ak := strings.Join(c.Request.Form["ak"], "")
|
||||
sn := strings.Join(c.Request.Form["sn"], "")
|
||||
ts := strings.Join(c.Request.Form["ts"], "")
|
||||
|
||||
// 验证来源
|
||||
value, ok := config.ApiAuthConfig[ak]
|
||||
if ok {
|
||||
AppSecret = value["rsa"]
|
||||
} else {
|
||||
return nil, errors.New("ak Error")
|
||||
}
|
||||
|
||||
if debug == "1" {
|
||||
currentUnix := timeUtil.GetCurrentUnix()
|
||||
req.Set("ts", strconv.FormatInt(currentUnix, 10))
|
||||
|
||||
sn, err := createSign(req)
|
||||
if err != nil {
|
||||
return nil, errors.New("sn Exception")
|
||||
}
|
||||
|
||||
res := map[string]string{
|
||||
"ts": strconv.FormatInt(currentUnix, 10),
|
||||
"sn": sn,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 验证过期时间
|
||||
timestamp := time.Now().Unix()
|
||||
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
|
||||
tsInt, _ := strconv.ParseInt(ts, 10, 64)
|
||||
if tsInt > timestamp || timestamp - tsInt >= exp {
|
||||
return nil, errors.New("ts Error")
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if sn == "" {
|
||||
return nil, errors.New("sn Error")
|
||||
}
|
||||
|
||||
decryptStr, decryptErr := rsa.PrivateDecrypt(sn, config.AppRsaPrivateFile)
|
||||
if decryptErr != nil {
|
||||
return nil, errors.New(decryptErr.Error())
|
||||
}
|
||||
if decryptStr != createEncryptStr(req) {
|
||||
return nil, errors.New("sn Error")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 创建签名
|
||||
func createSign(params url.Values) (string, error) {
|
||||
return rsa.PublicEncrypt(createEncryptStr(params), AppSecret)
|
||||
}
|
||||
|
||||
func createEncryptStr(params url.Values) string {
|
||||
var key []string
|
||||
var str = ""
|
||||
for k := range params {
|
||||
if k != "sn" && k != "debug" {
|
||||
key = append(key, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(key)
|
||||
for i := 0; i < len(key); i++ {
|
||||
if i == 0 {
|
||||
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
|
||||
} else {
|
||||
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/controller/jaeger_conn"
|
||||
"go-gin-api/app/controller/product"
|
||||
"go-gin-api/app/controller/test"
|
||||
"go-gin-api/app/route/middleware/exception"
|
||||
"go-gin-api/app/route/middleware/jaeger"
|
||||
"go-gin-api/app/route/middleware/logger"
|
||||
"go-gin-api/app/util/response"
|
||||
)
|
||||
|
||||
func SetupRouter(engine *gin.Engine) {
|
||||
|
||||
//设置路由中间件
|
||||
engine.Use(logger.SetUp(), exception.SetUp(), jaeger.SetUp())
|
||||
|
||||
//404
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(404,"请求方法不存在", nil)
|
||||
})
|
||||
|
||||
engine.GET("/ping", func(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1,"pong", nil)
|
||||
})
|
||||
|
||||
// 测试链路追踪
|
||||
engine.GET("/jaeger_test", jaeger_conn.JaegerTest)
|
||||
|
||||
//@todo 记录请求超时的路由
|
||||
|
||||
ProductRouter := engine.Group("/product")
|
||||
{
|
||||
// 新增产品
|
||||
ProductRouter.POST("", product.Add)
|
||||
|
||||
// 更新产品
|
||||
ProductRouter.PUT("/:id", product.Edit)
|
||||
|
||||
// 删除产品
|
||||
ProductRouter.DELETE("/:id", product.Delete)
|
||||
|
||||
// 获取产品详情
|
||||
ProductRouter.GET("/:id", product.Detail)
|
||||
}
|
||||
|
||||
// 测试加密性能
|
||||
TestRouter := engine.Group("/test")
|
||||
{
|
||||
// 测试 MD5 组合 的性能
|
||||
TestRouter.GET("/md5", test.Md5Test)
|
||||
|
||||
// 测试 AES 的性能
|
||||
TestRouter.GET("/aes", test.AesTest)
|
||||
|
||||
// 测试 RSA 的性能
|
||||
TestRouter.GET("/rsa", test.RsaTest)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
func Bind(s interface{}, c *gin.Context) (interface{}, error) {
|
||||
b := binding.Default(c.Request.Method, c.ContentType())
|
||||
if err := c.ShouldBindWith(s, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xinliangnote/go-util/json"
|
||||
"github.com/xinliangnote/go-util/mail"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/route/middleware/exception"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type errorString struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func ErrorNew (text string) error {
|
||||
alarm("INFO", text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 发邮件
|
||||
func ErrorMail (text string) error {
|
||||
alarm("MAIL",text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 发短信
|
||||
func ErrorSms (text string) error {
|
||||
alarm("SMS", text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 发微信
|
||||
func ErrorWeChat (text string) error {
|
||||
alarm("WX", text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 告警方法
|
||||
func alarm(level string, str string) {
|
||||
if level == "MAIL" {
|
||||
DebugStack := ""
|
||||
for _, v := range strings.Split(string(debug.Stack()), "\n") {
|
||||
DebugStack += v + "<br>"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("【系统告警】%s 项目出错了!", config.AppName)
|
||||
|
||||
body := strings.ReplaceAll(exception.MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", str))
|
||||
body = strings.ReplaceAll(body, "{RequestTime}", timeUtil.GetCurrentDate())
|
||||
body = strings.ReplaceAll(body, "{RequestURL}", "--")
|
||||
body = strings.ReplaceAll(body, "{RequestUA}", "--")
|
||||
body = strings.ReplaceAll(body, "{RequestIP}", "--")
|
||||
body = strings.ReplaceAll(body, "{DebugStack}", DebugStack)
|
||||
|
||||
// 执行发邮件
|
||||
options := &mail.Options{
|
||||
MailHost : config.SystemEmailHost,
|
||||
MailPort : config.SystemEmailPort,
|
||||
MailUser : config.SystemEmailUser,
|
||||
MailPass : config.SystemEmailPass,
|
||||
MailTo : config.ErrorNotifyUser,
|
||||
Subject : subject,
|
||||
Body : body,
|
||||
}
|
||||
_ = mail.Send(options)
|
||||
|
||||
} else if level == "SMS" {
|
||||
// 执行发短信
|
||||
|
||||
} else if level == "WX" {
|
||||
// 执行发微信
|
||||
|
||||
} else if level == "INFO" {
|
||||
// 执行记日志
|
||||
|
||||
if f, err := os.OpenFile(config.AppErrorLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
errorLogMap := make(map[string]interface{})
|
||||
errorLogMap["time"] = time.Now().Format("2006/01/02 - 15:04:05")
|
||||
errorLogMap["info"] = str
|
||||
|
||||
errorLogJson, _ := json.Encode(errorLogMap)
|
||||
_, _ = f.WriteString(errorLogJson + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package grpc_client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
grpc_middeware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/grpc_log"
|
||||
"go-gin-api/app/util/jaeger_trace"
|
||||
"google.golang.org/grpc"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CreateServiceListenConn(c *gin.Context) *grpc.ClientConn {
|
||||
return createGrpcConn("127.0.0.1:9901", c)
|
||||
}
|
||||
|
||||
func CreateServiceSpeakConn(c *gin.Context) *grpc.ClientConn {
|
||||
return createGrpcConn("127.0.0.1:9902", c)
|
||||
}
|
||||
|
||||
func CreateServiceReadConn(c *gin.Context) *grpc.ClientConn {
|
||||
return createGrpcConn("127.0.0.1:9903", c)
|
||||
}
|
||||
|
||||
func CreateServiceWriteConn(c *gin.Context) *grpc.ClientConn {
|
||||
return createGrpcConn("127.0.0.1:9904", c)
|
||||
}
|
||||
|
||||
func createGrpcConn(serviceAddress string, c *gin.Context) *grpc.ClientConn {
|
||||
|
||||
var conn *grpc.ClientConn
|
||||
var err error
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond * 500)
|
||||
defer cancel()
|
||||
|
||||
if config.JaegerOpen == 1 {
|
||||
|
||||
tracer, _ := c.Get("Tracer")
|
||||
parentSpanContext, _ := c.Get("ParentSpanContext")
|
||||
|
||||
conn, err = grpc.DialContext(
|
||||
ctx,
|
||||
serviceAddress,
|
||||
grpc.WithInsecure(),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithUnaryInterceptor(
|
||||
grpc_middeware.ChainUnaryClient(
|
||||
jaeger_trace.ClientInterceptor(tracer.(opentracing.Tracer), parentSpanContext.(opentracing.SpanContext)),
|
||||
grpc_log.ClientInterceptor(),
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
conn, err = grpc.DialContext(
|
||||
ctx,
|
||||
serviceAddress,
|
||||
grpc.WithInsecure(),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithUnaryInterceptor(
|
||||
grpc_middeware.ChainUnaryClient(
|
||||
grpc_log.ClientInterceptor(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(serviceAddress, "grpc conn err:", err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package grpc_log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/xinliangnote/go-util/json"
|
||||
"github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"google.golang.org/grpc"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var grpcChannel = make(chan string, 100)
|
||||
|
||||
func ClientInterceptor() grpc.UnaryClientInterceptor {
|
||||
|
||||
go handleGrpcChannel()
|
||||
|
||||
return func(ctx context.Context, method string,
|
||||
req, reply interface{}, cc *grpc.ClientConn,
|
||||
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
|
||||
// 开始时间
|
||||
startTime := time.GetCurrentMilliUnix()
|
||||
|
||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||
|
||||
// 结束时间
|
||||
endTime := time.GetCurrentMilliUnix()
|
||||
|
||||
// 日志格式
|
||||
grpcLogMap := make(map[string]interface{})
|
||||
|
||||
grpcLogMap["request_time"] = startTime
|
||||
grpcLogMap["request_data"] = req
|
||||
grpcLogMap["request_method"] = method
|
||||
|
||||
grpcLogMap["response_data"] = reply
|
||||
grpcLogMap["response_error"] = err
|
||||
|
||||
grpcLogMap["cost_time"] = fmt.Sprintf("%vms", endTime-startTime)
|
||||
|
||||
grpcLogJson, _ := json.Encode(grpcLogMap)
|
||||
|
||||
grpcChannel <- grpcLogJson
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func handleGrpcChannel() {
|
||||
if f, err := os.OpenFile(config.AppGrpcLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
for accessLog := range grpcChannel {
|
||||
_, _ = f.WriteString(accessLog + "\n")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package jaeger_trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
"github.com/uber/jaeger-client-go"
|
||||
jaegerConfig "github.com/uber/jaeger-client-go/config"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NewJaegerTracer(serviceName string, jaegerHostPort string) (opentracing.Tracer, io.Closer) {
|
||||
|
||||
cfg := &jaegerConfig.Configuration {
|
||||
Sampler: &jaegerConfig.SamplerConfig{
|
||||
Type : "const", //固定采样
|
||||
Param : 1, //1=全采样、0=不采样
|
||||
},
|
||||
|
||||
Reporter: &jaegerConfig.ReporterConfig{
|
||||
LogSpans : true,
|
||||
LocalAgentHostPort : jaegerHostPort,
|
||||
},
|
||||
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
|
||||
tracer, closer, err := cfg.NewTracer(jaegerConfig.Logger(jaeger.StdLogger))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
|
||||
}
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
return tracer, closer
|
||||
}
|
||||
|
||||
type MDReaderWriter struct {
|
||||
metadata.MD
|
||||
}
|
||||
|
||||
// ForeachKey implements ForeachKey of opentracing.TextMapReader
|
||||
func (c MDReaderWriter) ForeachKey(handler func(key, val string) error) error {
|
||||
for k, vs := range c.MD {
|
||||
for _, v := range vs {
|
||||
if err := handler(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set implements Set() of opentracing.TextMapWriter
|
||||
func (c MDReaderWriter) Set(key, val string) {
|
||||
key = strings.ToLower(key)
|
||||
c.MD[key] = append(c.MD[key], val)
|
||||
}
|
||||
|
||||
// ClientInterceptor grpc client
|
||||
func ClientInterceptor(tracer opentracing.Tracer, spanContext opentracing.SpanContext) grpc.UnaryClientInterceptor {
|
||||
return func(ctx context.Context, method string,
|
||||
req, reply interface{}, cc *grpc.ClientConn,
|
||||
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
|
||||
span := opentracing.StartSpan(
|
||||
"call gRPC",
|
||||
opentracing.ChildOf(spanContext),
|
||||
opentracing.Tag{Key: string(ext.Component), Value: "gRPC"},
|
||||
ext.SpanKindRPCClient,
|
||||
)
|
||||
|
||||
defer span.Finish()
|
||||
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(nil)
|
||||
} else {
|
||||
md = md.Copy()
|
||||
}
|
||||
|
||||
err := tracer.Inject(span.Context(), opentracing.TextMap, MDReaderWriter{md})
|
||||
if err != nil {
|
||||
span.LogFields(log.String("inject-error", err.Error()))
|
||||
}
|
||||
|
||||
newCtx := metadata.NewOutgoingContext(ctx, md)
|
||||
err = invoker(newCtx, method, req, reply, cc, opts...)
|
||||
if err != nil {
|
||||
span.LogFields(log.String("call-error", err.Error()))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"go-gin-api/app/config"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func HttpGet(url string, c *gin.Context) (string, error) {
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig : &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout : time.Second * 5, //默认5秒超时时间
|
||||
Transport : tr,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url,nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if config.JaegerOpen == 1 {
|
||||
|
||||
tracer, _ := c.Get("Tracer")
|
||||
parentSpanContext, _ := c.Get("ParentSpanContext")
|
||||
|
||||
span := opentracing.StartSpan(
|
||||
"call Http Get",
|
||||
opentracing.ChildOf(parentSpanContext.(opentracing.SpanContext)),
|
||||
opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
|
||||
ext.SpanKindRPCClient,
|
||||
)
|
||||
|
||||
span.Finish()
|
||||
|
||||
injectErr := tracer.(opentracing.Tracer).Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
|
||||
if injectErr != nil {
|
||||
log.Fatalf("%s: Couldn't inject headers", err)
|
||||
}
|
||||
}
|
||||
|
||||
resp ,err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), err
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package response
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type Gin struct {
|
||||
Ctx *gin.Context
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (g *Gin)Response(code int, msg string, data interface{}) {
|
||||
g.Ctx.JSON(200, Response{
|
||||
Code : code,
|
||||
Message : msg,
|
||||
Data : data,
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package assets
|
||||
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed bootstrap
|
||||
Bootstrap embed.FS
|
||||
|
||||
//go:embed templates
|
||||
Templates embed.FS
|
||||
)
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+4303
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
!function(r,e){"object"==typeof exports?module.exports=exports=e(require("./core")):"function"==typeof define&&define.amd?define(["./core"],e):e(r.CryptoJS)}(this,function(r){var s;return s=r.lib.WordArray,r.enc.Base64={stringify:function(r){var e=r.words,t=r.sigBytes,a=this._map;r.clamp();for(var n=[],o=0;o<t;o+=3)for(var i=(e[o>>>2]>>>24-o%4*8&255)<<16|(e[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|e[o+2>>>2]>>>24-(o+2)%4*8&255,f=0;f<4&&o+.75*f<t;f++)n.push(a.charAt(i>>>6*(3-f)&63));var c=a.charAt(64);if(c)for(;n.length%4;)n.push(c);return n.join("")},parse:function(r){var e=r.length,t=this._map,a=this._reverseMap;if(!a){a=this._reverseMap=[];for(var n=0;n<t.length;n++)a[t.charCodeAt(n)]=n}var o=t.charAt(64);if(o){var i=r.indexOf(o);-1!==i&&(e=i)}return function(r,e,t){for(var a=[],n=0,o=0;o<e;o++)if(o%4){var i=t[r.charCodeAt(o-1)]<<o%4*2,f=t[r.charCodeAt(o)]>>>6-o%4*2,c=i|f;a[n>>>2]|=c<<24-n%4*8,n++}return s.create(a,n)}(r,e,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},r.enc.Base64});
|
||||
@@ -0,0 +1,18 @@
|
||||
;(function (root, factory, undef) {
|
||||
if (typeof exports === "object") {
|
||||
// CommonJS
|
||||
module.exports = exports = factory(require("./core"), require("./sha256"), require("./hmac"));
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(["./core", "./sha256", "./hmac"], factory);
|
||||
}
|
||||
else {
|
||||
// Global (browser)
|
||||
factory(root.CryptoJS);
|
||||
}
|
||||
}(this, function (CryptoJS) {
|
||||
|
||||
return CryptoJS.HmacSHA256;
|
||||
|
||||
}));
|
||||
@@ -0,0 +1,78 @@
|
||||
// original:https://locutus.io/php/array/ksort/
|
||||
|
||||
function ksort(inputArr, sort_flags) {
|
||||
var tmp_arr = {},
|
||||
keys = [],
|
||||
sorter, i, k, that = this,
|
||||
strictForIn = false,
|
||||
populateArr = {};
|
||||
|
||||
switch (sort_flags) {
|
||||
case 'SORT_STRING':
|
||||
// compare items as strings
|
||||
sorter = function (a, b) {
|
||||
return that.strnatcmp(a, b);
|
||||
};
|
||||
break;
|
||||
case 'SORT_LOCALE_STRING':
|
||||
// compare items as strings, original by the current locale (set with i18n_loc_set_default() as of PHP6)
|
||||
var loc = this.i18n_loc_get_default();
|
||||
sorter = this.php_js.i18nLocales[loc].sorting;
|
||||
break;
|
||||
case 'SORT_NUMERIC':
|
||||
// compare items numerically
|
||||
sorter = function (a, b) {
|
||||
return ((a + 0) - (b + 0));
|
||||
};
|
||||
break;
|
||||
// case 'SORT_REGULAR': // compare items normally (don't change types)
|
||||
default:
|
||||
sorter = function (a, b) {
|
||||
var aFloat = parseFloat(a),
|
||||
bFloat = parseFloat(b),
|
||||
aNumeric = aFloat + '' === a,
|
||||
bNumeric = bFloat + '' === b;
|
||||
if (aNumeric && bNumeric) {
|
||||
return aFloat > bFloat ? 1 : aFloat < bFloat ? -1 : 0;
|
||||
} else if (aNumeric && !bNumeric) {
|
||||
return 1;
|
||||
} else if (!aNumeric && bNumeric) {
|
||||
return -1;
|
||||
}
|
||||
return a > b ? 1 : a < b ? -1 : 0;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// Make a list of key names
|
||||
for (k in inputArr) {
|
||||
if (inputArr.hasOwnProperty(k)) {
|
||||
keys.push(k);
|
||||
}
|
||||
}
|
||||
keys.sort(sorter);
|
||||
|
||||
// BEGIN REDUNDANT
|
||||
this.php_js = this.php_js || {};
|
||||
this.php_js.ini = this.php_js.ini || {};
|
||||
// END REDUNDANT
|
||||
strictForIn = this.php_js.ini['phpjs.strictForIn'] && this.php_js.ini['phpjs.strictForIn'].local_value && this.php_js
|
||||
.ini['phpjs.strictForIn'].local_value !== 'off';
|
||||
populateArr = strictForIn ? inputArr : populateArr;
|
||||
|
||||
// Rebuild array with sorted key names
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
k = keys[i];
|
||||
tmp_arr[k] = inputArr[k];
|
||||
if (strictForIn) {
|
||||
delete inputArr[k];
|
||||
}
|
||||
}
|
||||
for (i in tmp_arr) {
|
||||
if (tmp_arr.hasOwnProperty(i)) {
|
||||
populateArr[i] = tmp_arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
return strictForIn || populateArr;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
!function(n){"use strict";function d(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function f(n,t,r,e,o,u){return d((c=d(d(t,n),d(e,u)))<<(f=o)|c>>>32-f,r);var c,f}function l(n,t,r,e,o,u,c){return f(t&r|~t&e,n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f(t&e|r&~e,n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u;n[t>>5]|=128<<t%32,n[14+(t+64>>>9<<4)]=t;for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h<n.length;h+=16)c=l(r=c,e=f,o=i,u=a,n[h],7,-680876936),a=l(a,c,f,i,n[h+1],12,-389564586),i=l(i,a,c,f,n[h+2],17,606105819),f=l(f,i,a,c,n[h+3],22,-1044525330),c=l(c,f,i,a,n[h+4],7,-176418897),a=l(a,c,f,i,n[h+5],12,1200080426),i=l(i,a,c,f,n[h+6],17,-1473231341),f=l(f,i,a,c,n[h+7],22,-45705983),c=l(c,f,i,a,n[h+8],7,1770035416),a=l(a,c,f,i,n[h+9],12,-1958414417),i=l(i,a,c,f,n[h+10],17,-42063),f=l(f,i,a,c,n[h+11],22,-1990404162),c=l(c,f,i,a,n[h+12],7,1804603682),a=l(a,c,f,i,n[h+13],12,-40341101),i=l(i,a,c,f,n[h+14],17,-1502002290),c=v(c,f=l(f,i,a,c,n[h+15],22,1236535329),i,a,n[h+1],5,-165796510),a=v(a,c,f,i,n[h+6],9,-1069501632),i=v(i,a,c,f,n[h+11],14,643717713),f=v(f,i,a,c,n[h],20,-373897302),c=v(c,f,i,a,n[h+5],5,-701558691),a=v(a,c,f,i,n[h+10],9,38016083),i=v(i,a,c,f,n[h+15],14,-660478335),f=v(f,i,a,c,n[h+4],20,-405537848),c=v(c,f,i,a,n[h+9],5,568446438),a=v(a,c,f,i,n[h+14],9,-1019803690),i=v(i,a,c,f,n[h+3],14,-187363961),f=v(f,i,a,c,n[h+8],20,1163531501),c=v(c,f,i,a,n[h+13],5,-1444681467),a=v(a,c,f,i,n[h+2],9,-51403784),i=v(i,a,c,f,n[h+7],14,1735328473),c=g(c,f=v(f,i,a,c,n[h+12],20,-1926607734),i,a,n[h+5],4,-378558),a=g(a,c,f,i,n[h+8],11,-2022574463),i=g(i,a,c,f,n[h+11],16,1839030562),f=g(f,i,a,c,n[h+14],23,-35309556),c=g(c,f,i,a,n[h+1],4,-1530992060),a=g(a,c,f,i,n[h+4],11,1272893353),i=g(i,a,c,f,n[h+7],16,-155497632),f=g(f,i,a,c,n[h+10],23,-1094730640),c=g(c,f,i,a,n[h+13],4,681279174),a=g(a,c,f,i,n[h],11,-358537222),i=g(i,a,c,f,n[h+3],16,-722521979),f=g(f,i,a,c,n[h+6],23,76029189),c=g(c,f,i,a,n[h+9],4,-640364487),a=g(a,c,f,i,n[h+12],11,-421815835),i=g(i,a,c,f,n[h+15],16,530742520),c=m(c,f=g(f,i,a,c,n[h+2],23,-995338651),i,a,n[h],6,-198630844),a=m(a,c,f,i,n[h+7],10,1126891415),i=m(i,a,c,f,n[h+14],15,-1416354905),f=m(f,i,a,c,n[h+5],21,-57434055),c=m(c,f,i,a,n[h+12],6,1700485571),a=m(a,c,f,i,n[h+3],10,-1894986606),i=m(i,a,c,f,n[h+10],15,-1051523),f=m(f,i,a,c,n[h+1],21,-2054922799),c=m(c,f,i,a,n[h+8],6,1873313359),a=m(a,c,f,i,n[h+15],10,-30611744),i=m(i,a,c,f,n[h+6],15,-1560198380),f=m(f,i,a,c,n[h+13],21,1309151649),c=m(c,f,i,a,n[h+4],6,-145523070),a=m(a,c,f,i,n[h+11],10,-1120210379),i=m(i,a,c,f,n[h+2],15,718787259),f=m(f,i,a,c,n[h+9],21,-343485551),c=d(c,r),f=d(f,e),i=d(i,o),a=d(a,u);return[c,f,i,a]}function a(n){for(var t="",r=32*n.length,e=0;e<r;e+=8)t+=String.fromCharCode(n[e>>5]>>>e%32&255);return t}function h(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e<t.length;e+=1)t[e]=0;for(var r=8*n.length,e=0;e<r;e+=8)t[e>>5]|=(255&n.charCodeAt(e/8))<<e%32;return t}function e(n){for(var t,r="0123456789abcdef",e="",o=0;o<n.length;o+=1)t=n.charCodeAt(o),e+=r.charAt(t>>>4&15)+r.charAt(15&t);return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return a(i(h(t=r(n)),8*t.length));var t}function u(n,t){return function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,16<o.length&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}(r(n),r(t))}function t(n,t,r){return t?r?u(t,n):e(u(t,n)):r?o(n):e(o(n))}"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:n.md5=t}(this);
|
||||
//# sourceMappingURL=md5.min.js.map
|
||||
@@ -0,0 +1,589 @@
|
||||
/* ==========================================================
|
||||
* bootstrap-maxlength.js v1.9.0
|
||||
*
|
||||
* Copyright (c) 2013-2020 Maurizio Napoleoni;
|
||||
*
|
||||
* Licensed under the terms of the MIT license.
|
||||
* See: https://github.com/mimo84/bootstrap-maxlength/blob/master/LICENSE
|
||||
* ========================================================== */
|
||||
/*global jQuery*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
/**
|
||||
* We need an event when the elements are destroyed
|
||||
* because if an input is removed, we have to remove the
|
||||
* maxlength object associated (if any).
|
||||
* From:
|
||||
* http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
|
||||
*/
|
||||
if (!$.event.special.destroyed) {
|
||||
$.event.special.destroyed = {
|
||||
remove: function (o) {
|
||||
if (o.handler) {
|
||||
o.handler();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
$.fn.extend({
|
||||
maxlength: function (options, callback) {
|
||||
var documentBody = $('body'),
|
||||
defaults = {
|
||||
showOnReady: false, // true to always show when indicator is ready
|
||||
alwaysShow: true, // if true the indicator it's always shown.
|
||||
threshold: 0, // Represents how many chars left are needed to show up the counter
|
||||
warningClass: 'small form-text text-muted',
|
||||
limitReachedClass: 'small form-text text-danger',
|
||||
separator: ' / ',
|
||||
preText: '',
|
||||
postText: '',
|
||||
showMaxLength: true,
|
||||
placement: 'bottom-right-inside',
|
||||
message: null, // an alternative way to provide the message text
|
||||
showCharsTyped: true, // show the number of characters typed and not the number of characters remaining
|
||||
validate: false, // if the browser doesn't support the maxlength attribute, attempt to type more than the indicated chars, will be prevented.
|
||||
utf8: false, // counts using bytesize rather than length. eg: '£' is counted as 2 characters.
|
||||
appendToParent: false, // append the indicator to the input field's parent instead of body
|
||||
twoCharLinebreak: true, // count linebreak as 2 characters to match IE/Chrome textarea validation. As well as DB storage.
|
||||
customMaxAttribute: null, // null = use maxlength attribute and browser functionality, string = use specified attribute instead.
|
||||
allowOverMax: false, // Form submit validation is handled on your own. when maxlength has been exceeded 'overmax' class added to element
|
||||
zIndex: 1099
|
||||
};
|
||||
|
||||
if ($.isFunction(options) && !callback) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
options = $.extend(defaults, options);
|
||||
|
||||
|
||||
/**
|
||||
* Return the byte count of the specified character in UTF8 encoding.
|
||||
* Note: This won't cover UTF-8 characters that are 4 bytes long.
|
||||
*
|
||||
* @param input
|
||||
* @return {number}
|
||||
*/
|
||||
function utf8CharByteCount(character) {
|
||||
var c = character.charCodeAt();
|
||||
// Not c then 0, else c < 128 then 1, else c < 2048 then 2, else 3
|
||||
return !c ? 0 : c < 128 ? 1 : c < 2048 ? 2 : 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the specified input in UTF8 encoding.
|
||||
*
|
||||
* @param input
|
||||
* @return {number}
|
||||
*/
|
||||
function utf8Length(string) {
|
||||
return string.split("")
|
||||
.map(utf8CharByteCount)
|
||||
// Prevent reduce from throwing an error if the string is empty.
|
||||
.concat(0)
|
||||
.reduce(function (sum, val) {
|
||||
return sum + val;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the specified input.
|
||||
*
|
||||
* @param input
|
||||
* @return {number}
|
||||
*/
|
||||
function inputLength(input) {
|
||||
var text = input.val();
|
||||
|
||||
if (options.twoCharLinebreak) {
|
||||
// Count all line breaks as 2 characters
|
||||
text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');
|
||||
} else {
|
||||
// Remove all double-character (\r\n) linebreaks, so they're counted only once.
|
||||
text = text.replace(/(?:\r\n|\r|\n)/g, '\n');
|
||||
}
|
||||
|
||||
var currentLength = 0;
|
||||
|
||||
if (options.utf8) {
|
||||
currentLength = utf8Length(text);
|
||||
} else {
|
||||
currentLength = text.length;
|
||||
}
|
||||
|
||||
// Remove "C:\fakepath\" from counter when using file input
|
||||
// Fix https://github.com/mimo84/bootstrap-maxlength/issues/146
|
||||
if (input.prop("type") === "file" && input.val() !== "") {
|
||||
currentLength -= 12;
|
||||
}
|
||||
|
||||
return currentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the text of the specified input.
|
||||
*
|
||||
* @param input
|
||||
* @param limit
|
||||
*/
|
||||
function truncateChars(input, maxlength) {
|
||||
var text = input.val();
|
||||
|
||||
if (options.twoCharLinebreak) {
|
||||
text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');
|
||||
|
||||
if (text[text.length - 1] === '\n') {
|
||||
maxlength -= text.length % 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.utf8) {
|
||||
var indexedSize = text.split("").map(utf8CharByteCount);
|
||||
for (
|
||||
var removedBytes = 0,
|
||||
bytesPastMax = utf8Length(text) - maxlength; removedBytes < bytesPastMax; removedBytes += indexedSize.pop()
|
||||
);
|
||||
maxlength -= (maxlength - indexedSize.length);
|
||||
}
|
||||
|
||||
input.val(text.substr(0, maxlength));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the indicator should be showing up.
|
||||
*
|
||||
* @param input
|
||||
* @param threshold
|
||||
* @param maxlength
|
||||
* @return {number}
|
||||
*/
|
||||
function charsLeftThreshold(input, threshold, maxlength) {
|
||||
var output = true;
|
||||
if (!options.alwaysShow && (maxlength - inputLength(input) > threshold)) {
|
||||
output = false;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many chars are left to complete the fill up of the form.
|
||||
*
|
||||
* @param input
|
||||
* @param maxlength
|
||||
* @return {number}
|
||||
*/
|
||||
function remainingChars(input, maxlength) {
|
||||
var length = maxlength - inputLength(input);
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* When called displays the indicator.
|
||||
*
|
||||
* @param indicator
|
||||
*/
|
||||
function showRemaining(currentInput, indicator) {
|
||||
indicator.css({
|
||||
display: 'block'
|
||||
});
|
||||
currentInput.trigger('maxlength.shown');
|
||||
}
|
||||
|
||||
/**
|
||||
* When called shows the indicator.
|
||||
*
|
||||
* @param indicator
|
||||
*/
|
||||
function hideRemaining(currentInput, indicator) {
|
||||
|
||||
if (options.alwaysShow) {
|
||||
return;
|
||||
}
|
||||
|
||||
indicator.css({
|
||||
display: 'none'
|
||||
});
|
||||
currentInput.trigger('maxlength.hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function updates the value in the indicator
|
||||
*
|
||||
* @param maxLengthThisInput
|
||||
* @param typedChars
|
||||
* @return String
|
||||
*/
|
||||
function updateMaxLengthHTML(currentInputText, maxLengthThisInput, typedChars) {
|
||||
var output = '';
|
||||
if (options.message) {
|
||||
if (typeof options.message === 'function') {
|
||||
output = options.message(currentInputText, maxLengthThisInput);
|
||||
} else {
|
||||
output = options.message.replace('%charsTyped%', typedChars)
|
||||
.replace('%charsRemaining%', maxLengthThisInput - typedChars)
|
||||
.replace('%charsTotal%', maxLengthThisInput);
|
||||
}
|
||||
} else {
|
||||
if (options.preText) {
|
||||
output += options.preText;
|
||||
}
|
||||
if (!options.showCharsTyped) {
|
||||
output += maxLengthThisInput - typedChars;
|
||||
} else {
|
||||
output += typedChars;
|
||||
}
|
||||
if (options.showMaxLength) {
|
||||
output += options.separator + maxLengthThisInput;
|
||||
}
|
||||
if (options.postText) {
|
||||
output += options.postText;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function updates the value of the counter in the indicator.
|
||||
* Wants as parameters: the number of remaining chars, the element currently managed,
|
||||
* the maxLength for the current input and the indicator generated for it.
|
||||
*
|
||||
* @param remaining
|
||||
* @param currentInput
|
||||
* @param maxLengthCurrentInput
|
||||
* @param maxLengthIndicator
|
||||
*/
|
||||
function manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator) {
|
||||
if (maxLengthIndicator) {
|
||||
maxLengthIndicator.html(updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, (maxLengthCurrentInput - remaining)));
|
||||
|
||||
if (remaining > 0) {
|
||||
if (charsLeftThreshold(currentInput, options.threshold, maxLengthCurrentInput)) {
|
||||
showRemaining(currentInput, maxLengthIndicator.removeClass(options.limitReachedClass).addClass(options.warningClass));
|
||||
} else {
|
||||
hideRemaining(currentInput, maxLengthIndicator);
|
||||
}
|
||||
} else {
|
||||
showRemaining(currentInput, maxLengthIndicator.removeClass(options.warningClass).addClass(options.limitReachedClass));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.customMaxAttribute) {
|
||||
// class to use for form validation on custom maxlength attribute
|
||||
if (remaining < 0) {
|
||||
currentInput.addClass('overmax');
|
||||
} else {
|
||||
currentInput.removeClass('overmax');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns an object containing all the
|
||||
* informations about the position of the current input
|
||||
*
|
||||
* @param currentInput
|
||||
* @return object {bottom height left right top width}
|
||||
*
|
||||
*/
|
||||
function getPosition(currentInput) {
|
||||
var el = currentInput[0];
|
||||
return $.extend({}, (typeof el.getBoundingClientRect === 'function') ? el.getBoundingClientRect() : {
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight
|
||||
}, currentInput.offset());
|
||||
}
|
||||
|
||||
/**
|
||||
* This function places the maxLengthIndicator based on placement config object.
|
||||
*
|
||||
* @param {object} placement
|
||||
* @param {$} maxLengthIndicator
|
||||
* @return null
|
||||
*
|
||||
*/
|
||||
function placeWithCSS(placement, maxLengthIndicator) {
|
||||
if (!placement || !maxLengthIndicator) {
|
||||
return;
|
||||
}
|
||||
|
||||
var POSITION_KEYS = [
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'position'
|
||||
];
|
||||
|
||||
var cssPos = {};
|
||||
|
||||
// filter css properties to position
|
||||
$.each(POSITION_KEYS, function (i, key) {
|
||||
var val = options.placement[key];
|
||||
if (typeof val !== 'undefined') {
|
||||
cssPos[key] = val;
|
||||
}
|
||||
});
|
||||
|
||||
maxLengthIndicator.css(cssPos);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function places the maxLengthIndicator at the
|
||||
* top / bottom / left / right of the currentInput
|
||||
*
|
||||
* @param currentInput
|
||||
* @param maxLengthIndicator
|
||||
* @return null
|
||||
*
|
||||
*/
|
||||
function place(currentInput, maxLengthIndicator) {
|
||||
var pos = getPosition(currentInput);
|
||||
|
||||
// Supports custom placement handler
|
||||
if ($.type(options.placement) === 'function') {
|
||||
options.placement(currentInput, maxLengthIndicator, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
// Supports custom placement via css positional properties
|
||||
if ($.isPlainObject(options.placement)) {
|
||||
placeWithCSS(options.placement, maxLengthIndicator);
|
||||
return;
|
||||
}
|
||||
|
||||
var inputOuter = currentInput.outerWidth(),
|
||||
outerWidth = maxLengthIndicator.outerWidth(),
|
||||
actualWidth = maxLengthIndicator.width(),
|
||||
actualHeight = maxLengthIndicator.height();
|
||||
|
||||
// get the right position if the indicator is appended to the input's parent
|
||||
if (options.appendToParent) {
|
||||
pos.top -= currentInput.parent().offset().top;
|
||||
pos.left -= currentInput.parent().offset().left;
|
||||
}
|
||||
|
||||
switch (options.placement) {
|
||||
case 'bottom':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height,
|
||||
left: pos.left + pos.width / 2 - actualWidth / 2
|
||||
});
|
||||
break;
|
||||
case 'top':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left + pos.width / 2 - actualWidth / 2
|
||||
});
|
||||
break;
|
||||
case 'left':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height / 2 - actualHeight / 2,
|
||||
left: pos.left - actualWidth
|
||||
});
|
||||
break;
|
||||
case 'right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height / 2 - actualHeight / 2,
|
||||
left: pos.left + pos.width
|
||||
});
|
||||
break;
|
||||
case 'bottom-right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height,
|
||||
left: pos.left + pos.width
|
||||
});
|
||||
break;
|
||||
case 'top-right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left + inputOuter
|
||||
});
|
||||
break;
|
||||
case 'top-left':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'bottom-left':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + currentInput.outerHeight(),
|
||||
left: pos.left - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'centered-right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + (actualHeight / 2),
|
||||
left: pos.left + inputOuter - outerWidth - 3
|
||||
});
|
||||
break;
|
||||
|
||||
// Some more options for placements
|
||||
case 'bottom-right-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height,
|
||||
left: pos.left + pos.width - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'top-right-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left + inputOuter - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'top-left-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left
|
||||
});
|
||||
break;
|
||||
case 'bottom-left-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + currentInput.outerHeight(),
|
||||
left: pos.left
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns true if the indicator position needs to
|
||||
* be recalculated when the currentInput changes
|
||||
*
|
||||
* @return {boolean}
|
||||
*
|
||||
*/
|
||||
function isPlacementMutable() {
|
||||
return options.placement === 'bottom-right-inside' || options.placement === 'top-right-inside' || typeof options.placement === 'function' || (options.message && typeof options.message === 'function');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrieves the maximum length of currentInput
|
||||
*
|
||||
* @param currentInput
|
||||
* @return {number}
|
||||
*
|
||||
*/
|
||||
function getMaxLength(currentInput) {
|
||||
var max = currentInput.attr('maxlength') || options.customMaxAttribute;
|
||||
|
||||
if (options.customMaxAttribute && !options.allowOverMax) {
|
||||
var custom = currentInput.attr(options.customMaxAttribute);
|
||||
if (!max || custom < max) {
|
||||
max = custom;
|
||||
}
|
||||
}
|
||||
|
||||
if (!max) {
|
||||
max = currentInput.attr('size');
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
return this.each(function () {
|
||||
|
||||
var currentInput = $(this),
|
||||
maxLengthCurrentInput,
|
||||
maxLengthIndicator;
|
||||
|
||||
$(window).resize(function () {
|
||||
if (maxLengthIndicator) {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
});
|
||||
|
||||
function firstInit() {
|
||||
var maxlengthContent = updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, '0');
|
||||
maxLengthCurrentInput = getMaxLength(currentInput);
|
||||
|
||||
if (!maxLengthIndicator) {
|
||||
maxLengthIndicator = $('<span class="bootstrap-maxlength"></span>').css({
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
whiteSpace: 'nowrap',
|
||||
zIndex: options.zIndex
|
||||
}).html(maxlengthContent);
|
||||
}
|
||||
|
||||
// We need to detect resizes if we are dealing with a textarea:
|
||||
if (currentInput.is('textarea')) {
|
||||
currentInput.data('maxlenghtsizex', currentInput.outerWidth());
|
||||
currentInput.data('maxlenghtsizey', currentInput.outerHeight());
|
||||
|
||||
currentInput.mouseup(function () {
|
||||
if (currentInput.outerWidth() !== currentInput.data('maxlenghtsizex') || currentInput.outerHeight() !== currentInput.data('maxlenghtsizey')) {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
currentInput.data('maxlenghtsizex', currentInput.outerWidth());
|
||||
currentInput.data('maxlenghtsizey', currentInput.outerHeight());
|
||||
});
|
||||
}
|
||||
|
||||
if (options.appendToParent) {
|
||||
currentInput.parent().append(maxLengthIndicator);
|
||||
currentInput.parent().css('position', 'relative');
|
||||
} else {
|
||||
documentBody.append(maxLengthIndicator);
|
||||
}
|
||||
|
||||
var remaining = remainingChars(currentInput, getMaxLength(currentInput));
|
||||
manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
if (options.showOnReady) {
|
||||
currentInput.ready(function () {
|
||||
firstInit();
|
||||
});
|
||||
} else {
|
||||
currentInput.focus(function () {
|
||||
firstInit();
|
||||
});
|
||||
}
|
||||
|
||||
currentInput.on('maxlength.reposition', function () {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
});
|
||||
|
||||
|
||||
currentInput.on('destroyed', function () {
|
||||
if (maxLengthIndicator) {
|
||||
maxLengthIndicator.remove();
|
||||
}
|
||||
});
|
||||
|
||||
currentInput.on('blur', function () {
|
||||
if (maxLengthIndicator && !options.showOnReady) {
|
||||
maxLengthIndicator.remove();
|
||||
}
|
||||
});
|
||||
|
||||
currentInput.on('input', function () {
|
||||
var maxlength = getMaxLength(currentInput),
|
||||
remaining = remainingChars(currentInput, maxlength),
|
||||
output = true;
|
||||
|
||||
if (options.validate && remaining < 0) {
|
||||
truncateChars(currentInput, maxlength);
|
||||
output = false;
|
||||
} else {
|
||||
manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
if (isPlacementMutable()) {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
return output;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}(jQuery));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
.mt-wrapper{
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.mt-nav-bar {
|
||||
width : 100%;
|
||||
z-index: 200;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
}
|
||||
.mt-nav-bar .mt-nav{
|
||||
background-color: #fff;
|
||||
}
|
||||
.mt-nav-panel{
|
||||
overflow: hidden;
|
||||
}
|
||||
.mt-nav-panel ul{
|
||||
width: 10000px;
|
||||
}
|
||||
.mt-nav-panel ul li {
|
||||
position: relative;
|
||||
}
|
||||
.mt-tab-content{
|
||||
height: 100%;
|
||||
}
|
||||
.mt-close-tab {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 18px;
|
||||
right: 10px;
|
||||
color: #c2c2c2;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
vertical-align: 2px;
|
||||
-webkit-border-radius: 50%;
|
||||
-moz-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
|
||||
transition: all .3s cubic-bezier(.645,.045,.355,1);
|
||||
-webkit-transform-origin: 100% 50%;
|
||||
transform-origin: 100% 50%;
|
||||
}
|
||||
.mt-close-tab:before {
|
||||
-webkit-transform: scale(.8);
|
||||
transform: scale(.8);
|
||||
display: inline-block;
|
||||
/*vertical-align: -1px;*/
|
||||
}
|
||||
li:hover .mt-close-tab {
|
||||
display: inline;
|
||||
}
|
||||
.mt-hidden-list .mt-close-tab {
|
||||
display: none !important;
|
||||
}
|
||||
.mt-nav-bar a {
|
||||
cursor: pointer !important;
|
||||
max-height: 48px;
|
||||
padding-top: 14px;
|
||||
padding-bottom: 13px;
|
||||
}
|
||||
@media (max-width: 767px){
|
||||
.mt-tab-content{
|
||||
/*padding-top: 0 !important;*/
|
||||
}
|
||||
}
|
||||
.mt-tab-content{
|
||||
-webkit-overflow-scrolling:touch;
|
||||
overflow:auto;
|
||||
}
|
||||
.mt-dragging-tab{
|
||||
left: auto;
|
||||
position: absolute !important;
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
.mt-dragging-tab > a{
|
||||
background: #FBFDFD !important;
|
||||
}
|
||||
|
||||
/*新增*/
|
||||
.mt-nav-bar .mt-nav .nav-tabs {
|
||||
margin-bottom: 0px;
|
||||
border-color: #eceeef;
|
||||
}
|
||||
.mt-close-tab:hover {
|
||||
color: #f96868;
|
||||
}
|
||||
.mt-dropdown .caret {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
}
|
||||
.mt-dropdown .dropdown-menu {
|
||||
margin-top: 0px;
|
||||
}
|
||||
#contextify-menu {
|
||||
min-width: 80px!important;
|
||||
}
|
||||
.mt-close-tab:hover {
|
||||
background-color: #f96868;
|
||||
color: #fff;
|
||||
}
|
||||
.mt-nav .nav-tabs a:not([data-type="main"]) {
|
||||
padding-right: 40px;
|
||||
}
|
||||
.mt-nav-tools-left li a,
|
||||
.mt-nav-tools-right li a {
|
||||
padding-right: 15px!important;
|
||||
}
|
||||
.mt-nav-bar .mt-nav .nav-tabs .mt-dragging {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
|
||||
document.write('<script type="text/javascript" src="/assets/bootstrap/js/bootstrap-notify/bootstrap-notify.min.js"></script>');
|
||||
|
||||
function SuccessNotify(content) {
|
||||
$.notify({
|
||||
icon: "mdi mdi-alert",
|
||||
title: "",
|
||||
message: content,
|
||||
url: "",
|
||||
target: ""
|
||||
}, {
|
||||
type: "success",
|
||||
allow_dismiss: true,
|
||||
newest_on_top: false,
|
||||
placement: {
|
||||
from: "top",
|
||||
align: "right",
|
||||
},
|
||||
offset: {
|
||||
x: "20",
|
||||
y: "20"
|
||||
},
|
||||
spacing: "10",
|
||||
z_index: "1031",
|
||||
delay: "3000",
|
||||
animate: {
|
||||
enter: "animated fadeInDown",
|
||||
exit: "animated fadeOutUp"
|
||||
},
|
||||
onClosed: null,
|
||||
mouse_over: null
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.17 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
@-webkit-keyframes bs-notify-fadeOut {
|
||||
0% {
|
||||
opacity: 0.9;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@-o-keyframes bs-notify-fadeOut {
|
||||
0% {
|
||||
opacity: 0.9;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes bs-notify-fadeOut {
|
||||
0% {
|
||||
opacity: 0.9;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
select.bs-select-hidden,
|
||||
.bootstrap-select > select.bs-select-hidden,
|
||||
select.selectpicker {
|
||||
display: none !important;
|
||||
}
|
||||
.bootstrap-select {
|
||||
width: 220px \0;
|
||||
/*IE9 and below*/
|
||||
vertical-align: middle;
|
||||
}
|
||||
.bootstrap-select > .dropdown-toggle {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.bootstrap-select > .dropdown-toggle:after {
|
||||
margin-top: -1px;
|
||||
}
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder:active {
|
||||
color: #999;
|
||||
}
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:hover,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:hover,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:hover,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:hover,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:hover,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:hover,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:focus,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:focus,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:focus,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:focus,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:focus,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:focus,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:active,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:active,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:active,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:active,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:active,
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:active {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.bootstrap-select > select {
|
||||
position: absolute !important;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
display: block !important;
|
||||
width: 0.5px !important;
|
||||
height: 100% !important;
|
||||
padding: 0 !important;
|
||||
opacity: 0 !important;
|
||||
border: none;
|
||||
z-index: 0 !important;
|
||||
}
|
||||
.bootstrap-select > select.mobile-device {
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
z-index: 2 !important;
|
||||
}
|
||||
.has-error .bootstrap-select .dropdown-toggle,
|
||||
.error .bootstrap-select .dropdown-toggle,
|
||||
.bootstrap-select.is-invalid .dropdown-toggle,
|
||||
.was-validated .bootstrap-select select:invalid + .dropdown-toggle {
|
||||
border-color: #b94a48;
|
||||
}
|
||||
.bootstrap-select.is-valid .dropdown-toggle,
|
||||
.was-validated .bootstrap-select select:valid + .dropdown-toggle {
|
||||
border-color: #28a745;
|
||||
}
|
||||
.bootstrap-select.fit-width {
|
||||
width: auto !important;
|
||||
}
|
||||
.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
|
||||
width: 220px;
|
||||
}
|
||||
.bootstrap-select.form-control {
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
height: auto;
|
||||
}
|
||||
:not(.input-group) > .bootstrap-select.form-control:not([class*="col-"]) {
|
||||
width: 100%;
|
||||
}
|
||||
.bootstrap-select.form-control.input-group-btn {
|
||||
float: none;
|
||||
z-index: auto;
|
||||
}
|
||||
.form-inline .bootstrap-select,
|
||||
.form-inline .bootstrap-select.form-control:not([class*="col-"]) {
|
||||
width: auto;
|
||||
}
|
||||
.bootstrap-select:not(.input-group-btn),
|
||||
.bootstrap-select[class*="col-"] {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
margin-left: 0;
|
||||
}
|
||||
.bootstrap-select.dropdown-menu-right,
|
||||
.bootstrap-select[class*="col-"].dropdown-menu-right,
|
||||
.row .bootstrap-select[class*="col-"].dropdown-menu-right {
|
||||
float: right;
|
||||
}
|
||||
.form-inline .bootstrap-select,
|
||||
.form-horizontal .bootstrap-select,
|
||||
.form-group .bootstrap-select {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.form-group-lg .bootstrap-select.form-control,
|
||||
.form-group-sm .bootstrap-select.form-control {
|
||||
padding: 0;
|
||||
}
|
||||
.form-group-lg .bootstrap-select.form-control .dropdown-toggle,
|
||||
.form-group-sm .bootstrap-select.form-control .dropdown-toggle {
|
||||
height: 100%;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
border-radius: inherit;
|
||||
}
|
||||
.bootstrap-select.form-control-sm .dropdown-toggle,
|
||||
.bootstrap-select.form-control-lg .dropdown-toggle {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
border-radius: inherit;
|
||||
}
|
||||
.bootstrap-select.form-control-sm .dropdown-toggle {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
.bootstrap-select.form-control-lg .dropdown-toggle {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
.form-inline .bootstrap-select .form-control {
|
||||
width: 100%;
|
||||
}
|
||||
.bootstrap-select.disabled,
|
||||
.bootstrap-select > .disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.bootstrap-select.disabled:focus,
|
||||
.bootstrap-select > .disabled:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
.bootstrap-select.bs-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.bootstrap-select.bs-container .dropdown-menu {
|
||||
z-index: 1060;
|
||||
}
|
||||
.bootstrap-select .dropdown-toggle .filter-option {
|
||||
position: static;
|
||||
top: 0;
|
||||
left: 0;
|
||||
float: left;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 1 auto;
|
||||
-ms-flex: 0 1 auto;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
.bs3.bootstrap-select .dropdown-toggle .filter-option {
|
||||
padding-right: inherit;
|
||||
}
|
||||
.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option {
|
||||
position: absolute;
|
||||
padding-top: inherit;
|
||||
padding-bottom: inherit;
|
||||
padding-left: inherit;
|
||||
float: none;
|
||||
}
|
||||
.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner {
|
||||
padding-right: inherit;
|
||||
}
|
||||
.bootstrap-select .dropdown-toggle .filter-option-inner-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
.bootstrap-select .dropdown-toggle .filter-expand {
|
||||
width: 0 !important;
|
||||
float: left;
|
||||
opacity: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.bootstrap-select .dropdown-toggle .caret {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 12px;
|
||||
margin-top: -2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.input-group .bootstrap-select.form-control .dropdown-toggle {
|
||||
border-radius: inherit;
|
||||
}
|
||||
.bootstrap-select[class*="col-"] .dropdown-toggle {
|
||||
width: 100%;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu {
|
||||
min-width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu > .inner:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu.inner {
|
||||
position: static;
|
||||
float: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li {
|
||||
position: relative;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li.active small {
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li.disabled a {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li a {
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li a.opt {
|
||||
position: relative;
|
||||
padding-left: 2.25em;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li a span.check-mark {
|
||||
display: none;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li a span.text {
|
||||
display: inline-block;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu li small {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu .notify {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
width: 96%;
|
||||
margin: 0 2%;
|
||||
min-height: 26px;
|
||||
padding: 3px 5px;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #f2f3f3;
|
||||
pointer-events: none;
|
||||
opacity: 0.9;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bootstrap-select .dropdown-menu .notify.fadeOut {
|
||||
-webkit-animation: 300ms linear 750ms forwards bs-notify-fadeOut;
|
||||
-o-animation: 300ms linear 750ms forwards bs-notify-fadeOut;
|
||||
animation: 300ms linear 750ms forwards bs-notify-fadeOut;
|
||||
}
|
||||
.bootstrap-select .no-results {
|
||||
padding: 3px;
|
||||
background: #f5f5f5;
|
||||
margin: 0 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.bootstrap-select.fit-width .dropdown-toggle .filter-option {
|
||||
position: static;
|
||||
display: inline;
|
||||
padding: 0;
|
||||
}
|
||||
.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,
|
||||
.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner {
|
||||
display: inline;
|
||||
}
|
||||
.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before {
|
||||
content: '\00a0';
|
||||
}
|
||||
.bootstrap-select.fit-width .dropdown-toggle .caret {
|
||||
position: static;
|
||||
top: auto;
|
||||
margin-top: -1px;
|
||||
}
|
||||
.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
}
|
||||
.bootstrap-select.show-tick .dropdown-menu li a span.text {
|
||||
margin-right: 34px;
|
||||
}
|
||||
.bootstrap-select .bs-ok-default:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0.5em;
|
||||
height: 1em;
|
||||
border-style: solid;
|
||||
border-width: 0 0.13em 0.13em 0;
|
||||
border-color: #4d5259;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle,
|
||||
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle {
|
||||
z-index: 1061;
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before {
|
||||
content: '';
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid rgba(204, 204, 204, 0.2);
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 9px;
|
||||
display: none;
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after {
|
||||
content: '';
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid white;
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 10px;
|
||||
display: none;
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before {
|
||||
bottom: auto;
|
||||
top: -4px;
|
||||
border-top: 7px solid rgba(204, 204, 204, 0.2);
|
||||
border-bottom: 0;
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after {
|
||||
bottom: auto;
|
||||
top: -4px;
|
||||
border-top: 6px solid white;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before {
|
||||
right: 12px;
|
||||
left: auto;
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after {
|
||||
right: 13px;
|
||||
left: auto;
|
||||
}
|
||||
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:before,
|
||||
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:before,
|
||||
.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:after,
|
||||
.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:after {
|
||||
display: block;
|
||||
}
|
||||
.bs-searchbox,
|
||||
.bs-actionsbox,
|
||||
.bs-donebutton {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.bs-actionsbox {
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bs-actionsbox .btn-group button {
|
||||
width: 50%;
|
||||
}
|
||||
.bs-donebutton {
|
||||
float: left;
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bs-donebutton .btn-group button {
|
||||
width: 100%;
|
||||
}
|
||||
.bs-searchbox + .bs-actionsbox {
|
||||
padding: 0 8px 4px;
|
||||
}
|
||||
.bs-searchbox .form-control {
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-select.css.map */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.17 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u6ca1\u6709\u9009\u4e2d\u4efb\u4f55\u9879",noneResultsText:"\u6ca1\u6709\u627e\u5230\u5339\u914d\u9879",countSelectedText:"\u9009\u4e2d{1}\u4e2d\u7684{0}\u9879",maxOptionsText:["\u8d85\u51fa\u9650\u5236 (\u6700\u591a\u9009\u62e9{n}\u9879)","\u7ec4\u9009\u62e9\u8d85\u51fa\u9650\u5236(\u6700\u591a\u9009\u62e9{n}\u7ec4)"],multipleSeparator:", ",selectAllText:"\u5168\u9009",deselectAllText:"\u53d6\u6d88\u5168\u9009"}});
|
||||
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.17 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u6c92\u6709\u9078\u53d6\u4efb\u4f55\u9805\u76ee",noneResultsText:"\u6c92\u6709\u627e\u5230\u7b26\u5408\u7684\u7d50\u679c",countSelectedText:"\u5df2\u7d93\u9078\u53d6{0}\u500b\u9805\u76ee",maxOptionsText:["\u8d85\u904e\u9650\u5236 (\u6700\u591a\u9078\u64c7{n}\u9805)","\u8d85\u904e\u9650\u5236(\u6700\u591a\u9078\u64c7{n}\u7d44)"],selectAllText:"\u9078\u53d6\u5168\u90e8",deselectAllText:"\u5168\u90e8\u53d6\u6d88",multipleSeparator:", "}});
|
||||
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
* version: 1.16.0
|
||||
* https://github.com/wenzhixin/bootstrap-table/
|
||||
*/
|
||||
.bootstrap-table .fixed-table-toolbar::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .bs-bars,
|
||||
.bootstrap-table .fixed-table-toolbar .search,
|
||||
.bootstrap-table .fixed-table-toolbar .columns {
|
||||
position: relative;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group {
|
||||
display: inline-block;
|
||||
margin-left: -1px !important;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group > .btn {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:first-child > .btn {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:last-child > .btn {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu {
|
||||
text-align: left;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns label {
|
||||
display: block;
|
||||
padding: 3px 20px;
|
||||
clear: both;
|
||||
font-weight: normal;
|
||||
line-height: 1.428571429;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns-left {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .columns-right {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container {
|
||||
position: relative;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table {
|
||||
width: 100%;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table th,
|
||||
.bootstrap-table .fixed-table-container .table td {
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th {
|
||||
vertical-align: bottom;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th:focus {
|
||||
outline: 0 solid transparent;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th.detail {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th .th-inner {
|
||||
padding: 0.75rem;
|
||||
vertical-align: bottom;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th .sortable {
|
||||
cursor: pointer;
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 30px !important;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th .both {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC");
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th .asc {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==");
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table thead th .desc {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII= ");
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table tbody tr.selected td {
|
||||
background-color: rgba(0, 0, 0, 0.035);
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table tbody tr .card-view {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
min-width: 30%;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table .bs-checkbox {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table .bs-checkbox label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type="radio"],
|
||||
.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type="checkbox"] {
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .table.table-sm .th-inner {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer) {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container.fixed-height.has-card-view {
|
||||
border-top: 1px solid #dee2e6;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border {
|
||||
border-left: 1px solid #dee2e6;
|
||||
border-right: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container.fixed-height .table thead th {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th {
|
||||
border-bottom: 1px solid #32383e;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-header {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body {
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading {
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text {
|
||||
font-size: 2rem;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before {
|
||||
content: "";
|
||||
animation-duration: 1.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: LOADING;
|
||||
background: #212529;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
height: 5px;
|
||||
margin: 0 4px;
|
||||
opacity: 0;
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark {
|
||||
background: #212529;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,
|
||||
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-container .fixed-table-footer {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination-detail,
|
||||
.bootstrap-table .fixed-table-pagination > .pagination {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination-detail .pagination-info {
|
||||
line-height: 34px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group .dropdown-menu {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination ul.pagination {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination ul.pagination a {
|
||||
/*padding: 6px 12px;
|
||||
line-height: 1.428571429;*/
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a {
|
||||
color: #c8c8c8;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::before {
|
||||
content: '\2B05';
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::after {
|
||||
content: '\27A1';
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.disabled a {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.bootstrap-table.fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1050;
|
||||
width: 100% !important;
|
||||
background: #fff;
|
||||
height: calc(100vh);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.dropdown-item.dropdown-item-marker {
|
||||
padding: .25rem 1.5rem;
|
||||
}
|
||||
.dropdown-item.dropdown-item-marker:focus,
|
||||
.dropdown-item.dropdown-item:active {
|
||||
background-color: #fff;
|
||||
color: #4d5259;
|
||||
}
|
||||
|
||||
/* calculate scrollbar width */
|
||||
div.fixed-table-scroll-inner {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
div.fixed-table-scroll-outer {
|
||||
top: 0;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes LOADING {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
+7217
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+10
File diff suppressed because one or more lines are too long
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
|
||||
*
|
||||
* @version v1.16.0
|
||||
* @homepage https://bootstrap-table.com
|
||||
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
.table-cell-input{display:block!important;padding:5px!important;margin:0!important;border:0!important;width:100%!important;box-sizing:border-box!important;-moz-box-sizing:border-box!important;border-radius:0!important;line-height:1!important;white-space:nowrap}
|
||||
Vendored
+10
File diff suppressed because one or more lines are too long
+10
File diff suppressed because one or more lines are too long
+10
File diff suppressed because one or more lines are too long
+10
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
assets/bootstrap/js/bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.min.css
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
|
||||
*
|
||||
* @version v1.16.0
|
||||
* @homepage https://bootstrap-table.com
|
||||
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
@charset "UTF-8";.no-filter-control{height:34px}.filter-control{margin:0 2px 2px 2px}
|
||||
Vendored
+10
File diff suppressed because one or more lines are too long
Vendored
+395
@@ -0,0 +1,395 @@
|
||||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
// Reasonable defaults
|
||||
const PIXEL_STEP = 10
|
||||
const LINE_HEIGHT = 40
|
||||
const PAGE_HEIGHT = 800
|
||||
|
||||
function normalizeWheel (event) {
|
||||
let sX = 0 // spinX
|
||||
let sY = 0 // spinY
|
||||
let pX = 0 // pixelX
|
||||
let pY = 0 // pixelY
|
||||
|
||||
// Legacy
|
||||
if ('detail' in event) { sY = event.detail }
|
||||
if ('wheelDelta' in event) { sY = -event.wheelDelta / 120 }
|
||||
if ('wheelDeltaY' in event) { sY = -event.wheelDeltaY / 120 }
|
||||
if ('wheelDeltaX' in event) { sX = -event.wheelDeltaX / 120 }
|
||||
|
||||
// side scrolling on FF with DOMMouseScroll
|
||||
if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
|
||||
sX = sY
|
||||
sY = 0
|
||||
}
|
||||
|
||||
pX = sX * PIXEL_STEP
|
||||
pY = sY * PIXEL_STEP
|
||||
|
||||
if ('deltaY' in event) { pY = event.deltaY }
|
||||
if ('deltaX' in event) { pX = event.deltaX }
|
||||
|
||||
if ((pX || pY) && event.deltaMode) {
|
||||
if (event.deltaMode === 1) { // delta in LINE units
|
||||
pX *= LINE_HEIGHT
|
||||
pY *= LINE_HEIGHT
|
||||
} else { // delta in PAGE units
|
||||
pX *= PAGE_HEIGHT
|
||||
pY *= PAGE_HEIGHT
|
||||
}
|
||||
}
|
||||
|
||||
// Fall-back if spin cannot be determined
|
||||
if (pX && !sX) { sX = (pX < 1) ? -1 : 1 }
|
||||
if (pY && !sY) { sY = (pY < 1) ? -1 : 1 }
|
||||
|
||||
return {
|
||||
spinX: sX,
|
||||
spinY: sY,
|
||||
pixelX: pX,
|
||||
pixelY: pY
|
||||
}
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
fixedColumns: false,
|
||||
fixedNumber: 0,
|
||||
fixedRightNumber: 0
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
|
||||
fixedColumnsSupported () {
|
||||
return this.options.fixedColumns &&
|
||||
!this.options.detailView &&
|
||||
!this.options.cardView
|
||||
}
|
||||
|
||||
initContainer () {
|
||||
super.initContainer()
|
||||
|
||||
if (!this.fixedColumnsSupported()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.options.fixedNumber) {
|
||||
this.$tableContainer.append('<div class="fixed-columns"></div>')
|
||||
this.$fixedColumns = this.$tableContainer.find('.fixed-columns')
|
||||
}
|
||||
|
||||
if (this.options.fixedRightNumber) {
|
||||
this.$tableContainer.append('<div class="fixed-columns-right"></div>')
|
||||
this.$fixedColumnsRight = this.$tableContainer.find('.fixed-columns-right')
|
||||
}
|
||||
}
|
||||
|
||||
initBody (...args) {
|
||||
super.initBody(...args)
|
||||
|
||||
if (!this.fixedColumnsSupported()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.options.showHeader && this.options.height) {
|
||||
return
|
||||
}
|
||||
|
||||
this.initFixedColumnsBody()
|
||||
this.initFixedColumnsEvents()
|
||||
}
|
||||
|
||||
trigger (...args) {
|
||||
super.trigger(...args)
|
||||
|
||||
if (!this.fixedColumnsSupported()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (args[0] === 'post-header') {
|
||||
this.initFixedColumnsHeader()
|
||||
} else if (args[0] === 'scroll-body') {
|
||||
if (this.needFixedColumns && this.options.fixedNumber) {
|
||||
this.$fixedBody.scrollTop(this.$tableBody.scrollTop())
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedRightNumber) {
|
||||
this.$fixedBodyRight.scrollTop(this.$tableBody.scrollTop())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSelected () {
|
||||
super.updateSelected()
|
||||
|
||||
if (!this.fixedColumnsSupported()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$tableBody.find('tr').each((i, el) => {
|
||||
const $el = $(el)
|
||||
const index = $el.data('index')
|
||||
const classes = $el.attr('class')
|
||||
|
||||
const inputSelector = `[name="${this.options.selectItemName}"]`
|
||||
const $input = $el.find(inputSelector)
|
||||
|
||||
if (typeof index === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const updateFixedBody = ($fixedHeader, $fixedBody) => {
|
||||
const $tr = $fixedBody.find(`tr[data-index="${index}"]`)
|
||||
$tr.attr('class', classes)
|
||||
|
||||
if ($input.length) {
|
||||
$tr.find(inputSelector).prop('checked', $input.prop('checked'))
|
||||
}
|
||||
|
||||
if (this.$selectAll.length) {
|
||||
$fixedHeader.add($fixedBody)
|
||||
.find('[name="btSelectAll"]')
|
||||
.prop('checked', this.$selectAll.prop('checked'))
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$fixedBody && this.options.fixedNumber) {
|
||||
updateFixedBody(this.$fixedHeader, this.$fixedBody)
|
||||
}
|
||||
|
||||
if (this.$fixedBodyRight && this.options.fixedRightNumber) {
|
||||
updateFixedBody(this.$fixedHeaderRight, this.$fixedBodyRight)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
hideLoading () {
|
||||
super.hideLoading()
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedNumber) {
|
||||
this.$fixedColumns.find('.fixed-table-loading').hide()
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedRightNumber) {
|
||||
this.$fixedColumnsRight.find('.fixed-table-loading').hide()
|
||||
}
|
||||
}
|
||||
|
||||
initFixedColumnsHeader () {
|
||||
if (this.options.height) {
|
||||
this.needFixedColumns = this.$tableHeader.outerWidth(true) < this.$tableHeader.find('table').outerWidth(true)
|
||||
} else {
|
||||
this.needFixedColumns = this.$tableBody.outerWidth(true) < this.$tableBody.find('table').outerWidth(true)
|
||||
}
|
||||
|
||||
const initFixedHeader = ($fixedColumns, isRight) => {
|
||||
$fixedColumns.find('.fixed-table-header').remove()
|
||||
$fixedColumns.append(this.$tableHeader.clone(true))
|
||||
|
||||
$fixedColumns.css({
|
||||
width: this.getFixedColumnsWidth(isRight)
|
||||
})
|
||||
return $fixedColumns.find('.fixed-table-header')
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedNumber) {
|
||||
this.$fixedHeader = initFixedHeader(this.$fixedColumns)
|
||||
this.$fixedHeader.css('margin-right', '')
|
||||
} else if (this.$fixedColumns) {
|
||||
this.$fixedColumns.html('').css('width', '')
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedRightNumber) {
|
||||
this.$fixedHeaderRight = initFixedHeader(this.$fixedColumnsRight, true)
|
||||
this.$fixedHeaderRight.scrollLeft(this.$fixedHeaderRight.find('table').width())
|
||||
} else if (this.$fixedColumnsRight) {
|
||||
this.$fixedColumnsRight.html('').css('width', '')
|
||||
}
|
||||
|
||||
this.initFixedColumnsBody()
|
||||
this.initFixedColumnsEvents()
|
||||
}
|
||||
|
||||
initFixedColumnsBody () {
|
||||
const initFixedBody = ($fixedColumns, $fixedHeader) => {
|
||||
$fixedColumns.find('.fixed-table-body').remove()
|
||||
$fixedColumns.append(this.$tableBody.clone(true))
|
||||
|
||||
const $fixedBody = $fixedColumns.find('.fixed-table-body')
|
||||
|
||||
const tableBody = this.$tableBody.get(0)
|
||||
const scrollHeight = tableBody.scrollWidth > tableBody.clientWidth
|
||||
? Utils.getScrollBarWidth() : 0
|
||||
const height = this.$tableContainer.outerHeight(true) - scrollHeight - 1
|
||||
|
||||
$fixedColumns.css({
|
||||
height
|
||||
})
|
||||
|
||||
$fixedBody.css({
|
||||
height: height - $fixedHeader.height()
|
||||
})
|
||||
|
||||
return $fixedBody
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedNumber) {
|
||||
this.$fixedBody = initFixedBody(this.$fixedColumns, this.$fixedHeader)
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedRightNumber) {
|
||||
this.$fixedBodyRight = initFixedBody(this.$fixedColumnsRight, this.$fixedHeaderRight)
|
||||
this.$fixedBodyRight.scrollLeft(this.$fixedBodyRight.find('table').width())
|
||||
this.$fixedBodyRight.css('overflow-y', this.options.height ? 'auto' : 'hidden')
|
||||
}
|
||||
}
|
||||
|
||||
getFixedColumnsWidth (isRight) {
|
||||
let visibleFields = this.getVisibleFields()
|
||||
let width = 0
|
||||
let fixedNumber = this.options.fixedNumber
|
||||
let marginRight = 0
|
||||
|
||||
if (isRight) {
|
||||
visibleFields = visibleFields.reverse()
|
||||
fixedNumber = this.options.fixedRightNumber
|
||||
marginRight = parseInt(this.$tableHeader.css('margin-right'), 10)
|
||||
}
|
||||
|
||||
for (let i = 0; i < fixedNumber; i++) {
|
||||
width += this.$header.find(`th[data-field="${visibleFields[i]}"]`).outerWidth(true)
|
||||
}
|
||||
|
||||
return width + marginRight + 1
|
||||
}
|
||||
|
||||
initFixedColumnsEvents () {
|
||||
const toggleHover = (e, toggle) => {
|
||||
const tr = `tr[data-index="${$(e.currentTarget).data('index')}"]`
|
||||
let $trs = this.$tableBody.find(tr)
|
||||
|
||||
if (this.$fixedBody) {
|
||||
$trs = $trs.add(this.$fixedBody.find(tr))
|
||||
}
|
||||
if (this.$fixedBodyRight) {
|
||||
$trs = $trs.add(this.$fixedBodyRight.find(tr))
|
||||
}
|
||||
|
||||
$trs.css('background-color', toggle ? $(e.currentTarget).css('background-color') : '')
|
||||
}
|
||||
|
||||
this.$tableBody.find('tr').hover(e => {
|
||||
toggleHover(e, true)
|
||||
}, e => {
|
||||
toggleHover(e, false)
|
||||
})
|
||||
|
||||
const isFirefox = typeof navigator !== 'undefined' &&
|
||||
navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
||||
const mousewheel = isFirefox ? 'DOMMouseScroll' : 'mousewheel'
|
||||
const updateScroll = (e, fixedBody) => {
|
||||
const normalized = normalizeWheel(e)
|
||||
const deltaY = Math.ceil(normalized.pixelY)
|
||||
const top = this.$tableBody.scrollTop() + deltaY
|
||||
|
||||
if (
|
||||
deltaY < 0 && top > 0 ||
|
||||
deltaY > 0 && top < fixedBody.scrollHeight - fixedBody.clientHeight
|
||||
) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.$tableBody.scrollTop(top)
|
||||
if (this.$fixedBody) {
|
||||
this.$fixedBody.scrollTop(top)
|
||||
}
|
||||
if (this.$fixedBodyRight) {
|
||||
this.$fixedBodyRight.scrollTop(top)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedNumber) {
|
||||
this.$fixedBody.find('tr').hover(e => {
|
||||
toggleHover(e, true)
|
||||
}, e => {
|
||||
toggleHover(e, false)
|
||||
})
|
||||
|
||||
this.$fixedBody[0].addEventListener(mousewheel, e => {
|
||||
updateScroll(e, this.$fixedBody[0])
|
||||
})
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedRightNumber) {
|
||||
this.$fixedBodyRight.find('tr').hover(e => {
|
||||
toggleHover(e, true)
|
||||
}, e => {
|
||||
toggleHover(e, false)
|
||||
})
|
||||
|
||||
this.$fixedBodyRight.off('scroll').on('scroll', () => {
|
||||
const top = this.$fixedBodyRight.scrollTop()
|
||||
|
||||
this.$tableBody.scrollTop(top)
|
||||
if (this.$fixedBody) {
|
||||
this.$fixedBody.scrollTop(top)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.options.filterControl) {
|
||||
$(this.$fixedColumns).off('keyup change').on('keyup change', e => {
|
||||
const $target = $(e.target)
|
||||
const value = $target.val()
|
||||
const field = $target.parents('th').data('field')
|
||||
const $coreTh = this.$header.find(`th[data-field="${field}"]`)
|
||||
|
||||
if ($target.is('input')) {
|
||||
$coreTh.find('input').val(value)
|
||||
} else if ($target.is('select')) {
|
||||
const $select = $coreTh.find('select')
|
||||
$select.find('option[selected]').removeAttr('selected')
|
||||
$select.find(`option[value="${value}"]`).attr('selected', true)
|
||||
}
|
||||
|
||||
this.triggerSearch()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
renderStickyHeader () {
|
||||
if (!this.options.stickyHeader) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$stickyContainer = this.$container.find('.sticky-header-container')
|
||||
super.renderStickyHeader()
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedNumber) {
|
||||
this.$fixedColumns.css('z-index', 101)
|
||||
.find('.sticky-header-container')
|
||||
.css('right', '')
|
||||
.width(this.$fixedColumns.outerWidth())
|
||||
}
|
||||
|
||||
if (this.needFixedColumns && this.options.fixedRightNumber) {
|
||||
const $stickyHeaderContainerRight = this.$fixedColumnsRight.find('.sticky-header-container')
|
||||
|
||||
this.$fixedColumnsRight.css('z-index', 101)
|
||||
$stickyHeaderContainerRight.css('left', '')
|
||||
.scrollLeft($stickyHeaderContainerRight.find('.table').outerWidth())
|
||||
.width(this.$fixedColumnsRight.outerWidth())
|
||||
}
|
||||
}
|
||||
|
||||
matchPositionX () {
|
||||
if (!this.options.stickyHeader) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$stickyContainer.eq(0).scrollLeft(this.$tableBody.scrollLeft())
|
||||
}
|
||||
}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
.fixed-columns,
|
||||
.fixed-columns-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.fixed-columns {
|
||||
left: 0;
|
||||
|
||||
.fixed-table-body {
|
||||
overflow: hidden!important;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-columns-right {
|
||||
right: 0;
|
||||
|
||||
.fixed-table-body {
|
||||
overflow-x: hidden!important;
|
||||
}
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* @author: Yura Knoxville
|
||||
* @version: v1.1.0
|
||||
*/
|
||||
|
||||
let initBodyCaller
|
||||
|
||||
// it only does '%s', and return '' when arguments are undefined
|
||||
const sprintf = function (str) {
|
||||
const args = arguments
|
||||
let flag = true
|
||||
let i = 1
|
||||
|
||||
str = str.replace(/%s/g, () => {
|
||||
const arg = args[i++]
|
||||
|
||||
if (typeof arg === 'undefined') {
|
||||
flag = false
|
||||
return ''
|
||||
}
|
||||
return arg
|
||||
})
|
||||
return flag ? str : ''
|
||||
}
|
||||
|
||||
const groupBy = (array, f) => {
|
||||
const tmpGroups = {}
|
||||
array.forEach(o => {
|
||||
const groups = f(o)
|
||||
tmpGroups[groups] = tmpGroups[groups] || []
|
||||
tmpGroups[groups].push(o)
|
||||
})
|
||||
|
||||
return tmpGroups
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
groupBy: false,
|
||||
groupByField: '',
|
||||
groupByFormatter: undefined
|
||||
})
|
||||
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
const BootstrapTable = $.fn.bootstrapTable.Constructor
|
||||
const _initSort = BootstrapTable.prototype.initSort
|
||||
const _initBody = BootstrapTable.prototype.initBody
|
||||
const _updateSelected = BootstrapTable.prototype.updateSelected
|
||||
|
||||
BootstrapTable.prototype.initSort = function (...args) {
|
||||
_initSort.apply(this, Array.prototype.slice.apply(args))
|
||||
|
||||
const that = this
|
||||
this.tableGroups = []
|
||||
|
||||
if ((this.options.groupBy) && (this.options.groupByField !== '')) {
|
||||
|
||||
if ((this.options.sortName !== this.options.groupByField)) {
|
||||
if (this.options.customSort) {
|
||||
Utils.calculateObjectValue(this.options, this.options.customSort, [
|
||||
this.options.sortName,
|
||||
this.options.sortOrder,
|
||||
this.data
|
||||
])
|
||||
} else {
|
||||
this.data.sort((a, b) => {
|
||||
const groupByFields = this.getGroupByFields()
|
||||
const fieldValuesA = []
|
||||
const fieldValuesB = []
|
||||
|
||||
$.each(groupByFields, (i, field) => {
|
||||
fieldValuesA.push(a[field])
|
||||
fieldValuesB.push(b[field])
|
||||
})
|
||||
|
||||
a = fieldValuesA.join()
|
||||
b = fieldValuesB.join()
|
||||
return a.localeCompare(b, undefined, {numeric: true})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const groups = groupBy(that.data, (item) => {
|
||||
const groupByFields = this.getGroupByFields()
|
||||
const groupValues = []
|
||||
$.each(groupByFields, (i, field) => {
|
||||
groupValues.push(item[field])
|
||||
})
|
||||
|
||||
return groupValues.join(', ')
|
||||
})
|
||||
|
||||
let index = 0
|
||||
$.each(groups, (key, value) => {
|
||||
this.tableGroups.push({
|
||||
id: index,
|
||||
name: key,
|
||||
data: value
|
||||
})
|
||||
|
||||
value.forEach(item => {
|
||||
if (!item._data) {
|
||||
item._data = {}
|
||||
}
|
||||
|
||||
item._data['parent-index'] = index
|
||||
})
|
||||
|
||||
index++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.initBody = function (...args) {
|
||||
initBodyCaller = true
|
||||
|
||||
_initBody.apply(this, Array.prototype.slice.apply(args))
|
||||
|
||||
if ((this.options.groupBy) && (this.options.groupByField !== '')) {
|
||||
const that = this
|
||||
let checkBox = false
|
||||
let visibleColumns = 0
|
||||
|
||||
this.columns.forEach(column => {
|
||||
if (column.checkbox) {
|
||||
checkBox = true
|
||||
} else {
|
||||
if (column.visible) {
|
||||
visibleColumns += 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (this.options.detailView && !this.options.cardView) {
|
||||
visibleColumns += 1
|
||||
}
|
||||
|
||||
this.tableGroups.forEach(item => {
|
||||
const html = []
|
||||
|
||||
html.push(sprintf('<tr class="info groupBy expanded" data-group-index="%s">', item.id))
|
||||
|
||||
if (that.options.detailView && !that.options.cardView) {
|
||||
html.push('<td class="detail"></td>')
|
||||
}
|
||||
|
||||
if (checkBox) {
|
||||
html.push('<td class="bs-checkbox">',
|
||||
'<input name="btSelectGroup" type="checkbox" />',
|
||||
'</td>'
|
||||
)
|
||||
}
|
||||
let formattedValue = item.name
|
||||
if (typeof (that.options.groupByFormatter) === 'function') {
|
||||
formattedValue = that.options.groupByFormatter(item.name, item.id, item.data)
|
||||
}
|
||||
html.push('<td',
|
||||
sprintf(' colspan="%s"', visibleColumns),
|
||||
'>', formattedValue, '</td>'
|
||||
)
|
||||
|
||||
html.push('</tr>')
|
||||
|
||||
that.$body.find(`tr[data-parent-index=${item.id}]:first`).before($(html.join('')))
|
||||
})
|
||||
|
||||
this.$selectGroup = []
|
||||
this.$body.find('[name="btSelectGroup"]').each(function () {
|
||||
const self = $(this)
|
||||
|
||||
that.$selectGroup.push({
|
||||
group: self,
|
||||
item: that.$selectItem.filter(function () {
|
||||
return ($(this).closest('tr').data('parent-index') ===
|
||||
self.closest('tr').data('group-index'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.$container.off('click', '.groupBy')
|
||||
.on('click', '.groupBy', function () {
|
||||
$(this).toggleClass('expanded')
|
||||
that.$body.find(`tr[data-parent-index=${$(this).closest('tr').data('group-index')}]`).toggleClass('hidden')
|
||||
})
|
||||
|
||||
this.$container.off('click', '[name="btSelectGroup"]')
|
||||
.on('click', '[name="btSelectGroup"]', function (event) {
|
||||
event.stopImmediatePropagation()
|
||||
|
||||
const self = $(this)
|
||||
const checked = self.prop('checked')
|
||||
that[checked ? 'checkGroup' : 'uncheckGroup']($(this).closest('tr').data('group-index'))
|
||||
})
|
||||
}
|
||||
|
||||
initBodyCaller = false
|
||||
this.updateSelected()
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.updateSelected = function (...args) {
|
||||
if (!initBodyCaller) {
|
||||
_updateSelected.apply(this, Array.prototype.slice.apply(args))
|
||||
|
||||
if ((this.options.groupBy) && (this.options.groupByField !== '')) {
|
||||
this.$selectGroup.forEach(item => {
|
||||
const checkGroup = item.item.filter(':enabled').length ===
|
||||
item.item.filter(':enabled').filter(':checked').length
|
||||
|
||||
item.group.prop('checked', checkGroup)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.checkGroup = function (index) {
|
||||
this.checkGroup_(index, true)
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.uncheckGroup = function (index) {
|
||||
this.checkGroup_(index, false)
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.checkGroup_ = function (index, checked) {
|
||||
const rowsBefore = this.getSelections()
|
||||
let rows
|
||||
const filter = function () {
|
||||
return ($(this).closest('tr').data('parent-index') === index)
|
||||
}
|
||||
|
||||
this.$selectItem.filter(filter).prop('checked', checked)
|
||||
|
||||
this.updateRows()
|
||||
this.updateSelected()
|
||||
const rowsAfter = this.getSelections()
|
||||
if (checked) {
|
||||
this.trigger('check-all', rowsAfter, rowsBefore)
|
||||
return
|
||||
}
|
||||
|
||||
this.trigger('uncheck-all', rowsAfter, rowsBefore)
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.getGroupByFields = function () {
|
||||
let groupByFields = this.options.groupByField
|
||||
if (!$.isArray(this.options.groupByField)) {
|
||||
groupByFields = [this.options.groupByField]
|
||||
}
|
||||
|
||||
return groupByFields
|
||||
}
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
scrollTo (params) {
|
||||
if (this.options.groupBy) {
|
||||
let options = {unit: 'px', value: 0}
|
||||
if (typeof params === 'object') {
|
||||
options = Object.assign(options, params)
|
||||
}
|
||||
|
||||
if (options.unit === 'rows') {
|
||||
let scrollTo = 0
|
||||
this.$body.find(`> tr:lt(${options.value})`).each((i, el) => {
|
||||
scrollTo += $(el).outerHeight(true)
|
||||
})
|
||||
|
||||
const $targetColumn = this.$body.find(`> tr:not(.groupBy):eq(${options.value})`)
|
||||
$targetColumn.prevAll('.groupBy').each((i, el) => {
|
||||
scrollTo += $(el).outerHeight(true)
|
||||
})
|
||||
|
||||
this.$tableBody.scrollTop(scrollTo)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
super.scrollTo(params)
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
.bootstrap-table .table > tbody > tr.groupBy {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bootstrap-table .table > tbody > tr.hidden + tr.detail-view {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Group By V2",
|
||||
"version": "1.0.0",
|
||||
"description": "Group the data by field",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/group-by-v2",
|
||||
"example": "",
|
||||
"plugins": [],
|
||||
"author": {
|
||||
"name": "Knoxvillekm",
|
||||
"image": "https://avatars3.githubusercontent.com/u/11072464"
|
||||
}
|
||||
}
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @author: Jewway
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
$.fn.bootstrapTable.methods.push('changeTitle')
|
||||
$.fn.bootstrapTable.methods.push('changeLocale')
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
|
||||
changeTitle (locale) {
|
||||
$.each(this.options.columns, (idx, columnList) => {
|
||||
$.each(columnList, (idx, column) => {
|
||||
if (column.field) {
|
||||
column.title = locale[column.field]
|
||||
}
|
||||
})
|
||||
})
|
||||
this.initHeader()
|
||||
this.initBody()
|
||||
this.initToolbar()
|
||||
}
|
||||
|
||||
changeLocale (localeId) {
|
||||
this.options.locale = localeId
|
||||
this.initLocale()
|
||||
this.initPagination()
|
||||
this.initBody()
|
||||
this.initToolbar()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "i18n Enhance",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to add i18n API in order to change column's title and table locale.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/i18n-enhance",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/i18n-enhance.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-i18n-enhance",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/i18n-enhance"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "Jewway",
|
||||
"image": "https://avatars0.githubusercontent.com/u/3501899"
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
keyEvents: false
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
|
||||
init (...args) {
|
||||
super.init(...args)
|
||||
|
||||
if (this.options.keyEvents) {
|
||||
this.initKeyEvents()
|
||||
}
|
||||
}
|
||||
|
||||
initKeyEvents () {
|
||||
$(document).off('keydown').on('keydown', e => {
|
||||
const $search = this.$toolbar.find('.search input')
|
||||
const $refresh = this.$toolbar.find('button[name="refresh"]')
|
||||
const $toggle = this.$toolbar.find('button[name="toggle"]')
|
||||
const $paginationSwitch = this.$toolbar.find('button[name="paginationSwitch"]')
|
||||
|
||||
if (document.activeElement === $search.get(0) || !$.contains(document.activeElement ,this.$toolbar.get(0))) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 83: // s
|
||||
if (!this.options.search) {
|
||||
return
|
||||
}
|
||||
$search.focus()
|
||||
return false
|
||||
case 82: // r
|
||||
if (!this.options.showRefresh) {
|
||||
return
|
||||
}
|
||||
$refresh.click()
|
||||
return false
|
||||
case 84: // t
|
||||
if (!this.options.showToggle) {
|
||||
return
|
||||
}
|
||||
$toggle.click()
|
||||
return false
|
||||
case 80: // p
|
||||
if (!this.options.showPaginationSwitch) {
|
||||
return
|
||||
}
|
||||
$paginationSwitch.click()
|
||||
return false
|
||||
case 37: // left
|
||||
if (!this.options.pagination) {
|
||||
return
|
||||
}
|
||||
this.prevPage()
|
||||
return false
|
||||
case 39: // right
|
||||
if (!this.options.pagination) {
|
||||
return
|
||||
}
|
||||
this.nextPage()
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Key Events",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support the key events in the bootstrap table.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/key-events",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/key-events.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-key-events",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/key-events"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
const debounce = (func, wait) => {
|
||||
let timeout = 0
|
||||
return (...args) => {
|
||||
const later = () => {
|
||||
timeout = 0
|
||||
func(...args)
|
||||
}
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
mobileResponsive: false,
|
||||
minWidth: 562,
|
||||
minHeight: undefined,
|
||||
heightThreshold: 100, // just slightly larger than mobile chrome's auto-hiding toolbar
|
||||
checkOnInit: true,
|
||||
columnsHidden: []
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
init (...args) {
|
||||
super.init(...args)
|
||||
|
||||
if (!this.options.mobileResponsive || !this.options.minWidth) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.options.minWidth < 100 && this.options.resizable) {
|
||||
console.info('The minWidth when the resizable extension is active should be greater or equal than 100')
|
||||
this.options.minWidth = 100
|
||||
}
|
||||
|
||||
let old = {
|
||||
width: $(window).width(),
|
||||
height: $(window).height()
|
||||
}
|
||||
|
||||
$(window).on('resize orientationchange', debounce(() => {
|
||||
// reset view if height has only changed by at least the threshold.
|
||||
const width = $(window).width()
|
||||
const height = $(window).height()
|
||||
const $activeElement = $(document.activeElement)
|
||||
|
||||
if ($activeElement.length && ['INPUT', 'SELECT', 'TEXTAREA'].includes($activeElement.prop('nodeName'))) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
Math.abs(old.height - height) > this.options.heightThreshold ||
|
||||
old.width !== width
|
||||
) {
|
||||
this.changeView(width, height)
|
||||
old = {
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}, 200))
|
||||
|
||||
if (this.options.checkOnInit) {
|
||||
const width = $(window).width()
|
||||
const height = $(window).height()
|
||||
this.changeView(width, height)
|
||||
old = {
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conditionCardView () {
|
||||
this.changeTableView(false)
|
||||
this.showHideColumns(false)
|
||||
}
|
||||
|
||||
conditionFullView () {
|
||||
this.changeTableView(true)
|
||||
this.showHideColumns(true)
|
||||
}
|
||||
|
||||
changeTableView (cardViewState) {
|
||||
this.options.cardView = cardViewState
|
||||
this.toggleView()
|
||||
}
|
||||
|
||||
showHideColumns (checked) {
|
||||
if (this.options.columnsHidden.length > 0) {
|
||||
this.columns.forEach(column => {
|
||||
if (this.options.columnsHidden.includes(column.field)) {
|
||||
if (column.visible !== checked) {
|
||||
this._toggleColumn(this.fieldsColumnsIndex[column.field], checked, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
changeView (width, height) {
|
||||
if (this.options.minHeight) {
|
||||
if ((width <= this.options.minWidth) && (height <= this.options.minHeight)) {
|
||||
this.conditionCardView()
|
||||
} else if ((width > this.options.minWidth) && (height > this.options.minHeight)) {
|
||||
this.conditionFullView()
|
||||
}
|
||||
} else {
|
||||
if (width <= this.options.minWidth) {
|
||||
this.conditionCardView()
|
||||
} else if (width > this.options.minWidth) {
|
||||
this.conditionFullView()
|
||||
}
|
||||
}
|
||||
|
||||
this.resetView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Mobile",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to support the responsive feature.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/mobile",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/mobile.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-mobile",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/mobile"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
Vendored
+728
@@ -0,0 +1,728 @@
|
||||
/**
|
||||
* @author Nadim Basalamah <dimbslmh@gmail.com>
|
||||
* @version: v1.1.0
|
||||
* https://github.com/dimbslmh/bootstrap-table/tree/master/src/extensions/multiple-sort/bootstrap-table-multiple-sort.js
|
||||
* Modification: ErwannNevou <https://github.com/ErwannNevou>
|
||||
*/
|
||||
|
||||
let isSingleSort = false
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
const bootstrap = {
|
||||
bootstrap3: {
|
||||
icons: {
|
||||
plus: 'glyphicon-plus',
|
||||
minus: 'glyphicon-minus',
|
||||
sort: 'glyphicon-sort'
|
||||
},
|
||||
html: {
|
||||
multipleSortModal: `
|
||||
<div class="modal fade" id="%s" tabindex="-1" role="dialog" aria-labelledby="%sLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="%sLabel">%s</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="bootstrap-table">
|
||||
<div class="fixed-table-toolbar">
|
||||
<div class="bars">
|
||||
<div id="toolbar">
|
||||
<button id="add" type="button" class="btn btn-default">%s %s</button>
|
||||
<button id="delete" type="button" class="btn btn-default" disabled>%s %s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-table-container">
|
||||
<table id="multi-sort" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">%s</button>
|
||||
<button type="button" class="btn btn-primary multi-sort-order-button">%s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
multipleSortButton: '<button class="multi-sort btn btn-default" type="button" data-toggle="modal" data-target="#%s" title="%s">%s</button>',
|
||||
multipleSortSelect: '<select class="%s %s form-control">'
|
||||
}
|
||||
},
|
||||
bootstrap4: {
|
||||
icons: {
|
||||
'plus': 'fa-plus',
|
||||
'minus': 'fa-minus',
|
||||
'sort': 'fa-sort'
|
||||
},
|
||||
html: {
|
||||
multipleSortModal: `
|
||||
<div class="modal fade" id="%s" tabindex="-1" role="dialog" aria-labelledby="%sLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="%sLabel">%s</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="bootstrap-table">
|
||||
<div class="fixed-table-toolbar">
|
||||
<div class="bars">
|
||||
<div id="toolbar" class="pb-3">
|
||||
<button id="add" type="button" class="btn btn-secondary">%s %s</button>
|
||||
<button id="delete" type="button" class="btn btn-secondary" disabled>%s %s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-table-container">
|
||||
<table id="multi-sort" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">%s</button>
|
||||
<button type="button" class="btn btn-primary multi-sort-order-button">%s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
multipleSortButton: '<button class="multi-sort btn btn-secondary" type="button" data-toggle="modal" data-target="#%s" title="%s">%s</button>',
|
||||
multipleSortSelect: '<select class="%s %s form-control">'
|
||||
}
|
||||
},
|
||||
semantic: {
|
||||
icons: {
|
||||
'plus': 'fa-plus',
|
||||
'minus': 'fa-minus',
|
||||
'sort': 'fa-sort'
|
||||
},
|
||||
html: {
|
||||
multipleSortModal: `
|
||||
<div class="ui modal tiny" id="%s" aria-labelledby="%sLabel" aria-hidden="true">
|
||||
<i class="close icon"></i>
|
||||
<div class="header" id="%sLabel">
|
||||
%s
|
||||
</div>
|
||||
<div class="image content">
|
||||
<div class="bootstrap-table">
|
||||
<div class="fixed-table-toolbar">
|
||||
<div class="bars">
|
||||
<div id="toolbar" class="pb-3">
|
||||
<button id="add" type="button" class="ui button">%s %s</button>
|
||||
<button id="delete" type="button" class="ui button" disabled>%s %s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-table-container">
|
||||
<table id="multi-sort" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="ui button deny">%s</div>
|
||||
<div class="ui button approve multi-sort-order-button">%s</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
multipleSortButton: '<button class="multi-sort ui button" type="button" data-toggle="modal" data-target="#%s" title="%s">%s</button>',
|
||||
multipleSortSelect: '<select class="%s %s">'
|
||||
}
|
||||
},
|
||||
materialize: {
|
||||
icons: {
|
||||
'plus': 'plus',
|
||||
'minus': 'minus',
|
||||
'sort': 'sort'
|
||||
},
|
||||
html: {
|
||||
multipleSortModal: `
|
||||
<div id="%s" class="modal" aria-labelledby="%sLabel" aria-hidden="true">
|
||||
<div class="modal-content" id="%sLabel">
|
||||
<h4>%s</h4>
|
||||
<div class="bootstrap-table">
|
||||
<div class="fixed-table-toolbar">
|
||||
<div class="bars">
|
||||
<div id="toolbar" class="pb-3">
|
||||
<button id="add" type="button" class="waves-effect waves-light btn">%s %s</button>
|
||||
<button id="delete" type="button" class="waves-effect waves-light btn" disabled>%s %s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-table-container">
|
||||
<table id="multi-sort" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="javascript:void(0)" class="modal-close waves-effect waves-light btn">%s</a>
|
||||
<a href="javascript:void(0)" class="modal-close waves-effect waves-light btn multi-sort-order-button">%s</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
multipleSortButton: '<a href="#%s" class="multi-sort waves-effect waves-light btn modal-trigger" type="button" data-toggle="modal" title="%s">%s</a>',
|
||||
multipleSortSelect: '<select class="%s %s browser-default">'
|
||||
}
|
||||
},
|
||||
foundation: {
|
||||
icons: {
|
||||
'plus': 'fa-plus',
|
||||
'minus': 'fa-minus',
|
||||
'sort': 'fa-sort'
|
||||
},
|
||||
html: {
|
||||
multipleSortModal: `
|
||||
<div class="reveal" id="%s" data-reveal aria-labelledby="%sLabel" aria-hidden="true">
|
||||
<div id="%sLabel">
|
||||
<h1>%s</h1>
|
||||
<div class="bootstrap-table">
|
||||
<div class="fixed-table-toolbar">
|
||||
<div class="bars">
|
||||
<div id="toolbar" class="padding-bottom-2">
|
||||
<button id="add" type="button" class="waves-effect waves-light button">%s %s</button>
|
||||
<button id="delete" type="button" class="waves-effect waves-light button" disabled>%s %s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-table-container">
|
||||
<table id="multi-sort" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="waves-effect waves-light button" data-close aria-label="Close modal" type="button">
|
||||
<span aria-hidden="true">%s</span>
|
||||
</button>
|
||||
<button class="waves-effect waves-light button multi-sort-order-button" data-close aria-label="Order" type="button">
|
||||
<span aria-hidden="true">%s</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
multipleSortButton: '<button class="button multi-sort" data-open="%s" title="%s">%s</button>',
|
||||
multipleSortSelect: '<select class="%s %s browser-default">'
|
||||
}
|
||||
},
|
||||
bulma: {
|
||||
icons: {
|
||||
'plus': 'fa-plus',
|
||||
'minus': 'fa-minus',
|
||||
'sort': 'fa-sort'
|
||||
},
|
||||
html: {
|
||||
multipleSortModal: `
|
||||
<div class="modal" id="%s" aria-labelledby="%sLabel" aria-hidden="true">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-content" id="%sLabel">
|
||||
<div class="box">
|
||||
<h2>%s</h2>
|
||||
<div class="bootstrap-table">
|
||||
<div class="fixed-table-toolbar">
|
||||
<div class="bars">
|
||||
<div id="toolbar" class="padding-bottom-2">
|
||||
<button id="add" type="button" class="waves-effect waves-light button">%s %s</button>
|
||||
<button id="delete" type="button" class="waves-effect waves-light button" disabled>%s %s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-table-container">
|
||||
<table id="multi-sort" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
<th><div class="th-inner">%s</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="waves-effect waves-light button" data-close>%s</button>
|
||||
<button type="button" class="waves-effect waves-light button multi-sort-order-button" data-close>%s</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
multipleSortButton: '<button class="button multi-sort" data-target="%s" title="%s">%s</button>',
|
||||
multipleSortSelect: '<select class="%s %s browser-default">'
|
||||
}
|
||||
}
|
||||
}[$.fn.bootstrapTable.theme]
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, bootstrap.icons)
|
||||
$.extend($.fn.bootstrapTable.defaults.html, bootstrap.html)
|
||||
|
||||
const showSortModal = that => {
|
||||
const _selector = that.sortModalSelector
|
||||
const _id = `#${_selector}`
|
||||
const o = that.options
|
||||
|
||||
if (!$(_id).hasClass('modal')) {
|
||||
const sModal = Utils.sprintf(
|
||||
that.constants.html.multipleSortModal,
|
||||
_selector, _selector, _selector,
|
||||
that.options.formatMultipleSort(),
|
||||
Utils.sprintf(that.constants.html.icon, o.iconsPrefix, that.constants.icons.plus),
|
||||
that.options.formatAddLevel(),
|
||||
Utils.sprintf(that.constants.html.icon, o.iconsPrefix, that.constants.icons.minus),
|
||||
that.options.formatDeleteLevel(),
|
||||
that.options.formatColumn(),
|
||||
that.options.formatOrder(),
|
||||
that.options.formatCancel(),
|
||||
that.options.formatSort()
|
||||
)
|
||||
|
||||
$('body').append($(sModal))
|
||||
|
||||
that.$sortModal = $(_id)
|
||||
const $rows = that.$sortModal.find('tbody > tr')
|
||||
|
||||
that.$sortModal.off('click', '#add').on('click', '#add', () => {
|
||||
const total = that.$sortModal.find('.multi-sort-name:first option').length
|
||||
let current = that.$sortModal.find('tbody tr').length
|
||||
|
||||
if (current < total) {
|
||||
current++
|
||||
that.addLevel()
|
||||
that.setButtonStates()
|
||||
}
|
||||
})
|
||||
|
||||
that.$sortModal.off('click', '#delete').on('click', '#delete', () => {
|
||||
const total = that.$sortModal.find('.multi-sort-name:first option').length
|
||||
let current = that.$sortModal.find('tbody tr').length
|
||||
|
||||
if (current > 1 && current <= total) {
|
||||
current--
|
||||
that.$sortModal.find('tbody tr:last').remove()
|
||||
that.setButtonStates()
|
||||
}
|
||||
})
|
||||
|
||||
that.$sortModal.off('click', '.multi-sort-order-button').on('click', '.multi-sort-order-button', () => {
|
||||
const $rows = that.$sortModal.find('tbody > tr')
|
||||
let $alert = that.$sortModal.find('div.alert')
|
||||
const fields = []
|
||||
const results = []
|
||||
|
||||
const sortPriority = $.map($rows, row => {
|
||||
const $row = $(row)
|
||||
const name = $row.find('.multi-sort-name').val()
|
||||
const order = $row.find('.multi-sort-order').val()
|
||||
|
||||
fields.push(name)
|
||||
|
||||
return {
|
||||
sortName: name,
|
||||
sortOrder: order
|
||||
}
|
||||
})
|
||||
|
||||
const sorted_fields = fields.sort()
|
||||
|
||||
for (let i = 0; i < fields.length - 1; i++) {
|
||||
if (sorted_fields[i + 1] === sorted_fields[i]) {
|
||||
results.push(sorted_fields[i])
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length > 0) {
|
||||
if ($alert.length === 0) {
|
||||
$alert = `<div class="alert alert-danger" role="alert"><strong>${that.options.formatDuplicateAlertTitle()}</strong> ${that.options.formatDuplicateAlertDescription()}</div>`
|
||||
$($alert).insertBefore(that.$sortModal.find('.bars'))
|
||||
}
|
||||
} else {
|
||||
if ($alert.length === 1) {
|
||||
$($alert).remove()
|
||||
}
|
||||
|
||||
if ($.inArray($.fn.bootstrapTable.theme, ['bootstrap3', 'bootstrap4']) !== -1) {
|
||||
that.$sortModal.modal('hide')
|
||||
}
|
||||
|
||||
that.multiSort(sortPriority)
|
||||
}
|
||||
})
|
||||
|
||||
if (that.options.sortPriority === null || that.options.sortPriority.length === 0) {
|
||||
if (that.options.sortName) {
|
||||
that.options.sortPriority = [{
|
||||
sortName: that.options.sortName,
|
||||
sortOrder: that.options.sortOrder
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
if (that.options.sortPriority !== null && that.options.sortPriority.length > 0) {
|
||||
if ($rows.length < that.options.sortPriority.length && typeof that.options.sortPriority === 'object') {
|
||||
for (let i = 0; i < that.options.sortPriority.length; i++) {
|
||||
that.addLevel(i, that.options.sortPriority[i])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
that.addLevel(0)
|
||||
}
|
||||
|
||||
that.setButtonStates()
|
||||
}
|
||||
}
|
||||
|
||||
$.fn.bootstrapTable.methods.push('multipleSort')
|
||||
$.fn.bootstrapTable.methods.push('multiSort')
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showMultiSort: false,
|
||||
showMultiSortButton: true,
|
||||
multiSortStrictSort: false,
|
||||
sortPriority: null,
|
||||
onMultipleSort () {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'multiple-sort.bs.table': 'onMultipleSort'
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatMultipleSort () {
|
||||
return 'Multiple Sort'
|
||||
},
|
||||
formatAddLevel () {
|
||||
return 'Add Level'
|
||||
},
|
||||
formatDeleteLevel () {
|
||||
return 'Delete Level'
|
||||
},
|
||||
formatColumn () {
|
||||
return 'Column'
|
||||
},
|
||||
formatOrder () {
|
||||
return 'Order'
|
||||
},
|
||||
formatSortBy () {
|
||||
return 'Sort by'
|
||||
},
|
||||
formatThenBy () {
|
||||
return 'Then by'
|
||||
},
|
||||
formatSort () {
|
||||
return 'Sort'
|
||||
},
|
||||
formatCancel () {
|
||||
return 'Cancel'
|
||||
},
|
||||
formatDuplicateAlertTitle () {
|
||||
return 'Duplicate(s) detected!'
|
||||
},
|
||||
formatDuplicateAlertDescription () {
|
||||
return 'Please remove or change any duplicate column.'
|
||||
},
|
||||
formatSortOrders () {
|
||||
return {
|
||||
asc: 'Ascending',
|
||||
desc: 'Descending'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
|
||||
|
||||
const BootstrapTable = $.fn.bootstrapTable.Constructor
|
||||
const _initToolbar = BootstrapTable.prototype.initToolbar
|
||||
const _destroy = BootstrapTable.prototype.destroy
|
||||
|
||||
BootstrapTable.prototype.initToolbar = function (...args) {
|
||||
this.showToolbar = this.showToolbar || this.options.showMultiSort
|
||||
const that = this
|
||||
const sortModalSelector = `sortModal_${this.$el.attr('id')}`
|
||||
const sortModalId = `#${sortModalSelector}`
|
||||
this.$sortModal = $(sortModalId)
|
||||
this.sortModalSelector = sortModalSelector
|
||||
|
||||
if (that.options.sortPriority !== null) {
|
||||
that.onMultipleSort()
|
||||
}
|
||||
|
||||
_initToolbar.apply(this, Array.prototype.slice.apply(args))
|
||||
|
||||
if (that.options.sidePagination === 'server' && !isSingleSort && that.options.sortPriority !== null) {
|
||||
const t = that.options.queryParams
|
||||
that.options.queryParams = params => {
|
||||
params.multiSort = that.options.sortPriority
|
||||
return t(params)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.showMultiSort) {
|
||||
const $btnGroup = this.$toolbar.find('>.' + that.constants.classes.buttonsGroup.split(' ').join('.')).first()
|
||||
let $multiSortBtn = this.$toolbar.find('div.multi-sort')
|
||||
const o = that.options
|
||||
|
||||
if (!$multiSortBtn.length && this.options.showMultiSortButton) {
|
||||
$multiSortBtn = Utils.sprintf(that.constants.html.multipleSortButton, that.sortModalSelector, this.options.formatMultipleSort(), Utils.sprintf(that.constants.html.icon, o.iconsPrefix, o.icons.sort))
|
||||
$btnGroup.append($multiSortBtn)
|
||||
|
||||
if ($.fn.bootstrapTable.theme === 'semantic') {
|
||||
this.$toolbar.find('.multi-sort').on('click', () => {
|
||||
$(sortModalId).modal('show')
|
||||
})
|
||||
} else if ($.fn.bootstrapTable.theme === 'materialize') {
|
||||
this.$toolbar.find('.multi-sort').on('click', () => {
|
||||
$(sortModalId).modal()
|
||||
})
|
||||
} else if ($.fn.bootstrapTable.theme === 'foundation') {
|
||||
this.$toolbar.find('.multi-sort').on('click', () => {
|
||||
if (!this.foundationModal) {
|
||||
// eslint-disable-next-line no-undef
|
||||
this.foundationModal = new Foundation.Reveal($(sortModalId))
|
||||
}
|
||||
this.foundationModal.open()
|
||||
})
|
||||
} else if ($.fn.bootstrapTable.theme === 'bulma') {
|
||||
this.$toolbar.find('.multi-sort').on('click', () => {
|
||||
$('html').toggleClass('is-clipped')
|
||||
$(sortModalId).toggleClass('is-active')
|
||||
$('button[data-close]').one('click', () => {
|
||||
$('html').toggleClass('is-clipped')
|
||||
$(sortModalId).toggleClass('is-active')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
showSortModal(that)
|
||||
}
|
||||
|
||||
this.$el.on('sort.bs.table', () => {
|
||||
isSingleSort = true
|
||||
})
|
||||
|
||||
this.$el.on('multiple-sort.bs.table', () => {
|
||||
isSingleSort = false
|
||||
})
|
||||
|
||||
this.$el.on('load-success.bs.table', () => {
|
||||
if (!isSingleSort && that.options.sortPriority !== null && typeof that.options.sortPriority === 'object' && that.options.sidePagination !== 'server') {
|
||||
that.onMultipleSort()
|
||||
}
|
||||
})
|
||||
|
||||
this.$el.on('column-switch.bs.table', (field, checked) => {
|
||||
for (let i = 0; i < that.options.sortPriority.length; i++) {
|
||||
if (that.options.sortPriority[i].sortName === checked) {
|
||||
that.options.sortPriority.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
||||
that.assignSortableArrows()
|
||||
that.$sortModal.remove()
|
||||
showSortModal(that)
|
||||
})
|
||||
|
||||
this.$el.on('reset-view.bs.table', () => {
|
||||
if (!isSingleSort && that.options.sortPriority !== null && typeof that.options.sortPriority === 'object') {
|
||||
that.assignSortableArrows()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.destroy = function (...args) {
|
||||
_destroy.apply(this, Array.prototype.slice.apply(args))
|
||||
|
||||
if (this.options.showMultiSort) {
|
||||
this.$sortModal.remove()
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.multipleSort = function () {
|
||||
const that = this
|
||||
if (!isSingleSort && that.options.sortPriority !== null && typeof that.options.sortPriority === 'object' && that.options.sidePagination !== 'server') {
|
||||
that.onMultipleSort()
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.onMultipleSort = function () {
|
||||
const that = this
|
||||
|
||||
const cmp = (x, y) => x > y ? 1 : x < y ? -1 : 0
|
||||
|
||||
const arrayCmp = (a, b) => {
|
||||
const arr1 = []
|
||||
const arr2 = []
|
||||
|
||||
for (let i = 0; i < that.options.sortPriority.length; i++) {
|
||||
let fieldName = that.options.sortPriority[i].sortName
|
||||
const fieldIndex = that.header.fields.indexOf(fieldName)
|
||||
const sorterName = that.header.sorters[that.header.fields.indexOf(fieldName)]
|
||||
|
||||
if (that.header.sortNames[fieldIndex]) {
|
||||
fieldName = that.header.sortNames[fieldIndex]
|
||||
}
|
||||
|
||||
const order = that.options.sortPriority[i].sortOrder === 'desc' ? -1 : 1
|
||||
let aa = Utils.getItemField(a, fieldName)
|
||||
let bb = Utils.getItemField(b, fieldName)
|
||||
const value1 = $.fn.bootstrapTable.utils.calculateObjectValue(that.header, sorterName, [aa, bb])
|
||||
const value2 = $.fn.bootstrapTable.utils.calculateObjectValue(that.header, sorterName, [bb, aa])
|
||||
|
||||
if (value1 !== undefined && value2 !== undefined) {
|
||||
arr1.push(order * value1)
|
||||
arr2.push(order * value2)
|
||||
continue
|
||||
}
|
||||
|
||||
if (aa === undefined || aa === null) aa = ''
|
||||
if (bb === undefined || bb === null) bb = ''
|
||||
|
||||
if ($.isNumeric(aa) && $.isNumeric(bb)) {
|
||||
aa = parseFloat(aa)
|
||||
bb = parseFloat(bb)
|
||||
} else {
|
||||
aa = aa.toString()
|
||||
bb = bb.toString()
|
||||
|
||||
if (that.options.multiSortStrictSort) {
|
||||
aa = aa.toLowerCase()
|
||||
bb = bb.toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
arr1.push(order * cmp(aa, bb))
|
||||
arr2.push(order * cmp(bb, aa))
|
||||
}
|
||||
|
||||
return cmp(arr1, arr2)
|
||||
}
|
||||
|
||||
this.data.sort((a, b) => arrayCmp(a, b))
|
||||
|
||||
this.initBody()
|
||||
this.assignSortableArrows()
|
||||
this.trigger('multiple-sort')
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.addLevel = function (index, sortPriority) {
|
||||
const text = index === 0 ? this.options.formatSortBy() : this.options.formatThenBy()
|
||||
|
||||
this.$sortModal.find('tbody')
|
||||
.append($('<tr>')
|
||||
.append($('<td>').text(text))
|
||||
.append($('<td>').append($(Utils.sprintf(this.constants.html.multipleSortSelect, this.constants.classes.paginationDropdown, 'multi-sort-name'))))
|
||||
.append($('<td>').append($(Utils.sprintf(this.constants.html.multipleSortSelect, this.constants.classes.paginationDropdown, 'multi-sort-order'))))
|
||||
)
|
||||
|
||||
const $multiSortName = this.$sortModal.find('.multi-sort-name').last()
|
||||
const $multiSortOrder = this.$sortModal.find('.multi-sort-order').last()
|
||||
|
||||
$.each(this.columns, (i, column) => {
|
||||
if (column.sortable === false || column.visible === false) {
|
||||
return true
|
||||
}
|
||||
$multiSortName.append(`<option value="${column.field}">${column.title}</option>`)
|
||||
})
|
||||
|
||||
$.each(this.options.formatSortOrders(), (value, order) => {
|
||||
$multiSortOrder.append(`<option value="${value}">${order}</option>`)
|
||||
})
|
||||
|
||||
if (sortPriority !== undefined) {
|
||||
$multiSortName.find(`option[value="${sortPriority.sortName}"]`).attr('selected', true)
|
||||
$multiSortOrder.find(`option[value="${sortPriority.sortOrder}"]`).attr('selected', true)
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.assignSortableArrows = function () {
|
||||
const that = this
|
||||
const headers = that.$header.find('th')
|
||||
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
for (let c = 0; c < that.options.sortPriority.length; c++) {
|
||||
if ($(headers[i]).data('field') === that.options.sortPriority[c].sortName) {
|
||||
$(headers[i]).find('.sortable').removeClass('desc asc').addClass(that.options.sortPriority[c].sortOrder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.setButtonStates = function () {
|
||||
const total = this.$sortModal.find('.multi-sort-name:first option').length
|
||||
const current = this.$sortModal.find('tbody tr').length
|
||||
|
||||
if (current === total) {
|
||||
this.$sortModal.find('#add').attr('disabled', 'disabled')
|
||||
}
|
||||
if (current > 1) {
|
||||
this.$sortModal.find('#delete').removeAttr('disabled')
|
||||
}
|
||||
if (current < total) {
|
||||
this.$sortModal.find('#add').removeAttr('disabled')
|
||||
}
|
||||
if (current === 1) {
|
||||
this.$sortModal.find('#delete').attr('disabled', 'disabled')
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.multiSort = function (sortPriority) {
|
||||
this.options.sortPriority = sortPriority
|
||||
this.options.sortName = ''
|
||||
|
||||
if (this.options.sidePagination === 'server') {
|
||||
this.options.queryParams = params => {
|
||||
params.multiSort = this.options.sortPriority
|
||||
return $.fn.bootstrapTable.utils.calculateObjectValue(this.options, this.options.queryParams, [params])
|
||||
}
|
||||
isSingleSort = false
|
||||
this.initServer(this.options.silentSort)
|
||||
return
|
||||
}
|
||||
|
||||
this.onMultipleSort()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Multiple Sort",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to support the multiple sort.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-sort",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-multiple-sort",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-sort"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "dimbslmh",
|
||||
"image": "https://avatars1.githubusercontent.com/u/745635"
|
||||
}
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @author Jay <jwang@dizsoft.com>
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showJumpTo: false
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatJumpTo () {
|
||||
return 'GO'
|
||||
}
|
||||
})
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
initPagination (...args) {
|
||||
super.initPagination(...args)
|
||||
|
||||
if (this.options.showJumpTo) {
|
||||
const $pageGroup = this.$pagination.find('> .pagination')
|
||||
let $jumpTo = $pageGroup.find('.page-jump-to')
|
||||
|
||||
if (!$jumpTo.length) {
|
||||
$jumpTo = $(`
|
||||
<div class="page-jump-to ${this.constants.classes.inputGroup}">
|
||||
<input type="number" class="${this.constants.classes.input}${Utils.sprintf(' input-%s', this.options.iconSize)}" value="${this.options.pageNumber}">
|
||||
<button class="${this.constants.buttonsClass}" type="button">
|
||||
${this.options.formatJumpTo()}
|
||||
</button>
|
||||
</div>
|
||||
`).appendTo($pageGroup)
|
||||
|
||||
$jumpTo.on('click', 'button', (e) => {
|
||||
this.selectPage(+$(e.target).parent('.page-jump-to').find('input').val())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
.bootstrap-table.bootstrap3 .fixed-table-pagination > .pagination ul.pagination,
|
||||
.bootstrap-table.bootstrap3 .fixed-table-pagination > .pagination .page-jump-to {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.bootstrap-table .fixed-table-pagination > .pagination .page-jump-to input {
|
||||
width: 70px;
|
||||
margin-left: 5px;
|
||||
text-align: center;
|
||||
float: left;
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Manuel Martínez-Almeida
|
||||
Copyright (c) 2019 doug-the-guy <badlydrawnsun@yahoo.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -0,0 +1,92 @@
|
||||
# Bootstrap Table Pipelining
|
||||
|
||||
Use Plugin: [bootstrap-table-pipeline]
|
||||
|
||||
This plugin enables client side data caching for server side requests which will
|
||||
eliminate the need to issue a new request every page change. This will allow
|
||||
for a performance balance for a large data set between returning all data at once
|
||||
(client side paging) and a new server side request (server side paging).
|
||||
|
||||
There are two new options:
|
||||
- usePipeline: enables this feature
|
||||
- pipelineSize: the size of each cache window
|
||||
|
||||
The size of the pipeline must be evenly divisible by the current page size. This is
|
||||
assured by rounding up to the nearest evenly divisible value. For example, if
|
||||
the pipeline size is 4990 and the current page size is 25, then pipeline size will
|
||||
be dynamically set to 5000.
|
||||
|
||||
The cache windows are computed based on the pipeline size and the total number of rows
|
||||
returned by the server side query. For example, with pipeline size 500 and total rows
|
||||
1300, the cache windows will be:
|
||||
|
||||
[{'lower': 0, 'upper': 499}, {'lower': 500, 'upper': 999}, {'lower': 1000, 'upper': 1499}]
|
||||
|
||||
Using the limit (i.e. the pipelineSize) and offset parameters, the server side request
|
||||
**MUST** return only the data in the requested cache window **AND** the total number of rows.
|
||||
To wit, the server side code must use the offset and limit parameters to prepare the response
|
||||
data.
|
||||
|
||||
On a page change, the new offset is checked if it is within the current cache window. If so,
|
||||
the requested page data is returned from the cached data set. Otherwise, a new server side
|
||||
request will be issued for the new cache window.
|
||||
|
||||
The current cached data is only invalidated on these events:
|
||||
- sorting
|
||||
- searching
|
||||
- page size change
|
||||
- page change moves into a new cache window
|
||||
|
||||
There are two new events:
|
||||
- cached-data-hit.bs.table: issued when cached data is used on a page change
|
||||
- cached-data-reset.bs.table: issued when the cached data is invalidated and new server side request is issued
|
||||
|
||||
## Features
|
||||
|
||||
* Created with Bootstrap 4
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
# assumed import of bootstrap and bootstrap-table assets
|
||||
<script src="/path/to/bootstrap-table-pipeline.js"></script>
|
||||
...
|
||||
<table id="pipeline_table"
|
||||
class="table table-striped"
|
||||
data-method='post'
|
||||
data-use-pipeline="true"
|
||||
data-pipeline-size="5000"
|
||||
data-pagination="true"
|
||||
data-side-pagination="server"
|
||||
data-page-size="50">
|
||||
<thead><tr>
|
||||
<th data-field="type" data-sortable="true">Type</th>
|
||||
<th data-field="value" data-sortable="true">Value</th>
|
||||
<th data-field="date" data-sortable="true">Date</th>
|
||||
</tr></thead>
|
||||
</table>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### usePipeline
|
||||
|
||||
* type: Boolean
|
||||
* description: Set true to enable pipelining
|
||||
* default: `false`
|
||||
|
||||
## pipelineSize
|
||||
|
||||
* type: Integer
|
||||
* description: Size of each cache window. Must be greater than 0
|
||||
* default: `1000`
|
||||
|
||||
## Events
|
||||
|
||||
### onCachedDataHit(cached-data-hit.bs.table)
|
||||
|
||||
* Fires when paging was able to use the locally cached data.
|
||||
|
||||
### onCachedDataReset(cached-data-reset.bs.table)
|
||||
|
||||
* Fires when the locally cached data needed to be reset (i.e. on sorting, searching, page size change or paged out of current cache window)
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* @author doug-the-guy
|
||||
* @version v1.0.0
|
||||
*
|
||||
* Bootstrap Table Pipeline
|
||||
* -----------------------
|
||||
*
|
||||
* This plugin enables client side data caching for server side requests which will
|
||||
* eliminate the need to issue a new request every page change. This will allow
|
||||
* for a performance balance for a large data set between returning all data at once
|
||||
* (client side paging) and a new server side request (server side paging).
|
||||
*
|
||||
* There are two new options:
|
||||
* - usePipeline: enables this feature
|
||||
* - pipelineSize: the size of each cache window
|
||||
*
|
||||
* The size of the pipeline must be evenly divisible by the current page size. This is
|
||||
* assured by rounding up to the nearest evenly divisible value. For example, if
|
||||
* the pipeline size is 4990 and the current page size is 25, then pipeline size will
|
||||
* be dynamically set to 5000.
|
||||
*
|
||||
* The cache windows are computed based on the pipeline size and the total number of rows
|
||||
* returned by the server side query. For example, with pipeline size 500 and total rows
|
||||
* 1300, the cache windows will be:
|
||||
*
|
||||
* [{'lower': 0, 'upper': 499}, {'lower': 500, 'upper': 999}, {'lower': 1000, 'upper': 1499}]
|
||||
*
|
||||
* Using the limit (i.e. the pipelineSize) and offset parameters, the server side request
|
||||
* **MUST** return only the data in the requested cache window **AND** the total number of rows.
|
||||
* To wit, the server side code must use the offset and limit parameters to prepare the response
|
||||
* data.
|
||||
*
|
||||
* On a page change, the new offset is checked if it is within the current cache window. If so,
|
||||
* the requested page data is returned from the cached data set. Otherwise, a new server side
|
||||
* request will be issued for the new cache window.
|
||||
*
|
||||
* The current cached data is only invalidated on these events:
|
||||
* * sorting
|
||||
* * searching
|
||||
* * page size change
|
||||
* * page change moves into a new cache window
|
||||
*
|
||||
* There are two new events:
|
||||
* - cached-data-hit.bs.table: issued when cached data is used on a page change
|
||||
* - cached-data-reset.bs.table: issued when the cached data is invalidated and a
|
||||
* new server side request is issued
|
||||
*
|
||||
**/
|
||||
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
usePipeline: false,
|
||||
pipelineSize: 1000,
|
||||
onCachedDataHit (data) {
|
||||
return false
|
||||
},
|
||||
onCachedDataReset (data) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'cached-data-hit.bs.table': 'onCachedDataHit',
|
||||
'cached-data-reset.bs.table': 'onCachedDataReset'
|
||||
})
|
||||
|
||||
const BootstrapTable = $.fn.bootstrapTable.Constructor
|
||||
const _init = BootstrapTable.prototype.init
|
||||
const _initServer = BootstrapTable.prototype.initServer
|
||||
const _onSearch = BootstrapTable.prototype.onSearch
|
||||
const _onSort = BootstrapTable.prototype.onSort
|
||||
const _onPageListChange = BootstrapTable.prototype.onPageListChange
|
||||
|
||||
BootstrapTable.prototype.init = function (...args) {
|
||||
// needs to be called before initServer()
|
||||
this.initPipeline()
|
||||
_init.apply(this, Array.prototype.slice.apply(args))
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.initPipeline = function () {
|
||||
this.cacheRequestJSON = {}
|
||||
this.cacheWindows = []
|
||||
this.currWindow = 0
|
||||
this.resetCache = true
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.onSearch = function (event) {
|
||||
/* force a cache reset on search */
|
||||
if (this.options.usePipeline) {
|
||||
this.resetCache = true
|
||||
}
|
||||
_onSearch.apply(this, Array.prototype.slice.apply(arguments))
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.onSort = function (event) {
|
||||
/* force a cache reset on sort */
|
||||
if (this.options.usePipeline) {
|
||||
this.resetCache = true
|
||||
}
|
||||
_onSort.apply(this, Array.prototype.slice.apply(arguments))
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.onPageListChange = function (event) {
|
||||
/* rebuild cache window on page size change */
|
||||
const target = $(event.currentTarget)
|
||||
const newPageSize = parseInt(target.text())
|
||||
this.options.pipelineSize = this.calculatePipelineSize(this.options.pipelineSize, newPageSize)
|
||||
this.resetCache = true
|
||||
_onPageListChange.apply(this, Array.prototype.slice.apply(arguments))
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.calculatePipelineSize = (pipelineSize, pageSize) => {
|
||||
/* calculate pipeline size by rounding up to the nearest value evenly divisible
|
||||
* by the pageSize */
|
||||
if (pageSize === 0) return 0
|
||||
return Math.ceil(pipelineSize / pageSize) * pageSize
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.setCacheWindows = function () {
|
||||
/* set cache windows based on the total number of rows returned by server side
|
||||
* request and the pipelineSize */
|
||||
this.cacheWindows = []
|
||||
const numWindows = this.options.totalRows / this.options.pipelineSize
|
||||
for (let i = 0; i <= numWindows; i++) {
|
||||
const b = i * this.options.pipelineSize
|
||||
this.cacheWindows[i] = {'lower': b, 'upper': b + this.options.pipelineSize - 1}
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.setCurrWindow = function (offset) {
|
||||
/* set the current cache window index, based on where the current offset falls */
|
||||
this.currWindow = 0
|
||||
for (let i = 0; i < this.cacheWindows.length; i++) {
|
||||
if (this.cacheWindows[i].lower <= offset && offset <= this.cacheWindows[i].upper) {
|
||||
this.currWindow = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.drawFromCache = function (offset, limit) {
|
||||
/* draw rows from the cache using offset and limit */
|
||||
const res = $.extend(true, {}, this.cacheRequestJSON)
|
||||
const drawStart = offset - this.cacheWindows[this.currWindow].lower
|
||||
const drawEnd = drawStart + limit
|
||||
res.rows = res.rows.slice(drawStart, drawEnd)
|
||||
return res
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.initServer = function (silent, query, url) {
|
||||
/* determine if requested data is in cache (on paging) or if
|
||||
* a new ajax request needs to be issued (sorting, searching, paging
|
||||
* moving outside of cached data, page size change)
|
||||
* initial version of this extension will entirely override base initServer
|
||||
**/
|
||||
|
||||
let data = {}
|
||||
const index = this.header.fields.indexOf(this.options.sortName)
|
||||
|
||||
let params = {
|
||||
searchText: this.searchText,
|
||||
sortName: this.options.sortName,
|
||||
sortOrder: this.options.sortOrder
|
||||
}
|
||||
|
||||
let request = null
|
||||
|
||||
if (this.header.sortNames[index]) {
|
||||
params.sortName = this.header.sortNames[index]
|
||||
}
|
||||
|
||||
if (this.options.pagination && this.options.sidePagination === 'server') {
|
||||
params.pageSize = this.options.pageSize === this.options.formatAllRows()
|
||||
? this.options.totalRows : this.options.pageSize
|
||||
params.pageNumber = this.options.pageNumber
|
||||
}
|
||||
|
||||
if (!(url || this.options.url) && !this.options.ajax) {
|
||||
return
|
||||
}
|
||||
|
||||
let useAjax = true
|
||||
if (this.options.queryParamsType === 'limit') {
|
||||
params = {
|
||||
searchText: params.searchText,
|
||||
sortName: params.sortName,
|
||||
sortOrder: params.sortOrder
|
||||
}
|
||||
if (this.options.pagination && this.options.sidePagination === 'server') {
|
||||
params.limit = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize
|
||||
params.offset = (this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize) * (this.options.pageNumber - 1)
|
||||
if (this.options.usePipeline) {
|
||||
// if cacheWindows is empty, this is the initial request
|
||||
if (!this.cacheWindows.length) {
|
||||
useAjax = true
|
||||
params.drawOffset = params.offset
|
||||
// cache exists: determine if the page request is entirely within the current cached window
|
||||
} else {
|
||||
const w = this.cacheWindows[this.currWindow]
|
||||
// case 1: reset cache but stay within current window (e.g. column sort)
|
||||
// case 2: move outside of the current window (e.g. search or paging)
|
||||
// since each cache window is aligned with the current page size
|
||||
// checking if params.offset is outside the current window is sufficient.
|
||||
// need to requery for preceding or succeeding cache window
|
||||
// also handle case
|
||||
if (this.resetCache || (params.offset < w.lower || params.offset > w.upper)) {
|
||||
useAjax = true
|
||||
this.setCurrWindow(params.offset)
|
||||
// store the relative offset for drawing the page data afterwards
|
||||
params.drawOffset = params.offset
|
||||
// now set params.offset to the lower bound of the new cache window
|
||||
// the server will return that whole cache window
|
||||
params.offset = this.cacheWindows[this.currWindow].lower
|
||||
// within current cache window
|
||||
} else {
|
||||
useAjax = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (params.limit === 0) {
|
||||
delete params.limit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// force an ajax call - this is on search, sort or page size change
|
||||
if (this.resetCache) {
|
||||
useAjax = true
|
||||
this.resetCache = false
|
||||
}
|
||||
|
||||
if (this.options.usePipeline && useAjax) {
|
||||
/* in this scenario limit is used on the server to get the cache window
|
||||
* and drawLimit is used to get the page data afterwards */
|
||||
params.drawLimit = params.limit
|
||||
params.limit = this.options.pipelineSize
|
||||
}
|
||||
|
||||
// cached results can be used
|
||||
if (!useAjax) {
|
||||
const res = this.drawFromCache(params.offset, params.limit)
|
||||
this.load(res)
|
||||
this.trigger('load-success', res)
|
||||
this.trigger('cached-data-hit', res)
|
||||
return
|
||||
}
|
||||
// cached results can't be used
|
||||
// continue base initServer code
|
||||
if (!($.isEmptyObject(this.filterColumnsPartial))) {
|
||||
params.filter = JSON.stringify(this.filterColumnsPartial, null)
|
||||
}
|
||||
|
||||
data = Utils.calculateObjectValue(this.options, this.options.queryParams, [params], data)
|
||||
|
||||
$.extend(data, query || {})
|
||||
|
||||
// false to stop request
|
||||
if (data === false) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
this.$tableLoading.show()
|
||||
}
|
||||
const self = this
|
||||
|
||||
request = $.extend({}, Utils.calculateObjectValue(null, this.options.ajaxOptions), {
|
||||
type: this.options.method,
|
||||
url: url || this.options.url,
|
||||
data: this.options.contentType === 'application/json' && this.options.method === 'post'
|
||||
? JSON.stringify(data) : data,
|
||||
cache: this.options.cache,
|
||||
contentType: this.options.contentType,
|
||||
dataType: this.options.dataType,
|
||||
success (res) {
|
||||
res = Utils.calculateObjectValue(self.options, self.options.responseHandler, [res], res)
|
||||
// cache results if using pipelining
|
||||
if (self.options.usePipeline) {
|
||||
// store entire request in cache
|
||||
self.cacheRequestJSON = $.extend(true, {}, res)
|
||||
// this gets set in load() also but needs to be set before
|
||||
// setting cacheWindows
|
||||
self.options.totalRows = res[self.options.totalField]
|
||||
// if this is a search, potentially less results will be returned
|
||||
// so cache windows need to be rebuilt. Otherwise it
|
||||
// will come out the same
|
||||
self.setCacheWindows()
|
||||
self.setCurrWindow(params.drawOffset)
|
||||
// just load data for the page
|
||||
res = self.drawFromCache(params.drawOffset, params.drawLimit)
|
||||
self.trigger('cached-data-reset', res)
|
||||
}
|
||||
self.load(res)
|
||||
self.trigger('load-success', res)
|
||||
if (!silent) self.$tableLoading.hide()
|
||||
},
|
||||
error (res) {
|
||||
let data = []
|
||||
if (self.options.sidePagination === 'server') {
|
||||
data = {}
|
||||
data[self.options.totalField] = 0
|
||||
data[self.options.dataField] = []
|
||||
}
|
||||
self.load(data)
|
||||
self.trigger('load-error', res.status, res)
|
||||
if (!silent) self.$tableLoading.hide()
|
||||
}
|
||||
})
|
||||
|
||||
if (this.options.ajax) {
|
||||
Utils.calculateObjectValue(this, this.options.ajax, [request], null)
|
||||
} else {
|
||||
if (this._xhr && this._xhr.readyState !== 4) {
|
||||
this._xhr.abort()
|
||||
}
|
||||
this._xhr = $.ajax(request)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Pipeline",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support a hybrid approach to server/client side paging.",
|
||||
"url": "",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-pipeline",
|
||||
"url": ""
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "doug-the-guy",
|
||||
"image": ""
|
||||
}
|
||||
}
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
function printPageBuilderDefault (table) {
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css" media="print">
|
||||
@page {
|
||||
size: auto;
|
||||
margin: 25px 0 25px 0;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css" media="all">
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid grey;
|
||||
}
|
||||
th, td {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
p {
|
||||
font-weight: bold;
|
||||
margin-left:20px;
|
||||
}
|
||||
table {
|
||||
width:94%;
|
||||
margin-left:3%;
|
||||
margin-right:3%;
|
||||
}
|
||||
div.bs-table-print {
|
||||
text-align:center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<title>Print Table</title>
|
||||
<body>
|
||||
<p>Printed on: ${new Date} </p>
|
||||
<div class="bs-table-print">${table}</div>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showPrint: false,
|
||||
printAsFilteredAndSortedOnUI: true,
|
||||
printSortColumn: undefined,
|
||||
printSortOrder: 'asc',
|
||||
printPageBuilder (table) {
|
||||
return printPageBuilderDefault(table)
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
|
||||
printFilter: undefined,
|
||||
printIgnore: false,
|
||||
printFormatter: undefined
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, {
|
||||
print: {
|
||||
bootstrap3: 'glyphicon-print icon-share'
|
||||
}[$.fn.bootstrapTable.theme] || 'fa-print'
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
initToolbar (...args) {
|
||||
this.showToolbar = this.showToolbar || this.options.showPrint
|
||||
|
||||
super.initToolbar(...args)
|
||||
|
||||
if (!this.options.showPrint) {
|
||||
return
|
||||
}
|
||||
|
||||
const $btnGroup = this.$toolbar.find('>.columns')
|
||||
let $print = $btnGroup.find('button.bs-print')
|
||||
|
||||
if (!$print.length) {
|
||||
$print = $(`
|
||||
<button class="${this.constants.buttonsClass} bs-print" type="button">
|
||||
<i class="${this.options.iconsPrefix} ${this.options.icons.print}"></i>
|
||||
</button>`
|
||||
).appendTo($btnGroup)
|
||||
}
|
||||
|
||||
$print.off('click').on('click', () => {
|
||||
this.doPrint(this.options.printAsFilteredAndSortedOnUI ?
|
||||
this.getData() : this.options.data.slice(0))
|
||||
})
|
||||
}
|
||||
|
||||
doPrint (data) {
|
||||
const formatValue = (row, i, column ) => {
|
||||
const value = Utils.calculateObjectValue(column, column.printFormatter,
|
||||
[row[column.field], row, i], row[column.field])
|
||||
|
||||
return typeof value === 'undefined' || value === null
|
||||
? this.options.undefinedText : value
|
||||
}
|
||||
|
||||
const buildTable = (data, columnsArray) => {
|
||||
const dir = this.$el.attr('dir') || 'ltr'
|
||||
const html = [`<table dir="${dir}"><thead>`]
|
||||
|
||||
for (const columns of columnsArray) {
|
||||
html.push('<tr>')
|
||||
for (let h = 0; h < columns.length; h++) {
|
||||
if (!columns[h].printIgnore) {
|
||||
html.push(
|
||||
`<th
|
||||
${Utils.sprintf(' rowspan="%s"', columns[h].rowspan)}
|
||||
${Utils.sprintf(' colspan="%s"', columns[h].colspan)}
|
||||
>${columns[h].title}</th>`)
|
||||
}
|
||||
}
|
||||
html.push('</tr>')
|
||||
}
|
||||
|
||||
html.push('</thead><tbody>')
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
html.push('<tr>')
|
||||
|
||||
for (const columns of columnsArray) {
|
||||
for (let j = 0; j < columns.length; j++) {
|
||||
if (!columns[j].printIgnore && columns[j].field) {
|
||||
html.push('<td>', formatValue(data[i], i, columns[j]), '</td>')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.push('</tr>')
|
||||
}
|
||||
html.push('</tbody></table>')
|
||||
return html.join('')
|
||||
}
|
||||
|
||||
const sortRows = (data, colName, sortOrder) => {
|
||||
if (!colName) {
|
||||
return data
|
||||
}
|
||||
let reverse = sortOrder !== 'asc'
|
||||
reverse = -((+reverse) || -1)
|
||||
return data.sort((a, b) => reverse * (a[colName].localeCompare(b[colName])))
|
||||
}
|
||||
|
||||
const filterRow = (row, filters) => {
|
||||
for (let index = 0; index < filters.length; ++index) {
|
||||
if (row[filters[index].colName] !== filters[index].value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const filterRows = (data, filters) => data.filter(row => filterRow(row,filters))
|
||||
|
||||
const getColumnFilters = columns => !columns || !columns[0] ? [] : columns[0].filter(col => col.printFilter).map(col => ({
|
||||
colName: col.field,
|
||||
value: col.printFilter
|
||||
}))
|
||||
|
||||
data = filterRows(data,getColumnFilters(this.options.columns))
|
||||
data = sortRows(data, this.options.printSortColumn, this.options.printSortOrder)
|
||||
const table = buildTable(data, this.options.columns)
|
||||
const newWin = window.open('')
|
||||
newWin.document.write(this.options.printPageBuilder.call(this, table))
|
||||
newWin.document.close()
|
||||
newWin.focus()
|
||||
newWin.print()
|
||||
newWin.close()
|
||||
}
|
||||
}
|
||||
Vendored
+199
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @update: https://github.com/wenzhixin
|
||||
* @version: v1.2.0
|
||||
*/
|
||||
|
||||
$.akottr.dragtable.prototype._restoreState = function (persistObj) {
|
||||
for (const [field, value] of Object.entries(persistObj)) {
|
||||
var $th = this.originalTable.el.find(`th[data-field="${field}"]`)
|
||||
this.originalTable.startIndex = $th.prevAll().length + 1
|
||||
this.originalTable.endIndex = parseInt(value, 10) + 1
|
||||
this._bubbleCols()
|
||||
}
|
||||
}
|
||||
|
||||
// From MDN site, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
|
||||
const filterFn = () => {
|
||||
if (!Array.prototype.filter) {
|
||||
Array.prototype.filter = function (fun/* , thisArg*/) {
|
||||
if (this === undefined || this === null) {
|
||||
throw new TypeError()
|
||||
}
|
||||
|
||||
const t = Object(this)
|
||||
const len = t.length >>> 0
|
||||
if (typeof fun !== 'function') {
|
||||
throw new TypeError()
|
||||
}
|
||||
|
||||
const res = []
|
||||
const thisArg = arguments.length >= 2 ? arguments[1] : undefined
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (i in t) {
|
||||
const val = t[i]
|
||||
|
||||
// NOTE: Technically this should Object.defineProperty at
|
||||
// the next index, as push can be affected by
|
||||
// properties on Object.prototype and Array.prototype.
|
||||
// But this method's new, and collisions should be
|
||||
// rare, so use the more-compatible alternative.
|
||||
if (fun.call(thisArg, val, i, t)) {
|
||||
res.push(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
reorderableColumns: false,
|
||||
maxMovingRows: 10,
|
||||
onReorderColumn (headerFields) {
|
||||
return false
|
||||
},
|
||||
dragaccept: null
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'reorder-column.bs.table': 'onReorderColumn'
|
||||
})
|
||||
|
||||
$.fn.bootstrapTable.methods.push('orderColumns')
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
initHeader (...args) {
|
||||
super.initHeader(...args)
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return
|
||||
}
|
||||
|
||||
this.makeRowsReorderable()
|
||||
}
|
||||
|
||||
_toggleColumn (...args) {
|
||||
super._toggleColumn(...args)
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return
|
||||
}
|
||||
|
||||
this.makeRowsReorderable()
|
||||
}
|
||||
|
||||
toggleView (...args) {
|
||||
super.toggleView(...args)
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.options.cardView) {
|
||||
return
|
||||
}
|
||||
|
||||
this.makeRowsReorderable()
|
||||
}
|
||||
|
||||
resetView (...args) {
|
||||
super.resetView(...args)
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return
|
||||
}
|
||||
|
||||
this.makeRowsReorderable()
|
||||
}
|
||||
|
||||
makeRowsReorderable (order = null) {
|
||||
try {
|
||||
$(this.$el).dragtable('destroy')
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
$(this.$el).dragtable({
|
||||
maxMovingRows: this.options.maxMovingRows,
|
||||
dragaccept: this.options.dragaccept,
|
||||
clickDelay: 200,
|
||||
dragHandle: '.th-inner',
|
||||
restoreState: order ? order : this.columnsSortOrder,
|
||||
beforeStop: (table) => {
|
||||
const sortOrder = {}
|
||||
table.el.find('th').each((i, el) => {
|
||||
sortOrder[$(el).data('field')] = i
|
||||
})
|
||||
|
||||
this.columnsSortOrder = sortOrder
|
||||
if (this.options.cookie) {
|
||||
this.persistReorderColumnsState(this)
|
||||
}
|
||||
|
||||
const ths = []
|
||||
const formatters = []
|
||||
const columns = []
|
||||
let columnsHidden = []
|
||||
let columnIndex = -1
|
||||
const optionsColumns = []
|
||||
this.$header.find('th:not(.detail)').each(function (i) {
|
||||
ths.push($(this).data('field'))
|
||||
formatters.push($(this).data('formatter'))
|
||||
})
|
||||
|
||||
// Exist columns not shown
|
||||
if (ths.length < this.columns.length) {
|
||||
columnsHidden = this.columns.filter(column => !column.visible)
|
||||
for (var i = 0; i < columnsHidden.length; i++) {
|
||||
ths.push(columnsHidden[i].field)
|
||||
formatters.push(columnsHidden[i].formatter)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < ths.length; i++) {
|
||||
columnIndex = this.fieldsColumnsIndex[ths[i]]
|
||||
if (columnIndex !== -1) {
|
||||
this.fieldsColumnsIndex[ths[i]] = i
|
||||
this.columns[columnIndex].fieldIndex = i
|
||||
columns.push(this.columns[columnIndex])
|
||||
}
|
||||
}
|
||||
|
||||
this.columns = columns
|
||||
|
||||
filterFn() // Support <IE9
|
||||
$.each(this.columns, (i, column) => {
|
||||
let found = false
|
||||
const field = column.field
|
||||
this.options.columns[0].filter(item => {
|
||||
if (!found && item['field'] === field) {
|
||||
optionsColumns.push(item)
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
this.options.columns[0] = optionsColumns
|
||||
|
||||
this.header.fields = ths
|
||||
this.header.formatters = formatters
|
||||
this.initHeader()
|
||||
this.initToolbar()
|
||||
this.initSearchText()
|
||||
this.initBody()
|
||||
this.resetView()
|
||||
this.trigger('reorder-column', ths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
orderColumns (order) {
|
||||
this.columnsSortOrder = order
|
||||
this.makeRowsReorderable()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Reorder Columns",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to support the reordering columns feature.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-columns",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/reorder-columns.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-reorder-columns",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-columns"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
Vendored
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
const rowAttr = (row, index) => ({
|
||||
id: `customId_${index}`
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
reorderableRows: false,
|
||||
onDragStyle: null,
|
||||
onDropStyle: null,
|
||||
onDragClass: 'reorder_rows_onDragClass',
|
||||
dragHandle: '>tbody>tr>td',
|
||||
useRowAttrFunc: false,
|
||||
onReorderRowsDrag (row) {
|
||||
return false
|
||||
},
|
||||
onReorderRowsDrop (row) {
|
||||
return false
|
||||
},
|
||||
onReorderRow (newData) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'reorder-row.bs.table': 'onReorderRow'
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
init (...args) {
|
||||
if (!this.options.reorderableRows) {
|
||||
super.init(...args)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.options.useRowAttrFunc) {
|
||||
this.options.rowAttributes = rowAttr
|
||||
}
|
||||
|
||||
const onPostBody = this.options.onPostBody
|
||||
this.options.onPostBody = () => {
|
||||
setTimeout(() => {
|
||||
this.makeRowsReorderable()
|
||||
onPostBody.call(this.options, this.options.data)
|
||||
}, 1)
|
||||
}
|
||||
|
||||
super.init(...args)
|
||||
}
|
||||
|
||||
makeRowsReorderable () {
|
||||
this.$el.tableDnD({
|
||||
onDragStyle: this.options.onDragStyle,
|
||||
onDropStyle: this.options.onDropStyle,
|
||||
onDragClass: this.options.onDragClass,
|
||||
onDragStart: (table, droppedRow) => this.onDropStart(table, droppedRow),
|
||||
onDrop: (table, droppedRow) => this.onDrop(table, droppedRow),
|
||||
dragHandle: this.options.dragHandle
|
||||
})
|
||||
}
|
||||
|
||||
onDropStart (table, draggingTd) {
|
||||
this.$draggingTd = $(draggingTd).css('cursor', 'move')
|
||||
this.draggingIndex = $(this.$draggingTd.parent()).data('index')
|
||||
// Call the user defined function
|
||||
this.options.onReorderRowsDrag(this.data[this.draggingIndex])
|
||||
}
|
||||
|
||||
onDrop (table) {
|
||||
this.$draggingTd.css('cursor', '')
|
||||
const newData = []
|
||||
for (let i = 0; i < table.tBodies[0].rows.length; i++) {
|
||||
const $tr = $(table.tBodies[0].rows[i])
|
||||
newData.push(this.data[$tr.data('index')])
|
||||
$tr.data('index', i)
|
||||
}
|
||||
|
||||
const draggingRow = this.data[this.draggingIndex]
|
||||
const droppedIndex = newData.indexOf(this.data[this.draggingIndex])
|
||||
const droppedRow = this.data[droppedIndex]
|
||||
const index = this.options.data.indexOf(this.data[droppedIndex])
|
||||
this.options.data.splice(this.options.data.indexOf(draggingRow), 1)
|
||||
this.options.data.splice(index, 0, draggingRow)
|
||||
|
||||
// Call the user defined function
|
||||
this.options.onReorderRowsDrop(droppedRow)
|
||||
|
||||
// Call the event reorder-row
|
||||
this.trigger('reorder-row', newData)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user