47 Commits
Author SHA1 Message Date
新亮 ec3c9f0ccf upgrade 2021-05-09 21:43:23 +08:00
新亮笔记andGitHub 8fbcb5d717 upgrade 2021-05-07 20:12:02 +08:00
新亮笔记andGitHub 6f71852686 upgrade 2021-05-07 20:11:01 +08:00
新亮 9cace3337b upgrade 2021-05-05 17:20:48 +08:00
新亮 0dbd4b0935 upgrade 2021-04-24 10:12:03 +08:00
新亮 1f85d8df61 upgrade 2021-04-18 19:54:31 +08:00
新亮 3e1ef9c658 upgrade 2021-04-10 16:02:34 +08:00
新亮 8a6a34348f # 22 add web - admin/login 2021-04-10 15:05:50 +08:00
新亮 66a5e29c9c #21 add tool - view log 2021-04-04 21:14:15 +08:00
新亮 84cc0c9cbc #20 add web-authorized 2021-03-28 15:52:02 +08:00
新亮 3e918fce59 #18 add web 2021-03-20 22:05:59 +08:00
新亮 36ef904211 #17 add init.sh 2021-03-13 21:44:56 +08:00
新亮 49ccaf9bec #16 optimize handlergen.sh 2021-03-13 21:23:22 +08:00
新亮 4381a9a6f6 #15 optimize gormgen.sh 2021-03-13 14:14:31 +08:00
新亮 5e5db8917d #14 optimize third_party_request 2021-03-13 13:53:52 +08:00
新亮 f7b3dbae5e #13 Where{FieldName}In Where{FieldName}NotIn 2021-03-06 14:12:13 +08:00
新亮 5d7975056c #12 resubmit middlerware 2021-03-06 13:28:38 +08:00
新亮 499e171d63 #11 code generator 2021-02-28 14:14:56 +08:00
新亮 8d6cd2174e #11 code generator 2021-02-28 10:05:29 +08:00
新亮 3823eee2ca #11 code generator 2021-02-27 09:29:34 +08:00
新亮 0b6b9dd69f upgrade 2021-02-20 20:43:39 +08:00
新亮 7327275b7a upgrade 2021-02-20 10:02:53 +08:00
新亮 9b5028e9de upgrade 2021-02-20 09:59:36 +08:00
新亮 cc8012b09d upgrade 2021-02-19 22:00:01 +08:00
新亮 334bfbb4aa upgrade 2021-02-09 09:52:50 +08:00
新亮 a4701309cb upgrade 2021-02-03 20:43:02 +08:00
新亮 e5ea279f2e upgrade 2021-02-03 09:51:48 +08:00
新亮 ad8639fea8 upgrade 2021-02-02 20:53:30 +08:00
新亮 b346216f79 upgrade 2021-02-02 20:42:51 +08:00
新亮 e1314cf139 upgrade 2021-01-31 15:37:30 +08:00
新亮 a1a9208a27 upgrade 2021-01-31 15:19:36 +08:00
新亮 4ed8b81c57 upgrade 2021-01-31 14:51:41 +08:00
新亮 106d5b65d7 upgrade 2021-01-23 16:46:01 +08:00
新亮 d17c58ffe1 upgrade 2021-01-17 18:37:43 +08:00
新亮 845ef70a77 upgrade 2021-01-16 22:18:55 +08:00
新亮 f32a8a1fb5 upgrade 2021-01-16 18:08:00 +08:00
新亮 2466dfcbfc upgrade 2021-01-16 14:45:42 +08:00
新亮 f96d33fdef upgrade 2021-01-11 13:13:07 +08:00
新亮 f2cd04993f upgrade 2021-01-09 20:10:13 +08:00
新亮 f4072dc5fb upgrade 2021-01-01 16:12:01 +08:00
新亮 458ebe10c6 upgrade 2021-01-01 10:51:55 +08:00
新亮 aa5dec6eeb upgrade 2020-12-27 20:40:43 +08:00
新亮 ef8989fe33 upgrade 2020-12-27 17:19:53 +08:00
新亮 34f85ad14b upgrade 2020-12-26 13:52:34 +08:00
新亮 155f5fca1b upgrade 2020-12-25 21:26:56 +08:00
新亮笔记andGitHub f8cfc2f16c Merge pull request #7 from xinliangnote/add-license-1
Create LICENSE
2020-12-25 21:11:46 +08:00
新亮笔记andGitHub a017831aba Create LICENSE 2020-12-25 21:10:47 +08:00
1384 changed files with 51576 additions and 436433 deletions
+5 -1
View File
@@ -1 +1,5 @@
.idea
/.vscode
.vscode
/.idea
.idea
mfmt
@@ -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
+32 -145
View File
@@ -1,159 +1,46 @@
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B%E6%96%87%E6%A1%A3%5D/images/go-gin-api-logo.png)
## 关于
## 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. 支持 [jwt](https://github.com/dgrijalva/jwt-go) 接口鉴权
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. 支持 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 文档由以下几个主要部分组成:
## Quick start
- 准备工作
- 快速开始
- 目录接口
- 核心封装
- 组件指南
- 工具包
#### Requirements
**详细文档:[https://www.yuque.com/xinliangnote/go-gin-api/ngc3x5](https://www.yuque.com/xinliangnote/go-gin-api/ngc3x5)**
- Go version >= 1.12
- Global environment configure (Linux/Mac)
## 其他
```
export GO111MODULE=on
export GOPROXY=https://goproxy.io
```
#### Build & Run
```
cd go-gin-api
go run main.go
输出如下,表示 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)
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/jaeger_demo_2.png)
![](https://github.com/xinliangnote/Go/blob/master/03-go-gin-api%20%5B文档%5D/images/jaeger_demo_3.png)
#### 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)
查看 Jaeger 链路追踪代码,请查看 [v1.0版](https://github.com/xinliangnote/go-gin-api/releases/tag/v1.0),文档点这里 [jaeger.md](https://github.com/xinliangnote/go-gin-api/blob/master/docs/jaeger.md) 。
## Learning together
-52
View File
@@ -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
)
-57
View File
@@ -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)
}
-5
View File
@@ -1,5 +0,0 @@
package param_bind
type ProductAdd struct {
Name string `form:"name" json:"name" validate:"required,NameValid"`
}
-13
View File
@@ -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
}
-54
View File
@@ -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(&param_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)
}
-58
View File
@@ -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&param_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&param_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&param_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)
}
-207
View File
@@ -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",
}
-207
View File
@@ -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",
}
-207
View File
@@ -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",
}
-207
View File
@@ -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()
}
}
-946
View File
@@ -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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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(&quot;&quot;); 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>`
-39
View File
@@ -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()
}
}
-25
View File
@@ -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
}
}
-103
View File
@@ -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()
}
}
-122
View File
@@ -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
}
-110
View File
@@ -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
}
-122
View File
@@ -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
}
-62
View File
@@ -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)
}
}
-14
View File
@@ -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
}
-97
View File
@@ -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")
}
}
}
-75
View File
@@ -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
}
-61
View File
@@ -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
}
-97
View File
@@ -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
}
}
-62
View File
@@ -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
}
-22
View File
@@ -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
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4303
View File
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.

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
+1
View File
@@ -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;
}
+2
View File
@@ -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
File diff suppressed because one or more lines are too long
@@ -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:", "}});
+361
View File
@@ -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;
}
}
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
File diff suppressed because one or more lines are too long
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,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}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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,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}
File diff suppressed because one or more lines are too long
@@ -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())
}
}
@@ -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;
}
}
@@ -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)
}
}
@@ -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"
}
}
@@ -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"
}
}
@@ -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"
}
}
@@ -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"
}
}
@@ -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">&times;</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">&times;</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"
}
}
@@ -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())
})
}
}
}
}
@@ -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;
}
@@ -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)
@@ -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": ""
}
}
@@ -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()
}
}
@@ -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"
}
}
@@ -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)
}
}
@@ -0,0 +1,14 @@
.reorder_rows_onDragClass td {
background-color: #eee;
-webkit-box-shadow: 11px 5px 12px 2px #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
-webkit-box-shadow: 6px 3px 5px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
-moz-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
}
.reorder_rows_onDragClass td:last-child {
-webkit-box-shadow: 8px 7px 12px 0 #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
-webkit-box-shadow: 1px 8px 6px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
-moz-box-shadow: 0 9px 4px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, -1px 0 0 #ccc inset;
-box-shadow: 0 9px 4px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, -1px 0 0 #ccc inset;
}
@@ -0,0 +1,17 @@
{
"name": "Reorder Rows",
"version": "1.0.0",
"description": "Plugin to support the reordering rows feature.",
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-rows",
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/reorder-rows.html",
"plugins": [{
"name": "bootstrap-table-reorder-rows",
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-rows"
}],
"author": {
"name": "djhvscf",
"image": "https://avatars1.githubusercontent.com/u/4496763"
}
}
@@ -0,0 +1,68 @@
/**
* @author: Dennis Hernández
* @webSite: http://djhvscf.github.io/Blog
* @version: v2.0.0
*/
const isInit = that => that.$el.data('resizableColumns') !== undefined
const initResizable = that => {
if (that.options.resizable && !that.options.cardView && !isInit(that)) {
that.$el.resizableColumns({
store: window.store
})
}
}
const destroy = that => {
if (isInit(that)) {
that.$el.data('resizableColumns').destroy()
}
}
const reInitResizable = that => {
destroy(that)
initResizable(that)
}
$.extend($.fn.bootstrapTable.defaults, {
resizable: false
})
const BootstrapTable = $.fn.bootstrapTable.Constructor
const _initBody = BootstrapTable.prototype.initBody
const _toggleView = BootstrapTable.prototype.toggleView
const _resetView = BootstrapTable.prototype.resetView
BootstrapTable.prototype.initBody = function (...args) {
const that = this
_initBody.apply(this, Array.prototype.slice.apply(args))
that.$el
.off('column-switch.bs.table page-change.bs.table')
.on('column-switch.bs.table page-change.bs.table', () => {
reInitResizable(that)
})
}
BootstrapTable.prototype.toggleView = function (...args) {
_toggleView.apply(this, Array.prototype.slice.apply(args))
if (this.options.resizable && this.options.cardView) {
// Destroy the plugin
destroy(this)
}
}
BootstrapTable.prototype.resetView = function (...args) {
const that = this
_resetView.apply(this, Array.prototype.slice.apply(args))
if (this.options.resizable) {
// because in fitHeader function, we use setTimeout(func, 100);
setTimeout(() => {
initResizable(that)
}, 100)
}
}
@@ -0,0 +1,17 @@
{
"name": "Resizable",
"version": "1.1.0",
"description": "Plugin to support the resizable feature.",
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/resizable",
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/resizable.html",
"plugins": [{
"name": "bootstrap-table-resizable",
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/resizable"
}],
"author": {
"name": "djhvscf",
"image": "https://avatars1.githubusercontent.com/u/4496763"
}
}

Some files were not shown because too many files have changed in this diff Show More