feat: 添加云端函数HTTP请求、SMTP邮件、数据库操作能力
- 添加HTTP客户端工具支持GET/POST请求 - 添加SMTP邮件发送功能 - 添加数据库操作(db.getRecords, db.deleteRecord, db.deleteRecords) - 添加订单管理云端函数(读取、接收、筛选、成功、失败) - 添加测试推送和邮件通知云端函数 - 修复云端函数编辑页面预览代码换行显示 - 云端变量数据模型重构(合并数据类型)
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
- **用户系统** - 用户注册、登录、设备绑定、试用功能
|
- **用户系统** - 用户注册、登录、设备绑定、试用功能
|
||||||
- **卡密系统** - 生成、销售、管理卡密,支持多种卡类型
|
- **卡密系统** - 生成、销售、管理卡密,支持多种卡类型
|
||||||
- **代理系统** - 多级代理分销,佣金结算
|
- **代理系统** - 多级代理分销,佣金结算
|
||||||
- **云端变量** - 单值模式和流水模式,支持应用端写入
|
- **云端变量** - 单值模式和记录模式,支持应用端写入
|
||||||
- **版本管理** - ZIP包上传,文件索引,自动更新
|
- **版本管理** - ZIP包上传,文件索引,自动更新
|
||||||
- **扩展API** - Webhook推送,API Key认证
|
- **扩展API** - Webhook推送,API Key认证
|
||||||
- **邮件系统** - 邮箱验证、密码重置、通知邮件
|
- **邮件系统** - 邮箱验证、密码重置、通知邮件
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed embedded/dist
|
//go:embed embedded/dist/*
|
||||||
var embeddedFiles embed.FS
|
var embeddedFiles embed.FS
|
||||||
|
|
||||||
// @title 网络验证平台API
|
// @title 网络验证平台API
|
||||||
|
|||||||
@@ -457,9 +457,8 @@ type CloudVariable struct {
|
|||||||
AppID *uint `json:"app_id"`
|
AppID *uint `json:"app_id"`
|
||||||
Key string `gorm:"size:100;index" json:"key"`
|
Key string `gorm:"size:100;index" json:"key"`
|
||||||
DefaultValue string `gorm:"type:text" json:"default_value"`
|
DefaultValue string `gorm:"type:text" json:"default_value"`
|
||||||
VarType string `gorm:"size:20;default:string" json:"var_type"` // string, integer, decimal, binary
|
VarType string `gorm:"size:20;default:string" json:"var_type"` // string, integer, decimal, binary, stream
|
||||||
DataType string `gorm:"size:20;default:single" json:"data_type"` // single, stream
|
MaxRecords int `gorm:"default:0" json:"max_records"`
|
||||||
MaxRecords int `gorm:"default:0" json:"max_records"` // 流水类型最大记录数,0=不限制
|
|
||||||
FilePath string `gorm:"size:500" json:"file_path"`
|
FilePath string `gorm:"size:500" json:"file_path"`
|
||||||
FileSize int64 `json:"file_size"`
|
FileSize int64 `json:"file_size"`
|
||||||
MimeType string `gorm:"size:100" json:"mime_type"`
|
MimeType string `gorm:"size:100" json:"mime_type"`
|
||||||
|
|||||||
@@ -730,8 +730,8 @@ func handleAppCreateVariableRecord(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if variable.DataType != "stream" {
|
if variable.VarType != "stream" {
|
||||||
response.Error(c, 400, "该变量不是流水类型")
|
response.Error(c, 400, "该变量不是记录类型")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,8 +817,8 @@ func handleAppGetVariableRecords(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if variable.DataType != "stream" {
|
if variable.VarType != "stream" {
|
||||||
response.Error(c, 400, "该变量不是流水类型")
|
response.Error(c, 400, "该变量不是记录类型")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -894,8 +894,8 @@ func handleAppDeleteVariableRecord(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if variable.DataType != "stream" {
|
if variable.VarType != "stream" {
|
||||||
response.Error(c, 400, "该变量不是流水类型")
|
response.Error(c, 400, "该变量不是记录类型")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -8,6 +10,8 @@ import (
|
|||||||
"verification-platform-backend/internal/database"
|
"verification-platform-backend/internal/database"
|
||||||
"verification-platform-backend/internal/model"
|
"verification-platform-backend/internal/model"
|
||||||
"verification-platform-backend/internal/service"
|
"verification-platform-backend/internal/service"
|
||||||
|
"verification-platform-backend/internal/utils/httputil"
|
||||||
|
"verification-platform-backend/internal/utils/smtputil"
|
||||||
"verification-platform-backend/pkg/jwt"
|
"verification-platform-backend/pkg/jwt"
|
||||||
"verification-platform-backend/pkg/response"
|
"verification-platform-backend/pkg/response"
|
||||||
|
|
||||||
@@ -244,6 +248,68 @@ func handleExecuteDynamicCode(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpClient := httputil.NewHTTPClient(10 * time.Second)
|
||||||
|
httpObj := map[string]interface{}{
|
||||||
|
"get": func(url string, headers map[string]interface{}) *httputil.HTTPResponse {
|
||||||
|
convertedHeaders := make(map[string]string)
|
||||||
|
for k, v := range headers {
|
||||||
|
convertedHeaders[k] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return httpClient.Get(url, convertedHeaders)
|
||||||
|
},
|
||||||
|
"post": func(url string, headers map[string]interface{}, body interface{}) *httputil.HTTPResponse {
|
||||||
|
convertedHeaders := make(map[string]string)
|
||||||
|
for k, v := range headers {
|
||||||
|
convertedHeaders[k] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return httpClient.Post(url, convertedHeaders, body)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := vm.Set("http", httpObj); err != nil {
|
||||||
|
response.Error(c, 500, "HTTP对象设置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dbObj := map[string]interface{}{
|
||||||
|
"getRecords": func(variableKey string, page, pageSize int) map[string]interface{} {
|
||||||
|
return getRecords(app.ID, req.UserID, variableKey, page, pageSize)
|
||||||
|
},
|
||||||
|
"deleteRecord": func(variableKey string, recordID uint) map[string]interface{} {
|
||||||
|
return deleteRecord(app.ID, req.UserID, variableKey, recordID)
|
||||||
|
},
|
||||||
|
"deleteRecords": func(variableKey string, recordIDs []uint) map[string]interface{} {
|
||||||
|
return deleteRecords(app.ID, req.UserID, variableKey, recordIDs)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := vm.Set("db", dbObj); err != nil {
|
||||||
|
response.Error(c, 500, "数据库对象设置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
smtpObj := map[string]interface{}{
|
||||||
|
"send": func(config map[string]interface{}, to []string, subject, body string, html bool) *smtputil.SMTPResult {
|
||||||
|
smtpConfig := smtputil.SMTPConfig{
|
||||||
|
Host: getString(config, "host"),
|
||||||
|
Port: getInt(config, "port", 587),
|
||||||
|
Username: getString(config, "username"),
|
||||||
|
Password: getString(config, "password"),
|
||||||
|
From: getString(config, "from"),
|
||||||
|
UseTLS: getBool(config, "use_tls", true),
|
||||||
|
}
|
||||||
|
client := smtputil.NewSMTPClient(smtpConfig, 30*time.Second)
|
||||||
|
return client.Send(smtputil.EmailMessage{
|
||||||
|
To: to,
|
||||||
|
Subject: subject,
|
||||||
|
Body: body,
|
||||||
|
HTML: html,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := vm.Set("smtp", smtpObj); err != nil {
|
||||||
|
response.Error(c, 500, "SMTP对象设置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
value, err := vm.RunString("(function() { " + dynamicCode.Code + " })()")
|
value, err := vm.RunString("(function() { " + dynamicCode.Code + " })()")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 400, "代码执行错误: "+err.Error())
|
response.Error(c, 400, "代码执行错误: "+err.Error())
|
||||||
@@ -278,6 +344,11 @@ func handleExecuteDynamicCode(c *gin.Context) {
|
|||||||
response.Error(c, 500, "更新应用变量失败: "+err.Error())
|
response.Error(c, 500, "更新应用变量失败: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "add_record":
|
||||||
|
if err := handleAddRecord(app.ID, req.UserID, actionMap); err != nil {
|
||||||
|
response.Error(c, 500, "添加记录失败: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,3 +496,213 @@ func toString(v interface{}) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleAddRecord(appID uint, userID *uint, actionMap map[string]interface{}) error {
|
||||||
|
variableKey, ok := actionMap["variable_key"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("缺少variable_key参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
recordData, ok := actionMap["record_data"]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("缺少record_data参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
var variable model.CloudVariable
|
||||||
|
if err := database.DB.Where("app_id = ? AND key = ?", appID, variableKey).First(&variable).Error; err != nil {
|
||||||
|
return fmt.Errorf("云端变量不存在: %s", variableKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if variable.VarType != "stream" {
|
||||||
|
return fmt.Errorf("该变量不是记录类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
dataBytes, err := json.Marshal(recordData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("序列化记录数据失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
record := model.CloudVariableRecord{
|
||||||
|
CloudVariableID: variable.ID,
|
||||||
|
Data: string(dataBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
if variable.Scope == "user" && userID != nil {
|
||||||
|
appUserID := *userID
|
||||||
|
record.AppUserID = &appUserID
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.DB.Create(&record).Error; err != nil {
|
||||||
|
return fmt.Errorf("创建记录失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if variable.MaxRecords > 0 {
|
||||||
|
var total int64
|
||||||
|
database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID).Count(&total)
|
||||||
|
if int(total) > variable.MaxRecords {
|
||||||
|
deleteCount := int(total) - variable.MaxRecords
|
||||||
|
var oldRecords []model.CloudVariableRecord
|
||||||
|
database.DB.Where("cloud_variable_id = ?", variable.ID).
|
||||||
|
Order("created_at ASC").
|
||||||
|
Limit(deleteCount).
|
||||||
|
Find(&oldRecords)
|
||||||
|
for _, oldRecord := range oldRecords {
|
||||||
|
database.DB.Delete(&oldRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRecords(appID uint, userID *uint, variableKey string, page, pageSize int) map[string]interface{} {
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"records": []interface{}{},
|
||||||
|
"total": 0,
|
||||||
|
"page": page,
|
||||||
|
"pageSize": pageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
var variable model.CloudVariable
|
||||||
|
if err := database.DB.Where("app_id = ? AND key = ?", appID, variableKey).First(&variable).Error; err != nil {
|
||||||
|
result["error"] = "云端变量不存在"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if variable.VarType != "stream" {
|
||||||
|
result["error"] = "该变量不是记录类型"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 || pageSize > 100 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
query := database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID)
|
||||||
|
if variable.Scope == "user" && userID != nil {
|
||||||
|
query = query.Where("app_user_id = ?", *userID)
|
||||||
|
}
|
||||||
|
query.Count(&total)
|
||||||
|
|
||||||
|
var records []model.CloudVariableRecord
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
|
||||||
|
result["error"] = "查询记录失败"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordList []interface{}
|
||||||
|
for _, record := range records {
|
||||||
|
var data interface{}
|
||||||
|
if err := json.Unmarshal([]byte(record.Data), &data); err == nil {
|
||||||
|
recordList = append(recordList, map[string]interface{}{
|
||||||
|
"id": record.ID,
|
||||||
|
"data": data,
|
||||||
|
"created_at": record.CreatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result["success"] = true
|
||||||
|
result["records"] = recordList
|
||||||
|
result["total"] = total
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteRecord(appID uint, userID *uint, variableKey string, recordID uint) map[string]interface{} {
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
var variable model.CloudVariable
|
||||||
|
if err := database.DB.Where("app_id = ? AND key = ?", appID, variableKey).First(&variable).Error; err != nil {
|
||||||
|
result["error"] = "云端变量不存在"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if variable.VarType != "stream" {
|
||||||
|
result["error"] = "该变量不是记录类型"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
query := database.DB.Where("id = ? AND cloud_variable_id = ?", recordID, variable.ID)
|
||||||
|
if variable.Scope == "user" && userID != nil {
|
||||||
|
query = query.Where("app_user_id = ?", *userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Delete(&model.CloudVariableRecord{}).Error; err != nil {
|
||||||
|
result["error"] = "删除记录失败"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
result["success"] = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteRecords(appID uint, userID *uint, variableKey string, recordIDs []uint) map[string]interface{} {
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"deleted": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
var variable model.CloudVariable
|
||||||
|
if err := database.DB.Where("app_id = ? AND key = ?", appID, variableKey).First(&variable).Error; err != nil {
|
||||||
|
result["error"] = "云端变量不存在"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if variable.VarType != "stream" {
|
||||||
|
result["error"] = "该变量不是记录类型"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
query := database.DB.Where("id IN ? AND cloud_variable_id = ?", recordIDs, variable.ID)
|
||||||
|
if variable.Scope == "user" && userID != nil {
|
||||||
|
query = query.Where("app_user_id = ?", *userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteResult := query.Delete(&model.CloudVariableRecord{})
|
||||||
|
if deleteResult.Error != nil {
|
||||||
|
result["error"] = "删除记录失败"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
result["success"] = true
|
||||||
|
result["deleted"] = deleteResult.RowsAffected
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func getString(m map[string]interface{}, key string) string {
|
||||||
|
if v, ok := m[key]; ok {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInt(m map[string]interface{}, key string, defaultValue int) int {
|
||||||
|
if v, ok := m[key]; ok {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case int:
|
||||||
|
return val
|
||||||
|
case float64:
|
||||||
|
return int(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBool(m map[string]interface{}, key string, defaultValue bool) bool {
|
||||||
|
if v, ok := m[key]; ok {
|
||||||
|
if b, ok := v.(bool); ok {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|||||||
@@ -398,7 +398,6 @@ func handleCreateCloudVariable(c *gin.Context) {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
DefaultValue string `json:"default_value"`
|
DefaultValue string `json:"default_value"`
|
||||||
VarType string `json:"var_type"`
|
VarType string `json:"var_type"`
|
||||||
DataType string `json:"data_type"`
|
|
||||||
MaxRecords int `json:"max_records"`
|
MaxRecords int `json:"max_records"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
WritePermission string `json:"write_permission"`
|
WritePermission string `json:"write_permission"`
|
||||||
@@ -431,17 +430,12 @@ func handleCreateCloudVariable(c *gin.Context) {
|
|||||||
req.VarType = "string"
|
req.VarType = "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.DataType != "single" && req.DataType != "stream" {
|
|
||||||
req.DataType = "single"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable := model.CloudVariable{
|
variable := model.CloudVariable{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
AppID: &req.AppID,
|
AppID: &req.AppID,
|
||||||
Key: req.Key,
|
Key: req.Key,
|
||||||
DefaultValue: req.DefaultValue,
|
DefaultValue: req.DefaultValue,
|
||||||
VarType: req.VarType,
|
VarType: req.VarType,
|
||||||
DataType: req.DataType,
|
|
||||||
MaxRecords: req.MaxRecords,
|
MaxRecords: req.MaxRecords,
|
||||||
Scope: req.Scope,
|
Scope: req.Scope,
|
||||||
WritePermission: req.WritePermission,
|
WritePermission: req.WritePermission,
|
||||||
@@ -464,7 +458,6 @@ func handleUpdateCloudVariable(c *gin.Context) {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
DefaultValue string `json:"default_value"`
|
DefaultValue string `json:"default_value"`
|
||||||
VarType string `json:"var_type"`
|
VarType string `json:"var_type"`
|
||||||
DataType string `json:"data_type"`
|
|
||||||
MaxRecords int `json:"max_records"`
|
MaxRecords int `json:"max_records"`
|
||||||
WritePermission string `json:"write_permission"`
|
WritePermission string `json:"write_permission"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
@@ -485,9 +478,6 @@ func handleUpdateCloudVariable(c *gin.Context) {
|
|||||||
variable.Key = req.Key
|
variable.Key = req.Key
|
||||||
variable.DefaultValue = req.DefaultValue
|
variable.DefaultValue = req.DefaultValue
|
||||||
variable.VarType = req.VarType
|
variable.VarType = req.VarType
|
||||||
if req.DataType == "single" || req.DataType == "stream" {
|
|
||||||
variable.DataType = req.DataType
|
|
||||||
}
|
|
||||||
variable.MaxRecords = req.MaxRecords
|
variable.MaxRecords = req.MaxRecords
|
||||||
if req.WritePermission == "admin" || req.WritePermission == "user" || req.WritePermission == "app_user" {
|
if req.WritePermission == "admin" || req.WritePermission == "user" || req.WritePermission == "app_user" {
|
||||||
variable.WritePermission = req.WritePermission
|
variable.WritePermission = req.WritePermission
|
||||||
@@ -715,8 +705,8 @@ func handleGetCloudVariableRecords(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if variable.DataType != "stream" {
|
if variable.VarType != "stream" {
|
||||||
response.Error(c, 400, "该变量不是流水类型")
|
response.Error(c, 400, "该变量不是记录类型")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,8 +784,8 @@ func handleDeleteCloudVariableRecords(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if variable.DataType != "stream" {
|
if variable.VarType != "stream" {
|
||||||
response.Error(c, 400, "该变量不是流水类型")
|
response.Error(c, 400, "该变量不是记录类型")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package httputil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
type HTTPResponse struct {
|
||||||
|
StatusCode int `json:"statusCode"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
JSON map[string]interface{} `json:"json,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPClient struct {
|
||||||
|
client *http.Client
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPClient(timeout time.Duration) *HTTPClient {
|
||||||
|
if timeout <= 0 {
|
||||||
|
timeout = DefaultTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HTTPClient{
|
||||||
|
client: &http.Client{
|
||||||
|
Timeout: timeout,
|
||||||
|
},
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) Get(urlStr string, headers map[string]string) *HTTPResponse {
|
||||||
|
return c.doRequest("GET", urlStr, headers, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) Post(urlStr string, headers map[string]string, body interface{}) *HTTPResponse {
|
||||||
|
return c.doRequest("POST", urlStr, headers, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) doRequest(method, urlStr string, headers map[string]string, body interface{}) *HTTPResponse {
|
||||||
|
resp := &HTTPResponse{
|
||||||
|
Headers: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateURL(urlStr); err != nil {
|
||||||
|
resp.Error = err.Error()
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqBody []byte
|
||||||
|
var err error
|
||||||
|
contentType := "application/json"
|
||||||
|
|
||||||
|
if body != nil {
|
||||||
|
switch v := body.(type) {
|
||||||
|
case string:
|
||||||
|
reqBody = []byte(v)
|
||||||
|
contentType = "text/plain"
|
||||||
|
case map[string]interface{}:
|
||||||
|
reqBody, err = json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = fmt.Sprintf("failed to marshal body: %v", err)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
reqBody, err = json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = fmt.Sprintf("failed to marshal body: %v", err)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var req *http.Request
|
||||||
|
if reqBody != nil {
|
||||||
|
req, err = http.NewRequest(method, urlStr, bytes.NewReader(reqBody))
|
||||||
|
} else {
|
||||||
|
req, err = http.NewRequest(method, urlStr, nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = fmt.Sprintf("failed to create request: %v", err)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
if headers != nil {
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if body != nil && req.Header.Get("Content-Type") == "" {
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpResp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = fmt.Sprintf("request failed: %v", err)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
defer httpResp.Body.Close()
|
||||||
|
|
||||||
|
resp.StatusCode = httpResp.StatusCode
|
||||||
|
resp.Status = httpResp.Status
|
||||||
|
|
||||||
|
for k, v := range httpResp.Header {
|
||||||
|
if len(v) > 0 {
|
||||||
|
resp.Headers[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if _, err := buf.ReadFrom(httpResp.Body); err != nil {
|
||||||
|
resp.Error = fmt.Sprintf("failed to read response body: %v", err)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
resp.Body = buf.String()
|
||||||
|
|
||||||
|
if strings.Contains(httpResp.Header.Get("Content-Type"), "application/json") {
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &jsonData); err == nil {
|
||||||
|
resp.JSON = jsonData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateURL(urlStr string) error {
|
||||||
|
parsedURL, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid URL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
|
||||||
|
return fmt.Errorf("only http and https protocols are allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
host := parsedURL.Hostname()
|
||||||
|
if host == "" {
|
||||||
|
return fmt.Errorf("empty host")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPrivateIP(host) {
|
||||||
|
return fmt.Errorf("access to private IP addresses is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isLocalhost(host) {
|
||||||
|
return fmt.Errorf("access to localhost is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLocalhost(host string) bool {
|
||||||
|
lowerHost := strings.ToLower(host)
|
||||||
|
return lowerHost == "localhost" || lowerHost == "127.0.0.1" || lowerHost == "::1"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPrivateIP(host string) bool {
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
privateBlocks := []string{
|
||||||
|
"10.0.0.0/8",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"169.254.0.0/16",
|
||||||
|
"127.0.0.0/8",
|
||||||
|
"::1/128",
|
||||||
|
"fc00::/7",
|
||||||
|
"fe80::/10",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, block := range privateBlocks {
|
||||||
|
_, cidr, _ := net.ParseCIDR(block)
|
||||||
|
if cidr != nil && cidr.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package smtputil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SMTPConfig struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
From string
|
||||||
|
UseTLS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailMessage struct {
|
||||||
|
To []string
|
||||||
|
Subject string
|
||||||
|
Body string
|
||||||
|
HTML bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type SMTPResult struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SMTPClient struct {
|
||||||
|
config SMTPConfig
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSMTPClient(config SMTPConfig, timeout time.Duration) *SMTPClient {
|
||||||
|
if timeout <= 0 {
|
||||||
|
timeout = 30 * time.Second
|
||||||
|
}
|
||||||
|
return &SMTPClient{
|
||||||
|
config: config,
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SMTPClient) Send(msg EmailMessage) *SMTPResult {
|
||||||
|
result := &SMTPResult{Success: false}
|
||||||
|
|
||||||
|
if len(msg.To) == 0 {
|
||||||
|
result.Error = "收件人不能为空"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.Host == "" {
|
||||||
|
result.Error = "SMTP服务器地址不能为空"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("%s:%d", c.config.Host, c.config.Port)
|
||||||
|
|
||||||
|
var auth smtp.Auth
|
||||||
|
if c.config.Username != "" && c.config.Password != "" {
|
||||||
|
auth = smtp.PlainAuth("", c.config.Username, c.config.Password, c.config.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
from := c.config.From
|
||||||
|
if from == "" {
|
||||||
|
from = c.config.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := "text/plain"
|
||||||
|
if msg.HTML {
|
||||||
|
contentType = "text/html"
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := make(map[string]string)
|
||||||
|
headers["From"] = from
|
||||||
|
headers["To"] = strings.Join(msg.To, ", ")
|
||||||
|
headers["Subject"] = msg.Subject
|
||||||
|
headers["MIME-Version"] = "1.0"
|
||||||
|
headers["Content-Type"] = fmt.Sprintf("%s; charset=UTF-8", contentType)
|
||||||
|
headers["Date"] = time.Now().Format(time.RFC1123Z)
|
||||||
|
|
||||||
|
var message strings.Builder
|
||||||
|
for k, v := range headers {
|
||||||
|
message.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
|
||||||
|
}
|
||||||
|
message.WriteString("\r\n")
|
||||||
|
message.WriteString(msg.Body)
|
||||||
|
|
||||||
|
if c.config.UseTLS {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
ServerName: c.config.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := tls.Dial("tcp", addr, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
result.Error = fmt.Sprintf("TLS连接失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client, err := smtp.NewClient(conn, c.config.Host)
|
||||||
|
if err != nil {
|
||||||
|
result.Error = fmt.Sprintf("创建SMTP客户端失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if auth != nil {
|
||||||
|
if err := client.Auth(auth); err != nil {
|
||||||
|
result.Error = fmt.Sprintf("SMTP认证失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Mail(from); err != nil {
|
||||||
|
result.Error = fmt.Sprintf("设置发件人失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, to := range msg.To {
|
||||||
|
if err := client.Rcpt(to); err != nil {
|
||||||
|
result.Error = fmt.Sprintf("设置收件人失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
result.Error = fmt.Sprintf("准备邮件数据失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write([]byte(message.String()))
|
||||||
|
if err != nil {
|
||||||
|
result.Error = fmt.Sprintf("写入邮件内容失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
result.Error = fmt.Sprintf("关闭邮件写入失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Quit(); err != nil {
|
||||||
|
result.Error = fmt.Sprintf("关闭SMTP连接失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := smtp.SendMail(addr, auth, from, msg.To, []byte(message.String()))
|
||||||
|
if err != nil {
|
||||||
|
result.Error = fmt.Sprintf("发送邮件失败: %v", err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Success = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"verification-platform-backend/internal/database"
|
||||||
|
"verification-platform-backend/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
database.Init()
|
||||||
|
|
||||||
|
var codes []model.DynamicCode
|
||||||
|
if err := database.DB.Limit(2).Find(&codes).Error; err != nil {
|
||||||
|
log.Fatal("查询失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, code := range codes {
|
||||||
|
log.Printf("=== %s (key: %s) ===", code.Name, code.Key)
|
||||||
|
log.Printf("代码长度: %d", len(code.Code))
|
||||||
|
log.Printf("代码前200字符:\n%s", code.Code[:min(200, len(code.Code))])
|
||||||
|
log.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"verification-platform-backend/internal/database"
|
||||||
|
"verification-platform-backend/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
database.Init()
|
||||||
|
|
||||||
|
var code model.DynamicCode
|
||||||
|
if err := database.DB.Where("key = ?", "test_push").First(&code).Error; err != nil {
|
||||||
|
log.Fatal("查询失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("ID: %d\n", code.ID)
|
||||||
|
fmt.Printf("Name: %s\n", code.Name)
|
||||||
|
fmt.Printf("Code length: %d\n", len(code.Code))
|
||||||
|
fmt.Printf("Contains newline: %v\n", strings.Contains(code.Code, "\n"))
|
||||||
|
fmt.Printf("Contains \\n literal: %v\n", strings.Contains(code.Code, "\\n"))
|
||||||
|
fmt.Printf("\n=== Raw Code ===\n")
|
||||||
|
fmt.Printf("%s\n", code.Code)
|
||||||
|
fmt.Printf("\n=== Code bytes (first 100) ===\n")
|
||||||
|
for i, b := range []byte(code.Code) {
|
||||||
|
if i >= 100 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b == '\n' {
|
||||||
|
fmt.Printf("[\\n]")
|
||||||
|
} else if b == '\r' {
|
||||||
|
fmt.Printf("[\\r]")
|
||||||
|
} else if b >= 32 && b < 127 {
|
||||||
|
fmt.Printf("%c", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[%d]", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
@@ -0,0 +1,506 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"verification-platform-backend/internal/database"
|
||||||
|
"verification-platform-backend/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
database.Init()
|
||||||
|
|
||||||
|
var app model.Application
|
||||||
|
if err := database.DB.First(&app).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
log.Fatal("没有找到任何应用,请先创建应用")
|
||||||
|
}
|
||||||
|
log.Fatal("查询应用失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("使用应用: %s (ID: %d)", app.Name, app.ID)
|
||||||
|
|
||||||
|
var existingVar model.CloudVariable
|
||||||
|
err := database.DB.Where("app_id = ? AND key = ?", app.ID, "orders").First(&existingVar).Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
ordersVar := model.CloudVariable{
|
||||||
|
UserID: app.UserID,
|
||||||
|
AppID: &app.ID,
|
||||||
|
Key: "orders",
|
||||||
|
DefaultValue: "",
|
||||||
|
VarType: "stream",
|
||||||
|
MaxRecords: 10000,
|
||||||
|
Scope: "app",
|
||||||
|
WritePermission: "app_user",
|
||||||
|
Description: "订单记录存储",
|
||||||
|
Status: "active",
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&ordersVar).Error; err != nil {
|
||||||
|
log.Fatal("创建订单变量失败:", err)
|
||||||
|
}
|
||||||
|
log.Println("创建订单变量成功: orders")
|
||||||
|
} else {
|
||||||
|
log.Println("订单变量已存在: orders")
|
||||||
|
}
|
||||||
|
|
||||||
|
functions := []struct {
|
||||||
|
Name string
|
||||||
|
Key string
|
||||||
|
Description string
|
||||||
|
Code string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "测试推送",
|
||||||
|
Key: "test_push",
|
||||||
|
Description: "测试Telegram推送是否正常",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
|
||||||
|
if (!telegramToken || !chatId) {
|
||||||
|
return { error: "缺少telegram_token或chat_id参数" };
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "内容: 信息接收测试, 接收到本消息则表明测试正常.";
|
||||||
|
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
var result = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
return { success: false, error: result.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, data: result.json };`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "读取订单",
|
||||||
|
Key: "read_order",
|
||||||
|
Description: "读取并存储订单记录,推送到Telegram",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
var order = params.order || {};
|
||||||
|
|
||||||
|
if (!order.TID) {
|
||||||
|
return { error: "缺少订单号TID" };
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 读取注单\n";
|
||||||
|
msg += "单号: " + (order.TID || "") + "\n";
|
||||||
|
msg += "时间: " + (order.DATETIME || "") + "\n";
|
||||||
|
msg += "会员: " + (order.NAME0 || "") + "\n";
|
||||||
|
msg += "球种: " + (order.GT || "") + "\n";
|
||||||
|
msg += "赛段: " + (order.SESSION || "") + "\n";
|
||||||
|
msg += "玩法: " + (order.WAGERS_TYPE || "") + "\n";
|
||||||
|
if (order.SRV_IP === "手机") {
|
||||||
|
msg += "手机\n";
|
||||||
|
}
|
||||||
|
msg += "联盟: " + (order.LEAGUE || "") + "\n";
|
||||||
|
msg += "队伍: " + (order.TEAM_H || "") + " : " + (order.TEAM_C || "") + "\n";
|
||||||
|
msg += "比分: " + (order.SCORE || "") + "\n";
|
||||||
|
msg += "盘口: " + (order.ORDER_CON || "") + "\n";
|
||||||
|
msg += "下注: " + (order.ORDER_TYPE || "") + "\n";
|
||||||
|
msg += "赔率: " + (order.IORATIO || "") + "\n";
|
||||||
|
msg += "金额: " + (order.GOLD || "") + "\n";
|
||||||
|
msg += "开赛时间: " + (order.G_TIME || "");
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "add_record",
|
||||||
|
variable_key: "orders",
|
||||||
|
record_data: order,
|
||||||
|
message: msg,
|
||||||
|
telegram_result: telegramResult
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "接收订单",
|
||||||
|
Key: "accept_order",
|
||||||
|
Description: "接收订单通知",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
var order = params.order || {};
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 接收注单\n";
|
||||||
|
msg += "单号: " + (order.TID || "") + "\n";
|
||||||
|
msg += "时间: " + (order.DATETIME || "") + "\n";
|
||||||
|
msg += "会员: " + (order.NAME0 || "") + "\n";
|
||||||
|
msg += "球种: " + (order.GT || "") + "\n";
|
||||||
|
msg += "赛段: " + (order.SESSION || "") + "\n";
|
||||||
|
msg += "玩法: " + (order.WAGERS_TYPE || "") + "\n";
|
||||||
|
if (order.SRV_IP === "手机") {
|
||||||
|
msg += "手机\n";
|
||||||
|
}
|
||||||
|
msg += "联盟: " + (order.LEAGUE || "") + "\n";
|
||||||
|
msg += "队伍: " + (order.TEAM_H || "") + " : " + (order.TEAM_C || "") + "\n";
|
||||||
|
msg += "比分: " + (order.SCORE || "") + "\n";
|
||||||
|
msg += "盘口: " + (order.ORDER_CON || "") + "\n";
|
||||||
|
msg += "下注: " + (order.ORDER_TYPE || "") + "\n";
|
||||||
|
msg += "赔率: " + (order.IORATIO || "") + "\n";
|
||||||
|
msg += "金额: " + (order.GOLD || "") + "\n";
|
||||||
|
msg += "开赛时间: " + (order.G_TIME || "");
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "add_record",
|
||||||
|
variable_key: "orders",
|
||||||
|
record_data: order,
|
||||||
|
message: msg,
|
||||||
|
telegram_result: telegramResult
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "筛选订单",
|
||||||
|
Key: "screen_order",
|
||||||
|
Description: "筛选订单通知",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
var order = params.order || {};
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 筛选注单\n";
|
||||||
|
msg += "单号: " + (order.TID || "") + "\n";
|
||||||
|
msg += "时间: " + (order.DATETIME || "") + "\n";
|
||||||
|
msg += "会员: " + (order.NAME0 || "") + "\n";
|
||||||
|
msg += "球种: " + (order.GT || "") + "\n";
|
||||||
|
msg += "赛段: " + (order.SESSION || "") + "\n";
|
||||||
|
msg += "玩法: " + (order.WAGERS_TYPE || "") + "\n";
|
||||||
|
if (order.SRV_IP === "手机") {
|
||||||
|
msg += "手机\n";
|
||||||
|
}
|
||||||
|
msg += "联盟: " + (order.LEAGUE || "") + "\n";
|
||||||
|
msg += "队伍: " + (order.TEAM_H || "") + " : " + (order.TEAM_C || "") + "\n";
|
||||||
|
msg += "比分: " + (order.SCORE || "") + "\n";
|
||||||
|
msg += "最新比分: " + (order.SCORE_NEW || "") + "\n";
|
||||||
|
msg += "盘口: " + (order.ORDER_CON || "") + "\n";
|
||||||
|
msg += "最新盘口: " + (order.ORDER_CON_NEW || "") + "\n";
|
||||||
|
msg += "下注: " + (order.ORDER_TYPE || "") + "\n";
|
||||||
|
msg += "赔率: " + (order.IORATIO || "") + "\n";
|
||||||
|
msg += "最新赔率: " + (order.IORATIO_NEW || "") + "\n";
|
||||||
|
msg += "金额: " + (order.GOLD || "") + "\n";
|
||||||
|
msg += "开赛时间: " + (order.G_TIME || "") + "\n";
|
||||||
|
msg += "过滤原因: " + (order.REASON || "");
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "add_record",
|
||||||
|
variable_key: "orders",
|
||||||
|
record_data: order,
|
||||||
|
message: msg,
|
||||||
|
telegram_result: telegramResult
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "跟单成功",
|
||||||
|
Key: "bet_order_success",
|
||||||
|
Description: "跟单成功通知",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
var order = params.order || {};
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 跟单成功\n";
|
||||||
|
msg += "单号: " + (order.TID || "") + "\n";
|
||||||
|
msg += "时间: " + (order.DATETIME || "") + "\n";
|
||||||
|
msg += "会员: " + (order.NAME0 || "") + "\n";
|
||||||
|
msg += "球种: " + (order.GT || "") + "\n";
|
||||||
|
msg += "赛段: " + (order.SESSION || "") + "\n";
|
||||||
|
msg += "玩法: " + (order.WAGERS_TYPE || "") + "\n";
|
||||||
|
if (order.SRV_IP === "手机") {
|
||||||
|
msg += "手机\n";
|
||||||
|
}
|
||||||
|
msg += "联盟: " + (order.LEAGUE || "") + "\n";
|
||||||
|
msg += "队伍: " + (order.TEAM_H || "") + " : " + (order.TEAM_C || "") + "\n";
|
||||||
|
msg += "比分: " + (order.SCORE || "") + "\n";
|
||||||
|
msg += "最新比分: " + (order.SCORE_NEW || "") + "\n";
|
||||||
|
msg += "盘口: " + (order.ORDER_CON || "") + "\n";
|
||||||
|
msg += "最新盘口: " + (order.ORDER_CON_NEW || "") + "\n";
|
||||||
|
msg += "下注: " + (order.ORDER_TYPE || "") + "\n";
|
||||||
|
msg += "赔率: " + (order.IORATIO || "") + "\n";
|
||||||
|
msg += "最新赔率: " + (order.IORATIO_NEW || "") + "\n";
|
||||||
|
msg += "金额: " + (order.GOLD || "") + "\n";
|
||||||
|
msg += "开赛时间: " + (order.G_TIME || "");
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "add_record",
|
||||||
|
variable_key: "orders",
|
||||||
|
record_data: order,
|
||||||
|
message: msg,
|
||||||
|
telegram_result: telegramResult
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "跟单失败",
|
||||||
|
Key: "bet_order_failed",
|
||||||
|
Description: "跟单失败通知",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
var order = params.order || {};
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 跟单失败\n";
|
||||||
|
msg += "单号: " + (order.TID || "") + "\n";
|
||||||
|
msg += "时间: " + (order.DATETIME || "") + "\n";
|
||||||
|
msg += "会员: " + (order.NAME0 || "") + "\n";
|
||||||
|
msg += "球种: " + (order.GT || "") + "\n";
|
||||||
|
msg += "赛段: " + (order.SESSION || "") + "\n";
|
||||||
|
msg += "玩法: " + (order.WAGERS_TYPE || "") + "\n";
|
||||||
|
if (order.SRV_IP === "手机") {
|
||||||
|
msg += "手机\n";
|
||||||
|
}
|
||||||
|
msg += "联盟: " + (order.LEAGUE || "") + "\n";
|
||||||
|
msg += "队伍: " + (order.TEAM_H || "") + " : " + (order.TEAM_C || "") + "\n";
|
||||||
|
msg += "比分: " + (order.SCORE || "") + "\n";
|
||||||
|
msg += "最新比分: " + (order.SCORE_NEW || "") + "\n";
|
||||||
|
msg += "盘口: " + (order.ORDER_CON || "") + "\n";
|
||||||
|
msg += "最新盘口: " + (order.ORDER_CON_NEW || "") + "\n";
|
||||||
|
msg += "下注: " + (order.ORDER_TYPE || "") + "\n";
|
||||||
|
msg += "赔率: " + (order.IORATIO || "") + "\n";
|
||||||
|
msg += "最新赔率: " + (order.IORATIO_NEW || "") + "\n";
|
||||||
|
msg += "金额: " + (order.GOLD || "") + "\n";
|
||||||
|
msg += "开赛时间: " + (order.G_TIME || "") + "\n";
|
||||||
|
msg += "失败原因: " + (order.REASON || "");
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "add_record",
|
||||||
|
variable_key: "orders",
|
||||||
|
record_data: order,
|
||||||
|
message: msg,
|
||||||
|
telegram_result: telegramResult
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "账户掉线",
|
||||||
|
Key: "account_disconnected",
|
||||||
|
Description: "账户掉线通知",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 账户掉线";
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: msg, telegram_result: telegramResult };`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "账户重连",
|
||||||
|
Key: "account_reconnected",
|
||||||
|
Description: "账户重连通知",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 账户重连";
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: msg, telegram_result: telegramResult };`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "测试邮件",
|
||||||
|
Key: "test_email",
|
||||||
|
Description: "测试SMTP邮件发送",
|
||||||
|
Code: `var smtpConfig = params.smtp_config || {};
|
||||||
|
var to = params.to || [];
|
||||||
|
|
||||||
|
if (!smtpConfig.host || !smtpConfig.username || !smtpConfig.password) {
|
||||||
|
return { error: "缺少SMTP配置参数" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.length === 0) {
|
||||||
|
return { error: "缺少收件人地址" };
|
||||||
|
}
|
||||||
|
|
||||||
|
var subject = params.subject || "测试邮件";
|
||||||
|
var body = params.body || "这是一封测试邮件";
|
||||||
|
var html = params.html || false;
|
||||||
|
|
||||||
|
var result = smtp.send(smtpConfig, to, subject, body, html);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return { success: true, message: "邮件发送成功" };
|
||||||
|
} else {
|
||||||
|
return { success: false, error: result.error };
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "订单邮件通知",
|
||||||
|
Key: "order_email_notify",
|
||||||
|
Description: "订单邮件通知,同时发送Telegram和邮件",
|
||||||
|
Code: `var telegramToken = params.telegram_token || "";
|
||||||
|
var chatId = params.chat_id || "";
|
||||||
|
var smtpConfig = params.smtp_config || {};
|
||||||
|
var to = params.to || [];
|
||||||
|
var order = params.order || {};
|
||||||
|
|
||||||
|
if (!order.TID) {
|
||||||
|
return { error: "缺少订单号TID" };
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = "用户: " + (params.username || "未知") + "\n";
|
||||||
|
msg += "程序: " + (params.app || "未知") + "\n";
|
||||||
|
msg += "类型: 订单通知\n";
|
||||||
|
msg += "单号: " + (order.TID || "") + "\n";
|
||||||
|
msg += "时间: " + (order.DATETIME || "") + "\n";
|
||||||
|
msg += "会员: " + (order.NAME0 || "") + "\n";
|
||||||
|
msg += "金额: " + (order.GOLD || "") + "\n";
|
||||||
|
|
||||||
|
var telegramResult = null;
|
||||||
|
if (telegramToken && chatId) {
|
||||||
|
var url = "https://api.telegram.org/bot" + telegramToken + "/sendMessage";
|
||||||
|
telegramResult = http.post(url, {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, {
|
||||||
|
chat_id: chatId,
|
||||||
|
text: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var emailResult = null;
|
||||||
|
if (smtpConfig.host && to.length > 0) {
|
||||||
|
var htmlBody = "<h2>订单通知</h2>";
|
||||||
|
htmlBody += "<p><strong>用户:</strong> " + (params.username || "未知") + "</p>";
|
||||||
|
htmlBody += "<p><strong>程序:</strong> " + (params.app || "未知") + "</p>";
|
||||||
|
htmlBody += "<p><strong>单号:</strong> " + (order.TID || "") + "</p>";
|
||||||
|
htmlBody += "<p><strong>时间:</strong> " + (order.DATETIME || "") + "</p>";
|
||||||
|
htmlBody += "<p><strong>会员:</strong> " + (order.NAME0 || "") + "</p>";
|
||||||
|
htmlBody += "<p><strong>金额:</strong> " + (order.GOLD || "") + "</p>";
|
||||||
|
|
||||||
|
emailResult = smtp.send(smtpConfig, to, "订单通知 - " + (order.TID || ""), htmlBody, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "add_record",
|
||||||
|
variable_key: "orders",
|
||||||
|
record_data: order,
|
||||||
|
message: msg,
|
||||||
|
telegram_result: telegramResult,
|
||||||
|
email_result: emailResult
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range functions {
|
||||||
|
var existingFn model.DynamicCode
|
||||||
|
err := database.DB.Unscoped().Where("application_id = ? AND key = ?", app.ID, fn.Key).First(&existingFn).Error
|
||||||
|
if err == nil {
|
||||||
|
database.DB.Unscoped().Delete(&existingFn)
|
||||||
|
log.Printf("删除旧的云端函数: %s (key: %s)", fn.Name, fn.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("创建云端函数: %s (key: %s)", fn.Name, fn.Key)
|
||||||
|
dynamicCode := model.DynamicCode{
|
||||||
|
ApplicationID: app.ID,
|
||||||
|
UserID: &app.UserID,
|
||||||
|
Name: fn.Name,
|
||||||
|
Key: fn.Key,
|
||||||
|
Code: fn.Code,
|
||||||
|
Description: fn.Description,
|
||||||
|
Status: "active",
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&dynamicCode).Error; err != nil {
|
||||||
|
log.Printf("创建云端函数失败 %s: %v", fn.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("\n=== 创建完成 ===")
|
||||||
|
log.Println("云端变量: orders (记录类型)")
|
||||||
|
log.Println("云端函数:")
|
||||||
|
log.Println(" - test_push: 测试推送")
|
||||||
|
log.Println(" - read_order: 读取订单")
|
||||||
|
log.Println(" - accept_order: 接收订单")
|
||||||
|
log.Println(" - screen_order: 筛选订单")
|
||||||
|
log.Println(" - bet_order_success: 跟单成功")
|
||||||
|
log.Println(" - bet_order_failed: 跟单失败")
|
||||||
|
log.Println(" - account_disconnected: 账户掉线")
|
||||||
|
log.Println(" - account_reconnected: 账户重连")
|
||||||
|
log.Println(" - test_email: 测试邮件")
|
||||||
|
log.Println(" - order_email_notify: 订单邮件通知")
|
||||||
|
}
|
||||||
Generated
+12795
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Boxes, Code, Database, DollarSign, FileLock, Gauge, GitBranch, Hash, Key, Megaphone, MessageSquare, Network, Plug, ScrollText, Shield, Users, Variable } from 'lucide-vue-next'
|
import { Boxes, Code, DollarSign, FileLock, Gauge, GitBranch, Hash, Key, Megaphone, MessageSquare, Network, Plug, ScrollText, Shield, Users, Variable } from 'lucide-vue-next'
|
||||||
import { onMounted, reactive } from 'vue'
|
import { onMounted, reactive } from 'vue'
|
||||||
|
|
||||||
import NavTeam from '@/components/app-sidebar/nav-team.vue'
|
import NavTeam from '@/components/app-sidebar/nav-team.vue'
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from '@/components/ui/breadcrumb'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
import type { LayoutHeaderProps } from './types'
|
import type { LayoutHeaderProps } from './types'
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ defineExpose({
|
|||||||
type="search"
|
type="search"
|
||||||
:placeholder="t('developer.agents.searchPlaceholder')"
|
:placeholder="t('developer.agents.searchPlaceholder')"
|
||||||
class="h-9 w-[250px] pl-8"
|
class="h-9 w-[250px] pl-8"
|
||||||
@update:model-value="emit('update:searchFilter', $event)"
|
@update:model-value="emit('update:searchFilter', String($event))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<slot name="filters" />
|
<slot name="filters" />
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const agents = ref<Agent[]>([])
|
const agents = ref<Agent[]>([])
|
||||||
const tableRef = ref()
|
const tableRef = ref<InstanceType<typeof DataTable> | null>(null)
|
||||||
const searchFilter = ref('')
|
const searchFilter = ref('')
|
||||||
const statusFilter = ref<string>('')
|
const statusFilter = ref<string>('')
|
||||||
const viewMode = ref<'tree' | 'list'>('tree')
|
const viewMode = ref<'tree' | 'list'>('tree')
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ const testEmail = ref('')
|
|||||||
const sendCodeEmail = ref('')
|
const sendCodeEmail = ref('')
|
||||||
const sendCodePurpose = ref('register')
|
const sendCodePurpose = ref('register')
|
||||||
|
|
||||||
const canUseEmailVerify = computed(() => config.value?.permission?.allow_email_verify)
|
const _canUseEmailVerify = computed(() => config.value?.permission?.allow_email_verify)
|
||||||
const canUsePasswordReset = computed(() => config.value?.permission?.allow_password_reset)
|
const canUsePasswordReset = computed(() => config.value?.permission?.allow_password_reset)
|
||||||
const canUseCustomSMTP = computed(() => config.value?.permission?.allow_custom_smtp)
|
const _canUseCustomSMTP = computed(() => config.value?.permission?.allow_custom_smtp)
|
||||||
const canUseCustomTemplate = computed(() => config.value?.permission?.allow_custom_template)
|
const canUseCustomTemplate = computed(() => config.value?.permission?.allow_custom_template)
|
||||||
|
|
||||||
async function fetchConfig() {
|
async function fetchConfig() {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import type { App } from '../data/schema'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
app: App
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
refresh: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function formatDate(date: string | Date) {
|
||||||
|
return new Date(date).toLocaleDateString('zh-CN')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UiCard class="cursor-pointer hover:border-primary/50 transition-colors" @click="router.push(`/admin/applications/${props.app.id}`)">
|
||||||
|
<UiCardHeader class="pb-3">
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="size-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
||||||
|
<Icon v-if="props.app.icon_url" :icon="props.app.icon_url" class="size-6 text-primary" />
|
||||||
|
<Icon v-else icon="lucide:box" class="size-6 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<UiCardTitle class="text-base">
|
||||||
|
{{ props.app.name }}
|
||||||
|
</UiCardTitle>
|
||||||
|
<p class="text-sm text-muted-foreground line-clamp-1">
|
||||||
|
{{ props.app.description || '暂无描述' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UiBadge :variant="props.app.status === 'active' ? 'default' : 'secondary'">
|
||||||
|
{{ props.app.status === 'active' ? '正常' : '停用' }}
|
||||||
|
</UiBadge>
|
||||||
|
</div>
|
||||||
|
</UiCardHeader>
|
||||||
|
<UiCardContent>
|
||||||
|
<div class="flex items-center justify-between text-sm text-muted-foreground">
|
||||||
|
<span>用户: {{ props.app.users || 0 }}</span>
|
||||||
|
<span>验证: {{ props.app.verify_count || 0 }}</span>
|
||||||
|
<span>{{ formatDate(props.app.created_at) }}</span>
|
||||||
|
</div>
|
||||||
|
</UiCardContent>
|
||||||
|
</UiCard>
|
||||||
|
</template>
|
||||||
@@ -27,7 +27,6 @@ const form = ref({
|
|||||||
key: '',
|
key: '',
|
||||||
default_value: '',
|
default_value: '',
|
||||||
var_type: 'string',
|
var_type: 'string',
|
||||||
data_type: 'single',
|
|
||||||
max_records: 0,
|
max_records: 0,
|
||||||
scope: 'app',
|
scope: 'app',
|
||||||
write_permission: 'admin',
|
write_permission: 'admin',
|
||||||
@@ -49,7 +48,7 @@ async function fetchVariable() {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const varId = route.params.id
|
const varId = route.params.id
|
||||||
const data = await api.get<{ id: number, app_id: number, key: string, default_value: string, var_type: string, data_type: string, max_records: number, scope: string, write_permission: string, description: string, status: string }>(`/dev/cloud-variables/${varId}`)
|
const data = await api.get<{ id: number, app_id: number, key: string, default_value: string, var_type: string, max_records: number, scope: string, write_permission: string, description: string, status: string }>(`/dev/cloud-variables/${varId}`)
|
||||||
const variable = data
|
const variable = data
|
||||||
if (variable) {
|
if (variable) {
|
||||||
form.value = {
|
form.value = {
|
||||||
@@ -58,7 +57,6 @@ async function fetchVariable() {
|
|||||||
key: variable.key,
|
key: variable.key,
|
||||||
default_value: variable.default_value || '',
|
default_value: variable.default_value || '',
|
||||||
var_type: variable.var_type || 'string',
|
var_type: variable.var_type || 'string',
|
||||||
data_type: variable.data_type || 'single',
|
|
||||||
max_records: variable.max_records || 0,
|
max_records: variable.max_records || 0,
|
||||||
scope: variable.scope || 'app',
|
scope: variable.scope || 'app',
|
||||||
write_permission: variable.write_permission || 'admin',
|
write_permission: variable.write_permission || 'admin',
|
||||||
@@ -100,7 +98,6 @@ async function handleSave() {
|
|||||||
key: form.value.key,
|
key: form.value.key,
|
||||||
default_value: form.value.default_value,
|
default_value: form.value.default_value,
|
||||||
var_type: form.value.var_type,
|
var_type: form.value.var_type,
|
||||||
data_type: form.value.data_type,
|
|
||||||
max_records: form.value.max_records,
|
max_records: form.value.max_records,
|
||||||
write_permission: form.value.write_permission,
|
write_permission: form.value.write_permission,
|
||||||
description: form.value.description,
|
description: form.value.description,
|
||||||
@@ -176,47 +173,14 @@ onMounted(async () => {
|
|||||||
<UiSelectItem value="string">
|
<UiSelectItem value="string">
|
||||||
{{ t('developer.cloudVariables.types.string') }}
|
{{ t('developer.cloudVariables.types.string') }}
|
||||||
</UiSelectItem>
|
</UiSelectItem>
|
||||||
|
<UiSelectItem value="stream">
|
||||||
|
记录
|
||||||
|
</UiSelectItem>
|
||||||
</UiSelectContent>
|
</UiSelectContent>
|
||||||
</UiSelect>
|
</UiSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div v-if="form.var_type === 'stream'" class="space-y-2">
|
||||||
<UiLabel>数据模式</UiLabel>
|
|
||||||
<UiRadioGroup v-model="form.data_type" class="grid grid-cols-2 gap-4">
|
|
||||||
<div
|
|
||||||
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
|
|
||||||
:class="form.data_type === 'single' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
|
||||||
@click="form.data_type = 'single'"
|
|
||||||
>
|
|
||||||
<UiRadioGroupItem value="single" class="mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-medium text-sm">
|
|
||||||
单值模式
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
|
||||||
存储单个值,适合配置项
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
|
|
||||||
:class="form.data_type === 'stream' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
|
||||||
@click="form.data_type = 'stream'"
|
|
||||||
>
|
|
||||||
<UiRadioGroupItem value="stream" class="mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-medium text-sm">
|
|
||||||
流水模式
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
|
||||||
存储记录列表,适合订单、日志等
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</UiRadioGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="form.data_type === 'stream'" class="space-y-2">
|
|
||||||
<UiLabel for="maxRecords">
|
<UiLabel for="maxRecords">
|
||||||
最大记录数
|
最大记录数
|
||||||
</UiLabel>
|
</UiLabel>
|
||||||
@@ -334,7 +298,7 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="form.data_type === 'stream'"
|
v-if="form.var_type === 'stream'"
|
||||||
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
|
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
|
||||||
:class="form.write_permission === 'app_user' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
:class="form.write_permission === 'app_user' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
||||||
@click="form.write_permission = 'app_user'"
|
@click="form.write_permission = 'app_user'"
|
||||||
@@ -375,13 +339,7 @@ onMounted(async () => {
|
|||||||
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.type') }}</span>
|
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.type') }}</span>
|
||||||
<span>{{ t(`developer.cloudVariables.types.${form.var_type}`) }}</span>
|
<span>{{ t(`developer.cloudVariables.types.${form.var_type}`) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-sm">
|
<div v-if="form.var_type === 'stream'" class="flex justify-between text-sm">
|
||||||
<span class="text-muted-foreground">数据模式</span>
|
|
||||||
<UiBadge variant="outline">
|
|
||||||
{{ form.data_type === 'stream' ? '流水' : '单值' }}
|
|
||||||
</UiBadge>
|
|
||||||
</div>
|
|
||||||
<div v-if="form.data_type === 'stream'" class="flex justify-between text-sm">
|
|
||||||
<span class="text-muted-foreground">最大记录数</span>
|
<span class="text-muted-foreground">最大记录数</span>
|
||||||
<span>{{ form.max_records || '不限制' }}</span>
|
<span>{{ form.max_records || '不限制' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import api from '@/services/api'
|
|||||||
interface Variable {
|
interface Variable {
|
||||||
id: number
|
id: number
|
||||||
key: string
|
key: string
|
||||||
data_type: string
|
var_type: string
|
||||||
scope: string
|
scope: string
|
||||||
max_records: number
|
max_records: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Record {
|
interface VariableRecord {
|
||||||
id: number
|
id: number
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
user_id?: number
|
user_id?: number
|
||||||
@@ -31,7 +31,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const variable = ref<Variable | null>(null)
|
const variable = ref<Variable | null>(null)
|
||||||
const records = ref<Record[]>([])
|
const records = ref<VariableRecord[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const pageSize = ref(20)
|
const pageSize = ref(20)
|
||||||
@@ -44,8 +44,8 @@ async function fetchVariable() {
|
|||||||
const data = await api.get<{ variables: Variable[] }>(`/dev/cloud-variables?app_id=${route.query.app_id}`)
|
const data = await api.get<{ variables: Variable[] }>(`/dev/cloud-variables?app_id=${route.query.app_id}`)
|
||||||
const vars = data?.variables || []
|
const vars = data?.variables || []
|
||||||
variable.value = vars.find((v: Variable) => String(v.id) === route.params.id) || null
|
variable.value = vars.find((v: Variable) => String(v.id) === route.params.id) || null
|
||||||
if (variable.value && variable.value.data_type !== 'stream') {
|
if (variable.value && variable.value.var_type !== 'stream') {
|
||||||
toast.error('该变量不是流水类型')
|
toast.error('该变量不是记录类型')
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ async function fetchRecords() {
|
|||||||
if (endDate.value)
|
if (endDate.value)
|
||||||
params.append('end_date', endDate.value)
|
params.append('end_date', endDate.value)
|
||||||
|
|
||||||
const data = await api.get(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
|
const data = await api.get<{ records: VariableRecord[], total: number }>(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
|
||||||
records.value = data?.records || []
|
records.value = data?.records || []
|
||||||
total.value = data?.total || 0
|
total.value = data?.total || 0
|
||||||
}
|
}
|
||||||
@@ -141,11 +141,11 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicPage
|
<BasicPage
|
||||||
title="流水记录"
|
title="变量记录"
|
||||||
description="查看云端变量的流水记录数据"
|
description="查看云端变量的记录数据"
|
||||||
:breadcrumbs="[
|
:breadcrumbs="[
|
||||||
{ title: '云端变量', href: '/developer/cloud-variables' },
|
{ title: '云端变量', href: '/developer/cloud-variables' },
|
||||||
{ title: '流水记录' },
|
{ title: '变量记录' },
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export function getColumns(actions: {
|
|||||||
onDelete: (row: CloudVariable) => void
|
onDelete: (row: CloudVariable) => void
|
||||||
onToggleStatus: (row: CloudVariable) => void
|
onToggleStatus: (row: CloudVariable) => void
|
||||||
onDownload: (row: CloudVariable) => void
|
onDownload: (row: CloudVariable) => void
|
||||||
|
onViewRecords?: (row: CloudVariable) => void
|
||||||
}, t: Composer['t']): ColumnDef<CloudVariable>[] {
|
}, t: Composer['t']): ColumnDef<CloudVariable>[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -86,6 +87,7 @@ export function getColumns(actions: {
|
|||||||
decimal: t('developer.cloudVariables.types.decimal'),
|
decimal: t('developer.cloudVariables.types.decimal'),
|
||||||
string: t('developer.cloudVariables.types.string'),
|
string: t('developer.cloudVariables.types.string'),
|
||||||
binary: t('developer.cloudVariables.types.binary'),
|
binary: t('developer.cloudVariables.types.binary'),
|
||||||
|
stream: '记录',
|
||||||
}
|
}
|
||||||
return h(Badge, { variant: 'outline' }, () => typeMap[type || 'string'] || type || 'string')
|
return h(Badge, { variant: 'outline' }, () => typeMap[type || 'string'] || type || 'string')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function viewRecords() {
|
|||||||
</UiButton>
|
</UiButton>
|
||||||
</UiDropdownMenuTrigger>
|
</UiDropdownMenuTrigger>
|
||||||
<UiDropdownMenuContent align="end" class="w-[160px]">
|
<UiDropdownMenuContent align="end" class="w-[160px]">
|
||||||
<UiDropdownMenuItem v-if="variable.data_type === 'stream'" @click="viewRecords">
|
<UiDropdownMenuItem v-if="variable.var_type === 'stream'" @click="viewRecords">
|
||||||
<List class="mr-2 h-4 w-4" />
|
<List class="mr-2 h-4 w-4" />
|
||||||
查看记录
|
查看记录
|
||||||
</UiDropdownMenuItem>
|
</UiDropdownMenuItem>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ const form = ref({
|
|||||||
key: '',
|
key: '',
|
||||||
default_value: '',
|
default_value: '',
|
||||||
var_type: 'string',
|
var_type: 'string',
|
||||||
data_type: 'single',
|
|
||||||
max_records: 0,
|
max_records: 0,
|
||||||
scope: 'app',
|
scope: 'app',
|
||||||
write_permission: 'admin',
|
write_permission: 'admin',
|
||||||
@@ -95,7 +94,6 @@ async function handleSave() {
|
|||||||
key: form.value.key,
|
key: form.value.key,
|
||||||
default_value: form.value.default_value,
|
default_value: form.value.default_value,
|
||||||
var_type: form.value.var_type,
|
var_type: form.value.var_type,
|
||||||
data_type: form.value.data_type,
|
|
||||||
max_records: form.value.max_records,
|
max_records: form.value.max_records,
|
||||||
scope: form.value.scope,
|
scope: form.value.scope,
|
||||||
write_permission: form.value.write_permission,
|
write_permission: form.value.write_permission,
|
||||||
@@ -215,47 +213,14 @@ onMounted(() => {
|
|||||||
<UiSelectItem value="binary">
|
<UiSelectItem value="binary">
|
||||||
二进制
|
二进制
|
||||||
</UiSelectItem>
|
</UiSelectItem>
|
||||||
|
<UiSelectItem value="stream">
|
||||||
|
记录
|
||||||
|
</UiSelectItem>
|
||||||
</UiSelectContent>
|
</UiSelectContent>
|
||||||
</UiSelect>
|
</UiSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div v-if="form.var_type === 'stream'" class="space-y-2">
|
||||||
<UiLabel>数据模式</UiLabel>
|
|
||||||
<UiRadioGroup v-model="form.data_type" class="grid grid-cols-2 gap-4">
|
|
||||||
<div
|
|
||||||
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
|
|
||||||
:class="form.data_type === 'single' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
|
||||||
@click="form.data_type = 'single'"
|
|
||||||
>
|
|
||||||
<UiRadioGroupItem value="single" class="mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-medium text-sm">
|
|
||||||
单值模式
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
|
||||||
存储单个值,适合配置项
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
|
|
||||||
:class="form.data_type === 'stream' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
|
||||||
@click="form.data_type = 'stream'"
|
|
||||||
>
|
|
||||||
<UiRadioGroupItem value="stream" class="mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-medium text-sm">
|
|
||||||
流水模式
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
|
||||||
存储记录列表,适合订单、日志等
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</UiRadioGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="form.data_type === 'stream'" class="space-y-2">
|
|
||||||
<UiLabel for="maxRecords">
|
<UiLabel for="maxRecords">
|
||||||
最大记录数
|
最大记录数
|
||||||
</UiLabel>
|
</UiLabel>
|
||||||
@@ -428,7 +393,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="form.data_type === 'stream'"
|
v-if="form.var_type === 'stream'"
|
||||||
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
|
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
|
||||||
:class="form.write_permission === 'app_user' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
:class="form.write_permission === 'app_user' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
||||||
@click="form.write_permission = 'app_user'"
|
@click="form.write_permission = 'app_user'"
|
||||||
@@ -469,13 +434,7 @@ onMounted(() => {
|
|||||||
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.type') }}</span>
|
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.type') }}</span>
|
||||||
<span>{{ t(`developer.cloudVariables.types.${form.var_type}`) }}</span>
|
<span>{{ t(`developer.cloudVariables.types.${form.var_type}`) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-sm">
|
<div v-if="form.var_type === 'stream'" class="flex justify-between text-sm">
|
||||||
<span class="text-muted-foreground">数据模式</span>
|
|
||||||
<UiBadge variant="outline">
|
|
||||||
{{ form.data_type === 'stream' ? '流水' : '单值' }}
|
|
||||||
</UiBadge>
|
|
||||||
</div>
|
|
||||||
<div v-if="form.data_type === 'stream'" class="flex justify-between text-sm">
|
|
||||||
<span class="text-muted-foreground">最大记录数</span>
|
<span class="text-muted-foreground">最大记录数</span>
|
||||||
<span>{{ form.max_records || '不限制' }}</span>
|
<span>{{ form.max_records || '不限制' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const cloudVariableSchema = z.object({
|
|||||||
key: z.string(),
|
key: z.string(),
|
||||||
default_value: z.string().optional().nullable(),
|
default_value: z.string().optional().nullable(),
|
||||||
var_type: z.string().optional().nullable(),
|
var_type: z.string().optional().nullable(),
|
||||||
data_type: z.string().optional().nullable(),
|
|
||||||
max_records: z.union([z.string(), z.number()]).optional().nullable(),
|
max_records: z.union([z.string(), z.number()]).optional().nullable(),
|
||||||
file_path: z.string().optional().nullable(),
|
file_path: z.string().optional().nullable(),
|
||||||
file_size: z.union([z.string(), z.number()]).optional().nullable(),
|
file_size: z.union([z.string(), z.number()]).optional().nullable(),
|
||||||
|
|||||||
@@ -268,9 +268,7 @@ onMounted(() => {
|
|||||||
<div class="text-muted-foreground mb-2">
|
<div class="text-muted-foreground mb-2">
|
||||||
{{ t('developer.cloudFunction.create.code') }}
|
{{ t('developer.cloudFunction.create.code') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
|
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre-wrap;">{{ form.code || '-' }}</div>
|
||||||
{{ form.code || '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -221,9 +221,7 @@ onMounted(() => {
|
|||||||
<div class="text-muted-foreground mb-2">
|
<div class="text-muted-foreground mb-2">
|
||||||
{{ t('developer.cloudFunction.create.code') }}
|
{{ t('developer.cloudFunction.create.code') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
|
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre-wrap;">{{ form.code || '-' }}</div>
|
||||||
{{ form.code || '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const cardTypes = ref<any[]>([])
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const appId = route.params.id
|
const appId = route.params.id
|
||||||
try {
|
try {
|
||||||
const data = await api.get(`/agent/apps/${appId}`)
|
const data = await api.get<{ app: any, cardTypes: any[] }>(`/agent/apps/${appId}`)
|
||||||
app.value = data?.app
|
app.value = data?.app
|
||||||
cardTypes.value = data?.cardTypes || []
|
cardTypes.value = data?.cardTypes || []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const form = ref({
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedApp = computed(() => {
|
const _selectedApp = computed(() => {
|
||||||
return apps.value.find(a => a.id === Number(form.value.app_id))
|
return apps.value.find(a => a.id === Number(form.value.app_id))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -34,11 +34,11 @@ const totalPrice = computed(() => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await api.get('/agent/apps')
|
const data = await api.get<any[]>('/agent/apps')
|
||||||
apps.value = data || []
|
apps.value = data || []
|
||||||
|
|
||||||
if (apps.value.length > 0) {
|
if (apps.value.length > 0) {
|
||||||
const typesData = await api.get('/agent/card-types')
|
const typesData = await api.get<any[]>('/agent/card-types')
|
||||||
cardTypes.value = typesData || []
|
cardTypes.value = typesData || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function formatDate(dateStr: string) {
|
function formatDate(dateStr: string | null) {
|
||||||
if (!dateStr)
|
if (!dateStr)
|
||||||
return '-'
|
return '-'
|
||||||
return new Date(dateStr).toLocaleDateString('zh-CN')
|
return new Date(dateStr).toLocaleDateString('zh-CN')
|
||||||
|
|||||||
@@ -18,7 +18,14 @@ const recentCards = ref<any[]>([])
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await api.get('/agent/stats')
|
const data = await api.get<{
|
||||||
|
totalApps?: number
|
||||||
|
totalCards?: number
|
||||||
|
totalUsers?: number
|
||||||
|
totalRevenue?: number
|
||||||
|
todayCards?: number
|
||||||
|
todayRevenue?: number
|
||||||
|
}>('/agent/stats')
|
||||||
if (data) {
|
if (data) {
|
||||||
stats.value = {
|
stats.value = {
|
||||||
totalApps: data.totalApps || 0,
|
totalApps: data.totalApps || 0,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function formatDate(dateStr: string) {
|
function formatDate(dateStr: string | null) {
|
||||||
if (!dateStr)
|
if (!dateStr)
|
||||||
return '-'
|
return '-'
|
||||||
return new Date(dateStr).toLocaleDateString('zh-CN')
|
return new Date(dateStr).toLocaleDateString('zh-CN')
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ defineExpose({
|
|||||||
type="search"
|
type="search"
|
||||||
:placeholder="t('developer.agents.searchPlaceholder')"
|
:placeholder="t('developer.agents.searchPlaceholder')"
|
||||||
class="h-9 w-[250px] pl-8"
|
class="h-9 w-[250px] pl-8"
|
||||||
@update:model-value="emit('update:searchFilter', $event)"
|
@update:model-value="emit('update:searchFilter', String($event))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<slot name="filters" />
|
<slot name="filters" />
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const agents = ref<Agent[]>([])
|
const agents = ref<Agent[]>([])
|
||||||
const tableRef = ref()
|
const _tableRef = ref<InstanceType<typeof DataTable> | null>(null)
|
||||||
const searchFilter = ref('')
|
const searchFilter = ref('')
|
||||||
const statusFilter = ref<string>('')
|
const statusFilter = ref<string>('')
|
||||||
const viewMode = ref<'tree' | 'list'>('tree')
|
const viewMode = ref<'tree' | 'list'>('tree')
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import api from '@/services/api'
|
|||||||
interface Variable {
|
interface Variable {
|
||||||
id: number
|
id: number
|
||||||
key: string
|
key: string
|
||||||
data_type: string
|
var_type: string
|
||||||
scope: string
|
scope: string
|
||||||
max_records: number
|
max_records: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Record {
|
interface VariableRecord {
|
||||||
id: number
|
id: number
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
user_id?: number
|
user_id?: number
|
||||||
@@ -31,7 +31,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const variable = ref<Variable | null>(null)
|
const variable = ref<Variable | null>(null)
|
||||||
const records = ref<Record[]>([])
|
const records = ref<VariableRecord[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const pageSize = ref(20)
|
const pageSize = ref(20)
|
||||||
@@ -44,8 +44,8 @@ async function fetchVariable() {
|
|||||||
const data = await api.get<{ variables: Variable[] }>(`/dev/cloud-variables?app_id=${route.query.app_id}`)
|
const data = await api.get<{ variables: Variable[] }>(`/dev/cloud-variables?app_id=${route.query.app_id}`)
|
||||||
const vars = data?.variables || []
|
const vars = data?.variables || []
|
||||||
variable.value = vars.find((v: Variable) => String(v.id) === route.params.id) || null
|
variable.value = vars.find((v: Variable) => String(v.id) === route.params.id) || null
|
||||||
if (variable.value && variable.value.data_type !== 'stream') {
|
if (variable.value && variable.value.var_type !== 'stream') {
|
||||||
toast.error('该变量不是流水类型')
|
toast.error('该变量不是记录类型')
|
||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ async function fetchRecords() {
|
|||||||
if (endDate.value)
|
if (endDate.value)
|
||||||
params.append('end_date', endDate.value)
|
params.append('end_date', endDate.value)
|
||||||
|
|
||||||
const data = await api.get(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
|
const data = await api.get<{ records: VariableRecord[], total: number }>(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
|
||||||
records.value = data?.records || []
|
records.value = data?.records || []
|
||||||
total.value = data?.total || 0
|
total.value = data?.total || 0
|
||||||
}
|
}
|
||||||
@@ -141,11 +141,11 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicPage
|
<BasicPage
|
||||||
title="流水记录"
|
title="变量记录"
|
||||||
description="查看云端变量的流水记录数据"
|
description="查看云端变量的记录数据"
|
||||||
:breadcrumbs="[
|
:breadcrumbs="[
|
||||||
{ title: '云端变量', href: '/developer/cloud-variables' },
|
{ title: '云端变量', href: '/developer/cloud-variables' },
|
||||||
{ title: '流水记录' },
|
{ title: '变量记录' },
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { Composer } from 'vue-i18n'
|
|||||||
|
|
||||||
import { Download, File, List, MoreHorizontal, Pencil, Power, PowerOff, Trash2 } from 'lucide-vue-next'
|
import { Download, File, List, MoreHorizontal, Pencil, Power, PowerOff, Trash2 } from 'lucide-vue-next'
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
|
|
||||||
import type { CloudVariable } from '@/pages/developer/cloud-variables/data/schema'
|
import type { CloudVariable } from '@/pages/developer/cloud-variables/data/schema'
|
||||||
|
|
||||||
@@ -88,21 +87,11 @@ export function getColumns(actions: {
|
|||||||
decimal: t('developer.cloudVariables.types.decimal'),
|
decimal: t('developer.cloudVariables.types.decimal'),
|
||||||
string: t('developer.cloudVariables.types.string'),
|
string: t('developer.cloudVariables.types.string'),
|
||||||
binary: t('developer.cloudVariables.types.binary'),
|
binary: t('developer.cloudVariables.types.binary'),
|
||||||
|
stream: '记录',
|
||||||
}
|
}
|
||||||
return h(Badge, { variant: 'outline' }, () => typeMap[type || 'string'] || type || 'string')
|
return h(Badge, { variant: 'outline' }, () => typeMap[type || 'string'] || type || 'string')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorKey: 'data_type',
|
|
||||||
header: () => '数据模式',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const dataType = row.getValue('data_type') as string
|
|
||||||
if (dataType === 'stream') {
|
|
||||||
return h(Badge, { variant: 'secondary', class: 'bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300' }, () => '流水')
|
|
||||||
}
|
|
||||||
return h(Badge, { variant: 'outline' }, () => '单值')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'description',
|
accessorKey: 'description',
|
||||||
header: () => t('developer.cloudVariables.columns.description'),
|
header: () => t('developer.cloudVariables.columns.description'),
|
||||||
@@ -182,7 +171,7 @@ export function getColumns(actions: {
|
|||||||
() => {
|
() => {
|
||||||
const items = []
|
const items = []
|
||||||
|
|
||||||
if (variable.data_type === 'stream') {
|
if (variable.var_type === 'stream') {
|
||||||
items.push(
|
items.push(
|
||||||
h(DropdownMenuItem, { onClick: () => actions.onViewRecords(variable) }, () => [
|
h(DropdownMenuItem, { onClick: () => actions.onViewRecords(variable) }, () => [
|
||||||
h(List, { class: 'mr-2 h-4 w-4' }),
|
h(List, { class: 'mr-2 h-4 w-4' }),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function viewRecords() {
|
|||||||
</UiButton>
|
</UiButton>
|
||||||
</UiDropdownMenuTrigger>
|
</UiDropdownMenuTrigger>
|
||||||
<UiDropdownMenuContent align="end" class="w-[160px]">
|
<UiDropdownMenuContent align="end" class="w-[160px]">
|
||||||
<UiDropdownMenuItem v-if="variable.data_type === 'stream'" @click="viewRecords">
|
<UiDropdownMenuItem v-if="variable.var_type === 'stream'" @click="viewRecords">
|
||||||
<List class="mr-2 h-4 w-4" />
|
<List class="mr-2 h-4 w-4" />
|
||||||
查看记录
|
查看记录
|
||||||
</UiDropdownMenuItem>
|
</UiDropdownMenuItem>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ const form = ref({
|
|||||||
key: '',
|
key: '',
|
||||||
default_value: '',
|
default_value: '',
|
||||||
var_type: 'string',
|
var_type: 'string',
|
||||||
data_type: 'single',
|
|
||||||
max_records: 0,
|
max_records: 0,
|
||||||
scope: 'app',
|
scope: 'app',
|
||||||
write_permission: 'admin',
|
write_permission: 'admin',
|
||||||
@@ -95,7 +94,6 @@ async function handleSave() {
|
|||||||
key: form.value.key,
|
key: form.value.key,
|
||||||
default_value: form.value.default_value,
|
default_value: form.value.default_value,
|
||||||
var_type: form.value.var_type,
|
var_type: form.value.var_type,
|
||||||
data_type: form.value.data_type,
|
|
||||||
max_records: form.value.max_records,
|
max_records: form.value.max_records,
|
||||||
scope: form.value.scope,
|
scope: form.value.scope,
|
||||||
write_permission: form.value.write_permission,
|
write_permission: form.value.write_permission,
|
||||||
@@ -215,47 +213,14 @@ onMounted(() => {
|
|||||||
<UiSelectItem value="binary">
|
<UiSelectItem value="binary">
|
||||||
二进制
|
二进制
|
||||||
</UiSelectItem>
|
</UiSelectItem>
|
||||||
|
<UiSelectItem value="stream">
|
||||||
|
记录
|
||||||
|
</UiSelectItem>
|
||||||
</UiSelectContent>
|
</UiSelectContent>
|
||||||
</UiSelect>
|
</UiSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div v-if="form.var_type === 'stream'" class="space-y-2">
|
||||||
<UiLabel>数据模式</UiLabel>
|
|
||||||
<UiRadioGroup v-model="form.data_type" class="grid grid-cols-2 gap-4">
|
|
||||||
<div
|
|
||||||
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
|
|
||||||
:class="form.data_type === 'single' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
|
||||||
@click="form.data_type = 'single'"
|
|
||||||
>
|
|
||||||
<UiRadioGroupItem value="single" class="mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-medium text-sm">
|
|
||||||
单值模式
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
|
||||||
存储单个值,适合配置项
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
|
|
||||||
:class="form.data_type === 'stream' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
|
|
||||||
@click="form.data_type = 'stream'"
|
|
||||||
>
|
|
||||||
<UiRadioGroupItem value="stream" class="mt-0.5" />
|
|
||||||
<div>
|
|
||||||
<p class="font-medium text-sm">
|
|
||||||
流水模式
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
|
||||||
存储记录列表,适合订单、日志等
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</UiRadioGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="form.data_type === 'stream'" class="space-y-2">
|
|
||||||
<UiLabel for="maxRecords">
|
<UiLabel for="maxRecords">
|
||||||
最大记录数
|
最大记录数
|
||||||
</UiLabel>
|
</UiLabel>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const cloudVariableSchema = z.object({
|
|||||||
key: z.string(),
|
key: z.string(),
|
||||||
default_value: z.string().optional().nullable(),
|
default_value: z.string().optional().nullable(),
|
||||||
var_type: z.string().optional().nullable(),
|
var_type: z.string().optional().nullable(),
|
||||||
data_type: z.string().optional().nullable(),
|
|
||||||
max_records: z.union([z.string(), z.number()]).optional().nullable(),
|
max_records: z.union([z.string(), z.number()]).optional().nullable(),
|
||||||
file_path: z.string().optional().nullable(),
|
file_path: z.string().optional().nullable(),
|
||||||
file_size: z.union([z.string(), z.number()]).optional().nullable(),
|
file_size: z.union([z.string(), z.number()]).optional().nullable(),
|
||||||
|
|||||||
@@ -268,9 +268,7 @@ onMounted(() => {
|
|||||||
<div class="text-muted-foreground mb-2">
|
<div class="text-muted-foreground mb-2">
|
||||||
{{ t('developer.cloudFunction.create.code') }}
|
{{ t('developer.cloudFunction.create.code') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
|
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre !important;">{{ form.code || '-' }}</div>
|
||||||
{{ form.code || '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -221,9 +221,7 @@ onMounted(() => {
|
|||||||
<div class="text-muted-foreground mb-2">
|
<div class="text-muted-foreground mb-2">
|
||||||
{{ t('developer.cloudFunction.create.code') }}
|
{{ t('developer.cloudFunction.create.code') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
|
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre !important;">{{ form.code || '-' }}</div>
|
||||||
{{ form.code || '-' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1152,7 +1152,8 @@
|
|||||||
"integer": "Integer",
|
"integer": "Integer",
|
||||||
"decimal": "Decimal",
|
"decimal": "Decimal",
|
||||||
"string": "String",
|
"string": "String",
|
||||||
"binary": "Binary"
|
"binary": "Binary",
|
||||||
|
"stream": "Record"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"config": "Configuration",
|
"config": "Configuration",
|
||||||
@@ -1238,7 +1239,8 @@
|
|||||||
"integer": "Integer",
|
"integer": "Integer",
|
||||||
"decimal": "Decimal",
|
"decimal": "Decimal",
|
||||||
"string": "String",
|
"string": "String",
|
||||||
"binary": "Binary"
|
"binary": "Binary",
|
||||||
|
"stream": "Record"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"config": "Configuration",
|
"config": "Configuration",
|
||||||
|
|||||||
@@ -1128,7 +1128,8 @@
|
|||||||
"integer": "整数",
|
"integer": "整数",
|
||||||
"decimal": "小数",
|
"decimal": "小数",
|
||||||
"string": "字符串",
|
"string": "字符串",
|
||||||
"binary": "二进制"
|
"binary": "二进制",
|
||||||
|
"stream": "记录"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"config": "配置",
|
"config": "配置",
|
||||||
@@ -1214,7 +1215,8 @@
|
|||||||
"integer": "整数",
|
"integer": "整数",
|
||||||
"decimal": "小数",
|
"decimal": "小数",
|
||||||
"string": "字符串",
|
"string": "字符串",
|
||||||
"binary": "二进制"
|
"binary": "二进制",
|
||||||
|
"stream": "记录"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"config": "配置",
|
"config": "配置",
|
||||||
|
|||||||
-16
@@ -12,8 +12,6 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AdminSidebar: typeof import('./../components/admin-sidebar/index.vue')['default']
|
|
||||||
AdminSidebarNavFooter: typeof import('./../components/admin-sidebar/nav-footer.vue')['default']
|
|
||||||
AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
||||||
AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
|
AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
|
||||||
AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
|
AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
|
||||||
@@ -57,14 +55,8 @@ declare module 'vue' {
|
|||||||
InspiraUiRippleContainer: typeof import('./../components/inspira-ui/ripple/container.vue')['default']
|
InspiraUiRippleContainer: typeof import('./../components/inspira-ui/ripple/container.vue')['default']
|
||||||
LanguageChange: typeof import('./../components/language-change.vue')['default']
|
LanguageChange: typeof import('./../components/language-change.vue')['default']
|
||||||
Loading: typeof import('./../components/loading.vue')['default']
|
Loading: typeof import('./../components/loading.vue')['default']
|
||||||
MarketingEvaluation: typeof import('./../components/marketing/evaluation.vue')['default']
|
|
||||||
MarketingFeatures: typeof import('./../components/marketing/features.vue')['default']
|
|
||||||
MarketingHero: typeof import('./../components/marketing/hero.vue')['default']
|
|
||||||
MarketingLayoutTheFooter: typeof import('./../components/marketing-layout/the-footer.vue')['default']
|
MarketingLayoutTheFooter: typeof import('./../components/marketing-layout/the-footer.vue')['default']
|
||||||
MarketingLayoutTheHeader: typeof import('./../components/marketing-layout/the-header.vue')['default']
|
MarketingLayoutTheHeader: typeof import('./../components/marketing-layout/the-header.vue')['default']
|
||||||
MarketingLogos: typeof import('./../components/marketing/logos.vue')['default']
|
|
||||||
MarketingPricingPlans: typeof import('./../components/marketing/pricing-plans/index.vue')['default']
|
|
||||||
MarketingSetup: typeof import('./../components/marketing/setup.vue')['default']
|
|
||||||
NoResultFound: typeof import('./../components/no-result-found.vue')['default']
|
NoResultFound: typeof import('./../components/no-result-found.vue')['default']
|
||||||
PropUiModal: typeof import('./../components/prop-ui/modal/Modal.vue')['default']
|
PropUiModal: typeof import('./../components/prop-ui/modal/Modal.vue')['default']
|
||||||
PropUiModalClose: typeof import('./../components/prop-ui/modal/ModalClose.vue')['default']
|
PropUiModalClose: typeof import('./../components/prop-ui/modal/ModalClose.vue')['default']
|
||||||
@@ -423,8 +415,6 @@ declare module 'vue' {
|
|||||||
|
|
||||||
// For TSX support
|
// For TSX support
|
||||||
declare global {
|
declare global {
|
||||||
const AdminSidebar: typeof import('./../components/admin-sidebar/index.vue')['default']
|
|
||||||
const AdminSidebarNavFooter: typeof import('./../components/admin-sidebar/nav-footer.vue')['default']
|
|
||||||
const AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
const AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
||||||
const AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
|
const AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
|
||||||
const AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
|
const AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
|
||||||
@@ -468,14 +458,8 @@ declare global {
|
|||||||
const InspiraUiRippleContainer: typeof import('./../components/inspira-ui/ripple/container.vue')['default']
|
const InspiraUiRippleContainer: typeof import('./../components/inspira-ui/ripple/container.vue')['default']
|
||||||
const LanguageChange: typeof import('./../components/language-change.vue')['default']
|
const LanguageChange: typeof import('./../components/language-change.vue')['default']
|
||||||
const Loading: typeof import('./../components/loading.vue')['default']
|
const Loading: typeof import('./../components/loading.vue')['default']
|
||||||
const MarketingEvaluation: typeof import('./../components/marketing/evaluation.vue')['default']
|
|
||||||
const MarketingFeatures: typeof import('./../components/marketing/features.vue')['default']
|
|
||||||
const MarketingHero: typeof import('./../components/marketing/hero.vue')['default']
|
|
||||||
const MarketingLayoutTheFooter: typeof import('./../components/marketing-layout/the-footer.vue')['default']
|
const MarketingLayoutTheFooter: typeof import('./../components/marketing-layout/the-footer.vue')['default']
|
||||||
const MarketingLayoutTheHeader: typeof import('./../components/marketing-layout/the-header.vue')['default']
|
const MarketingLayoutTheHeader: typeof import('./../components/marketing-layout/the-header.vue')['default']
|
||||||
const MarketingLogos: typeof import('./../components/marketing/logos.vue')['default']
|
|
||||||
const MarketingPricingPlans: typeof import('./../components/marketing/pricing-plans/index.vue')['default']
|
|
||||||
const MarketingSetup: typeof import('./../components/marketing/setup.vue')['default']
|
|
||||||
const NoResultFound: typeof import('./../components/no-result-found.vue')['default']
|
const NoResultFound: typeof import('./../components/no-result-found.vue')['default']
|
||||||
const PropUiModal: typeof import('./../components/prop-ui/modal/Modal.vue')['default']
|
const PropUiModal: typeof import('./../components/prop-ui/modal/Modal.vue')['default']
|
||||||
const PropUiModalClose: typeof import('./../components/prop-ui/modal/ModalClose.vue')['default']
|
const PropUiModalClose: typeof import('./../components/prop-ui/modal/ModalClose.vue')['default']
|
||||||
|
|||||||
Vendored
+3
@@ -14,6 +14,9 @@ import type {
|
|||||||
ParamValueZeroOrMore,
|
ParamValueZeroOrMore,
|
||||||
ParamValueZeroOrOne,
|
ParamValueZeroOrOne,
|
||||||
} from 'vue-router'
|
} from 'vue-router'
|
||||||
|
import type {
|
||||||
|
_ExtractParamParserType,
|
||||||
|
} from 'vue-router/experimental'
|
||||||
|
|
||||||
declare module 'vue-router' {
|
declare module 'vue-router' {
|
||||||
interface TypesConfig {
|
interface TypesConfig {
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": false,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user