feat: add tenant entry application pages

This commit is contained in:
richjohnrambo
2026-09-22 23:17:55 +08:00
parent 935bfe17a0
commit abd5eb162a
22 changed files with 1447 additions and 55 deletions
@@ -0,0 +1,34 @@
// Package imtenantapplication
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package imtenantapplication
import (
"hotgo/internal/model/input/form"
"hotgo/internal/model/input/sysin"
"github.com/gogf/gf/v2/frame/g"
)
// ListReq 查询企业注册申请列表
type ListReq struct {
g.Meta `path:"/imTenantApplication/list" method:"get" tags:"企业注册申请" summary:"获取企业注册申请列表"`
sysin.ImTenantApplicationListInp
}
type ListRes struct {
form.PageRes
List []*sysin.ImTenantApplicationListModel `json:"list" dc:"数据列表"`
}
// ViewReq 获取企业注册申请指定信息
type ViewReq struct {
g.Meta `path:"/imTenantApplication/view" method:"get" tags:"企业注册申请" summary:"获取企业注册申请指定信息"`
sysin.ImTenantApplicationViewInp
}
type ViewRes struct {
*sysin.ImTenantApplicationViewModel
}
@@ -0,0 +1,34 @@
// Package imtenantentryfeedback
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package imtenantentryfeedback
import (
"hotgo/internal/model/input/form"
"hotgo/internal/model/input/sysin"
"github.com/gogf/gf/v2/frame/g"
)
// ListReq 查询企业入口反馈列表
type ListReq struct {
g.Meta `path:"/imTenantEntryFeedback/list" method:"get" tags:"企业入口反馈" summary:"获取企业入口反馈列表"`
sysin.ImTenantEntryFeedbackListInp
}
type ListRes struct {
form.PageRes
List []*sysin.ImTenantEntryFeedbackListModel `json:"list" dc:"数据列表"`
}
// ViewReq 获取企业入口反馈指定信息
type ViewReq struct {
g.Meta `path:"/imTenantEntryFeedback/view" method:"get" tags:"企业入口反馈" summary:"获取企业入口反馈指定信息"`
sysin.ImTenantEntryFeedbackViewInp
}
type ViewRes struct {
*sysin.ImTenantEntryFeedbackViewModel
}
@@ -0,0 +1,47 @@
// Package sys
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sys
import (
"context"
"hotgo/api/admin/imtenantapplication"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
)
var (
ImTenantApplication = cImTenantApplication{}
)
type cImTenantApplication struct{}
// List 查看企业注册申请列表
func (c *cImTenantApplication) List(ctx context.Context, req *imtenantapplication.ListReq) (res *imtenantapplication.ListRes, err error) {
list, totalCount, err := service.SysImTenantApplication().List(ctx, &req.ImTenantApplicationListInp)
if err != nil {
return
}
if list == nil {
list = []*sysin.ImTenantApplicationListModel{}
}
res = new(imtenantapplication.ListRes)
res.List = list
res.PageRes.Pack(req, totalCount)
return
}
// View 获取指定企业注册申请信息
func (c *cImTenantApplication) View(ctx context.Context, req *imtenantapplication.ViewReq) (res *imtenantapplication.ViewRes, err error) {
data, err := service.SysImTenantApplication().View(ctx, &req.ImTenantApplicationViewInp)
if err != nil {
return
}
res = new(imtenantapplication.ViewRes)
res.ImTenantApplicationViewModel = data
return
}
@@ -0,0 +1,47 @@
// Package sys
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sys
import (
"context"
"hotgo/api/admin/imtenantentryfeedback"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
)
var (
ImTenantEntryFeedback = cImTenantEntryFeedback{}
)
type cImTenantEntryFeedback struct{}
// List 查看企业入口反馈列表
func (c *cImTenantEntryFeedback) List(ctx context.Context, req *imtenantentryfeedback.ListReq) (res *imtenantentryfeedback.ListRes, err error) {
list, totalCount, err := service.SysImTenantEntryFeedback().List(ctx, &req.ImTenantEntryFeedbackListInp)
if err != nil {
return
}
if list == nil {
list = []*sysin.ImTenantEntryFeedbackListModel{}
}
res = new(imtenantentryfeedback.ListRes)
res.List = list
res.PageRes.Pack(req, totalCount)
return
}
// View 获取指定企业入口反馈信息
func (c *cImTenantEntryFeedback) View(ctx context.Context, req *imtenantentryfeedback.ViewReq) (res *imtenantentryfeedback.ViewRes, err error) {
data, err := service.SysImTenantEntryFeedback().View(ctx, &req.ImTenantEntryFeedbackViewInp)
if err != nil {
return
}
res = new(imtenantentryfeedback.ViewRes)
res.ImTenantEntryFeedbackViewModel = data
return
}
@@ -0,0 +1,90 @@
// Package sys
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sys
import (
"context"
"hotgo/internal/library/storager"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"strings"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
func loadImPublicUploads(ctx context.Context, fileIds []string) (files []*sysin.ImPublicUploadModel, err error) {
fileIds = compactStringSlice(fileIds)
if len(fileIds) == 0 {
return []*sysin.ImPublicUploadModel{}, nil
}
if err = g.DB().Model("im_public_uploads").Safe().Ctx(ctx).
Fields(
"id",
"file_id",
"purpose",
"mime_type",
"size",
"etag",
"location",
"expires_at",
"created_at",
"uploaded_at",
).
WhereIn("file_id", fileIds).
OrderAsc("id").
Scan(&files); err != nil {
return nil, gerror.Wrap(err, "获取公共上传文件信息失败,请稍后重试!")
}
drive := ""
if conf, confErr := service.SysConfig().GetUpload(ctx); confErr == nil && conf != nil {
drive = conf.Drive
}
for _, file := range files {
file.Url = storager.LastUrl(ctx, file.Location, drive)
}
return
}
func firstImPublicUpload(ctx context.Context, fileId string) (file *sysin.ImPublicUploadModel, err error) {
files, err := loadImPublicUploads(ctx, []string{fileId})
if err != nil || len(files) == 0 {
return
}
return files[0], nil
}
func parseJSONStringSlice(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" || raw == "null" {
return nil
}
var ids []string
if err := gjson.DecodeTo([]byte(raw), &ids); err == nil && len(ids) > 0 {
return compactStringSlice(ids)
}
return compactStringSlice([]string{raw})
}
func compactStringSlice(values []string) []string {
res := make([]string, 0, len(values))
seen := make(map[string]struct{}, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
res = append(res, value)
}
return res
}
@@ -0,0 +1,117 @@
// Package sys
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sys
import (
"context"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
type sSysImTenantApplication struct{}
func NewSysImTenantApplication() *sSysImTenantApplication {
return &sSysImTenantApplication{}
}
func init() {
service.RegisterSysImTenantApplication(NewSysImTenantApplication())
}
// List 获取企业注册申请列表
func (s *sSysImTenantApplication) List(ctx context.Context, in *sysin.ImTenantApplicationListInp) (list []*sysin.ImTenantApplicationListModel, totalCount int, err error) {
if err = in.Filter(ctx); err != nil {
return
}
mod := s.listModel(ctx)
mod = s.applyWhere(mod, in)
mod = mod.Page(in.Page, in.PerPage).OrderDesc("created_at").OrderDesc("id")
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
return nil, 0, gerror.Wrap(err, "获取企业注册申请列表失败,请稍后重试!")
}
return
}
func (s *sSysImTenantApplication) listModel(ctx context.Context) *gdb.Model {
return g.DB().Model("im_tenant_applications").Safe().Ctx(ctx).Fields(
"id",
"application_id",
"credit_code",
"enterprise_email",
"license_file_id",
"tenant_code_hint",
"status",
"estimated_review_hour",
"submitted_at",
"created_at",
"updated_at",
)
}
func (s *sSysImTenantApplication) applyWhere(mod *gdb.Model, in *sysin.ImTenantApplicationListInp) *gdb.Model {
if in.ApplicationId != "" {
mod = mod.WhereLike("application_id", "%"+in.ApplicationId+"%")
}
if in.CreditCode != "" {
mod = mod.WhereLike("credit_code", "%"+in.CreditCode+"%")
}
if in.EnterpriseEmail != "" {
mod = mod.WhereLike("enterprise_email", "%"+in.EnterpriseEmail+"%")
}
if in.TenantCodeHint != "" {
mod = mod.WhereLike("tenant_code_hint", "%"+in.TenantCodeHint+"%")
}
if in.Status != "" {
mod = mod.Where("status", in.Status)
}
if in.Keyword != "" {
like := "%" + in.Keyword + "%"
mod = mod.Where("(application_id LIKE ? OR credit_code LIKE ? OR enterprise_email LIKE ? OR tenant_code_hint LIKE ?)", like, like, like, like)
}
if len(in.CreatedAt) == 2 {
mod = mod.WhereBetween("created_at", in.CreatedAt[0], in.CreatedAt[1])
}
return mod
}
// View 获取企业注册申请指定信息
func (s *sSysImTenantApplication) View(ctx context.Context, in *sysin.ImTenantApplicationViewInp) (res *sysin.ImTenantApplicationViewModel, err error) {
if err = in.Filter(ctx); err != nil {
return
}
if err = g.DB().Model("im_tenant_applications").Safe().Ctx(ctx).
Fields(
"id",
"application_id",
"credit_code",
"enterprise_email",
"license_file_id",
"tenant_code_hint",
"CAST(client_context AS CHAR) client_context",
"idempotency_key",
"status",
"estimated_review_hour",
"submitted_at",
"created_at",
"updated_at",
).
Where("id", in.Id).
Scan(&res); err != nil {
return nil, gerror.Wrap(err, "获取企业注册申请信息失败,请稍后重试!")
}
if res == nil {
return
}
res.LicenseFile, err = firstImPublicUpload(ctx, res.LicenseFileId)
return
}
@@ -0,0 +1,111 @@
// Package sys
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sys
import (
"context"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
type sSysImTenantEntryFeedback struct{}
func NewSysImTenantEntryFeedback() *sSysImTenantEntryFeedback {
return &sSysImTenantEntryFeedback{}
}
func init() {
service.RegisterSysImTenantEntryFeedback(NewSysImTenantEntryFeedback())
}
// List 获取企业入口反馈列表
func (s *sSysImTenantEntryFeedback) List(ctx context.Context, in *sysin.ImTenantEntryFeedbackListInp) (list []*sysin.ImTenantEntryFeedbackListModel, totalCount int, err error) {
if err = in.Filter(ctx); err != nil {
return
}
mod := s.listModel(ctx)
mod = s.applyWhere(mod, in)
mod = mod.Page(in.Page, in.PerPage).OrderDesc("created_at").OrderDesc("id")
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
return nil, 0, gerror.Wrap(err, "获取企业入口反馈列表失败,请稍后重试!")
}
return
}
func (s *sSysImTenantEntryFeedback) listModel(ctx context.Context) *gdb.Model {
return g.DB().Model("im_tenant_entry_feedbacks").Safe().Ctx(ctx).Fields(
"id",
"feedback_id",
"description",
"contact",
"tenant_code_hint",
"status",
"submitted_at",
"created_at",
"updated_at",
)
}
func (s *sSysImTenantEntryFeedback) applyWhere(mod *gdb.Model, in *sysin.ImTenantEntryFeedbackListInp) *gdb.Model {
if in.FeedbackId != "" {
mod = mod.WhereLike("feedback_id", "%"+in.FeedbackId+"%")
}
if in.Contact != "" {
mod = mod.WhereLike("contact", "%"+in.Contact+"%")
}
if in.TenantCodeHint != "" {
mod = mod.WhereLike("tenant_code_hint", "%"+in.TenantCodeHint+"%")
}
if in.Status != "" {
mod = mod.Where("status", in.Status)
}
if in.Keyword != "" {
like := "%" + in.Keyword + "%"
mod = mod.Where("(feedback_id LIKE ? OR description LIKE ? OR contact LIKE ? OR tenant_code_hint LIKE ?)", like, like, like, like)
}
if len(in.CreatedAt) == 2 {
mod = mod.WhereBetween("created_at", in.CreatedAt[0], in.CreatedAt[1])
}
return mod
}
// View 获取企业入口反馈指定信息
func (s *sSysImTenantEntryFeedback) View(ctx context.Context, in *sysin.ImTenantEntryFeedbackViewInp) (res *sysin.ImTenantEntryFeedbackViewModel, err error) {
if err = in.Filter(ctx); err != nil {
return
}
if err = g.DB().Model("im_tenant_entry_feedbacks").Safe().Ctx(ctx).
Fields(
"id",
"feedback_id",
"description",
"contact",
"tenant_code_hint",
"CAST(screenshot_file_ids AS CHAR) screenshot_file_ids",
"CAST(client_context AS CHAR) client_context",
"idempotency_key",
"status",
"submitted_at",
"created_at",
"updated_at",
).
Where("id", in.Id).
Scan(&res); err != nil {
return nil, gerror.Wrap(err, "获取企业入口反馈信息失败,请稍后重试!")
}
if res == nil {
return
}
res.ScreenshotFiles, err = loadImPublicUploads(ctx, parseJSONStringSlice(res.ScreenshotFileIds))
return
}
@@ -0,0 +1,23 @@
// Package sysin
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sysin
import "github.com/gogf/gf/v2/os/gtime"
// ImPublicUploadModel 企业注册入口公共上传文件
type ImPublicUploadModel struct {
Id int64 `json:"id" dc:"ID"`
FileId string `json:"fileId" dc:"文件ID"`
Purpose string `json:"purpose" dc:"用途"`
MimeType string `json:"mimeType" dc:"MIME类型"`
Size int64 `json:"size" dc:"文件大小"`
Etag string `json:"etag" dc:"ETag"`
Location string `json:"location" dc:"存储位置"`
Url string `json:"url" dc:"访问地址"`
ExpiresAt *gtime.Time `json:"expiresAt" dc:"过期时间"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
UploadedAt *gtime.Time `json:"uploadedAt" dc:"上传完成时间"`
}
@@ -0,0 +1,63 @@
// Package sysin
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sysin
import (
"context"
"hotgo/internal/model/input/form"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
// ImTenantApplicationListInp 获取企业注册申请列表
type ImTenantApplicationListInp struct {
form.PageReq
ApplicationId string `json:"applicationId" dc:"申请ID"`
CreditCode string `json:"creditCode" dc:"统一社会信用代码"`
EnterpriseEmail string `json:"enterpriseEmail" dc:"企业邮箱"`
TenantCodeHint string `json:"tenantCodeHint" dc:"商户编码线索"`
Status string `json:"status" dc:"状态"`
Keyword string `json:"keyword" dc:"关键词"`
CreatedAt []*gtime.Time `json:"createdAt" dc:"创建时间"`
}
func (in *ImTenantApplicationListInp) Filter(ctx context.Context) (err error) {
return
}
type ImTenantApplicationListModel struct {
Id int64 `json:"id" dc:"ID"`
ApplicationId string `json:"applicationId" dc:"申请ID"`
CreditCode string `json:"creditCode" dc:"统一社会信用代码"`
EnterpriseEmail string `json:"enterpriseEmail" dc:"企业邮箱"`
LicenseFileId string `json:"licenseFileId" dc:"营业执照文件ID"`
TenantCodeHint string `json:"tenantCodeHint" dc:"商户编码线索"`
Status string `json:"status" dc:"状态"`
EstimatedReviewHour int `json:"estimatedReviewHour" dc:"预计审核小时"`
SubmittedAt *gtime.Time `json:"submittedAt" dc:"提交时间"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
}
// ImTenantApplicationViewInp 获取企业注册申请详情
type ImTenantApplicationViewInp struct {
Id int64 `json:"id" v:"required#ID不能为空" dc:"ID"`
}
func (in *ImTenantApplicationViewInp) Filter(ctx context.Context) (err error) {
if in.Id <= 0 {
return gerror.New("ID不能为空")
}
return
}
type ImTenantApplicationViewModel struct {
ImTenantApplicationListModel
ClientContext string `json:"clientContext" dc:"客户端上下文"`
IdempotencyKey string `json:"idempotencyKey" dc:"幂等键"`
LicenseFile *ImPublicUploadModel `json:"licenseFile" dc:"营业执照文件"`
}
@@ -0,0 +1,61 @@
// Package sysin
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2026 HotGo
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package sysin
import (
"context"
"hotgo/internal/model/input/form"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
// ImTenantEntryFeedbackListInp 获取企业入口反馈列表
type ImTenantEntryFeedbackListInp struct {
form.PageReq
FeedbackId string `json:"feedbackId" dc:"反馈ID"`
Contact string `json:"contact" dc:"联系方式"`
TenantCodeHint string `json:"tenantCodeHint" dc:"商户编码线索"`
Status string `json:"status" dc:"状态"`
Keyword string `json:"keyword" dc:"关键词"`
CreatedAt []*gtime.Time `json:"createdAt" dc:"创建时间"`
}
func (in *ImTenantEntryFeedbackListInp) Filter(ctx context.Context) (err error) {
return
}
type ImTenantEntryFeedbackListModel struct {
Id int64 `json:"id" dc:"ID"`
FeedbackId string `json:"feedbackId" dc:"反馈ID"`
Description string `json:"description" dc:"问题描述"`
Contact string `json:"contact" dc:"联系方式"`
TenantCodeHint string `json:"tenantCodeHint" dc:"商户编码线索"`
Status string `json:"status" dc:"状态"`
SubmittedAt *gtime.Time `json:"submittedAt" dc:"提交时间"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
}
// ImTenantEntryFeedbackViewInp 获取企业入口反馈详情
type ImTenantEntryFeedbackViewInp struct {
Id int64 `json:"id" v:"required#ID不能为空" dc:"ID"`
}
func (in *ImTenantEntryFeedbackViewInp) Filter(ctx context.Context) (err error) {
if in.Id <= 0 {
return gerror.New("ID不能为空")
}
return
}
type ImTenantEntryFeedbackViewModel struct {
ImTenantEntryFeedbackListModel
ScreenshotFileIds string `json:"screenshotFileIds" dc:"截图文件ID JSON"`
ClientContext string `json:"clientContext" dc:"客户端上下文"`
IdempotencyKey string `json:"idempotencyKey" dc:"幂等键"`
ScreenshotFiles []*ImPublicUploadModel `json:"screenshotFiles" dc:"截图文件"`
}
@@ -0,0 +1,7 @@
package genrouter
import "hotgo/internal/controller/admin/sys"
func init() {
LoginRequiredRouter = append(LoginRequiredRouter, sys.ImTenantApplication) // 企业注册申请
}
@@ -0,0 +1,7 @@
package genrouter
import "hotgo/internal/controller/admin/sys"
func init() {
LoginRequiredRouter = append(LoginRequiredRouter, sys.ImTenantEntryFeedback) // 企业入口反馈
}
+91 -55
View File
@@ -350,6 +350,18 @@ type (
// Status 更新IM举报反馈工单状态
Status(ctx context.Context, in *sysin.ImWorkOrderStatusInp) (err error)
}
ISysImTenantApplication interface {
// List 获取企业注册申请列表
List(ctx context.Context, in *sysin.ImTenantApplicationListInp) (list []*sysin.ImTenantApplicationListModel, totalCount int, err error)
// View 获取企业注册申请指定信息
View(ctx context.Context, in *sysin.ImTenantApplicationViewInp) (res *sysin.ImTenantApplicationViewModel, err error)
}
ISysImTenantEntryFeedback interface {
// List 获取企业入口反馈列表
List(ctx context.Context, in *sysin.ImTenantEntryFeedbackListInp) (list []*sysin.ImTenantEntryFeedbackListModel, totalCount int, err error)
// View 获取企业入口反馈指定信息
View(ctx context.Context, in *sysin.ImTenantEntryFeedbackViewInp) (res *sysin.ImTenantEntryFeedbackViewModel, err error)
}
ISysImOutbox interface {
// List 获取IM Outbox列表
List(ctx context.Context, in *sysin.ImOutboxListInp) (list []*sysin.ImOutboxListModel, totalCount int, err error)
@@ -788,61 +800,63 @@ type (
)
var (
localSysAddons ISysAddons
localSysAddonsConfig ISysAddonsConfig
localSysAttachment ISysAttachment
localSysBlacklist ISysBlacklist
localSysConfig ISysConfig
localSysCron ISysCron
localSysCronGroup ISysCronGroup
localSysCurdDemo ISysCurdDemo
localSysCultivateFlower ISysCultivateFlower
localSysCultivateContent ISysCultivateContent
localSysImTenant ISysImTenant
localSysImMessageEncryption ISysImMessageEncryption
localSysImRegistration ISysImRegistration
localSysImInvite ISysImInvite
localSysImUser ISysImUser
localSysImGroup ISysImGroup
localSysImRealname ISysImRealname
localSysImWorkOrder ISysImWorkOrder
localSysImOutbox ISysImOutbox
localSysImOrg ISysImOrg
localSysImFile ISysImFile
localSysImFilePolicy ISysImFilePolicy
localSysImUserPolicyGroup ISysImUserPolicyGroup
localSysImSensitiveWord ISysImSensitiveWord
localSysImSecurityPolicy ISysImSecurityPolicy
localSysImDashboard ISysImDashboard
localSysImPrivateMessage ISysImPrivateMessage
localSysImGroupMessage ISysImGroupMessage
localSysImUserLoginHistory ISysImUserLoginHistory
localSysImLoginFailureLog ISysImLoginFailureLog
localSysImExternalConfig ISysImExternalConfig
localSysImNotification ISysImNotification
localSysImAppRelease ISysImAppRelease
localSysImAbuseReason ISysImAbuseReason
localSysImMailboxConfig ISysImMailboxConfig
localSysImFeedbackConfig ISysImFeedbackConfig
localSysImWorkbench ISysImWorkbench
localSysImTaskDashboard ISysImTaskDashboard
localSysImAuditLog ISysImAuditLog
localSysImKafkaAudit ISysImKafkaAudit
localSysImKafkaCommand ISysImKafkaCommand
localSysImDeadLetter ISysImDeadLetter
localSysDictData ISysDictData
localSysDictType ISysDictType
localSysEmsLog ISysEmsLog
localSysGenCodes ISysGenCodes
localSysLog ISysLog
localSysLoginLog ISysLoginLog
localSysNormalTreeDemo ISysNormalTreeDemo
localSysOptionTreeDemo ISysOptionTreeDemo
localSysProvinces ISysProvinces
localSysServeLicense ISysServeLicense
localSysServeLog ISysServeLog
localSysSmsLog ISysSmsLog
localSysTestCategory ISysTestCategory
localSysAddons ISysAddons
localSysAddonsConfig ISysAddonsConfig
localSysAttachment ISysAttachment
localSysBlacklist ISysBlacklist
localSysConfig ISysConfig
localSysCron ISysCron
localSysCronGroup ISysCronGroup
localSysCurdDemo ISysCurdDemo
localSysCultivateFlower ISysCultivateFlower
localSysCultivateContent ISysCultivateContent
localSysImTenant ISysImTenant
localSysImMessageEncryption ISysImMessageEncryption
localSysImRegistration ISysImRegistration
localSysImInvite ISysImInvite
localSysImUser ISysImUser
localSysImGroup ISysImGroup
localSysImRealname ISysImRealname
localSysImWorkOrder ISysImWorkOrder
localSysImTenantApplication ISysImTenantApplication
localSysImTenantEntryFeedback ISysImTenantEntryFeedback
localSysImOutbox ISysImOutbox
localSysImOrg ISysImOrg
localSysImFile ISysImFile
localSysImFilePolicy ISysImFilePolicy
localSysImUserPolicyGroup ISysImUserPolicyGroup
localSysImSensitiveWord ISysImSensitiveWord
localSysImSecurityPolicy ISysImSecurityPolicy
localSysImDashboard ISysImDashboard
localSysImPrivateMessage ISysImPrivateMessage
localSysImGroupMessage ISysImGroupMessage
localSysImUserLoginHistory ISysImUserLoginHistory
localSysImLoginFailureLog ISysImLoginFailureLog
localSysImExternalConfig ISysImExternalConfig
localSysImNotification ISysImNotification
localSysImAppRelease ISysImAppRelease
localSysImAbuseReason ISysImAbuseReason
localSysImMailboxConfig ISysImMailboxConfig
localSysImFeedbackConfig ISysImFeedbackConfig
localSysImWorkbench ISysImWorkbench
localSysImTaskDashboard ISysImTaskDashboard
localSysImAuditLog ISysImAuditLog
localSysImKafkaAudit ISysImKafkaAudit
localSysImKafkaCommand ISysImKafkaCommand
localSysImDeadLetter ISysImDeadLetter
localSysDictData ISysDictData
localSysDictType ISysDictType
localSysEmsLog ISysEmsLog
localSysGenCodes ISysGenCodes
localSysLog ISysLog
localSysLoginLog ISysLoginLog
localSysNormalTreeDemo ISysNormalTreeDemo
localSysOptionTreeDemo ISysOptionTreeDemo
localSysProvinces ISysProvinces
localSysServeLicense ISysServeLicense
localSysServeLog ISysServeLog
localSysSmsLog ISysSmsLog
localSysTestCategory ISysTestCategory
)
func SysAddons() ISysAddons {
@@ -1041,6 +1055,28 @@ func RegisterSysImWorkOrder(i ISysImWorkOrder) {
localSysImWorkOrder = i
}
func SysImTenantApplication() ISysImTenantApplication {
if localSysImTenantApplication == nil {
panic("implement not found for interface ISysImTenantApplication, forgot register?")
}
return localSysImTenantApplication
}
func RegisterSysImTenantApplication(i ISysImTenantApplication) {
localSysImTenantApplication = i
}
func SysImTenantEntryFeedback() ISysImTenantEntryFeedback {
if localSysImTenantEntryFeedback == nil {
panic("implement not found for interface ISysImTenantEntryFeedback, forgot register?")
}
return localSysImTenantEntryFeedback
}
func RegisterSysImTenantEntryFeedback(i ISysImTenantEntryFeedback) {
localSysImTenantEntryFeedback = i
}
func SysImOutbox() ISysImOutbox {
if localSysImOutbox == nil {
panic("implement not found for interface ISysImOutbox, forgot register?")
@@ -90,6 +90,38 @@ LEFT JOIN `hg_admin_role_menu` existing ON existing.`role_id` = rm.`role_id` AND
WHERE btn.`name` IN ('imSecurityPolicyView','imSecurityPolicySave','imSecurityPolicyPublish','imSecurityPolicyRestoreDefault','imSecurityPolicyLoginLockoutConfig')
AND existing.`menu_id` IS NULL;
INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT @settingsId, '企业注册', 'imTenantApplicationIndex', 'tenant-application/index', '', 2, '', '/imTenantApplication/list', '', '/imTenantApplication/index', 1, 'imSystemSettings', 0, 0, '', 0, 0, 0, 3, CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' '), 80, '', 1, @now, @now
WHERE @settingsId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name`='imTenantApplicationIndex');
INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT @settingsId, '企业反馈', 'imTenantEntryFeedbackIndex', 'tenant-entry-feedback/index', '', 2, '', '/imTenantEntryFeedback/list', '', '/imTenantEntryFeedback/index', 1, 'imSystemSettings', 0, 0, '', 0, 0, 0, 3, CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' '), 90, '', 1, @now, @now
WHERE @settingsId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name`='imTenantEntryFeedbackIndex');
SET @tenantApplicationId := (SELECT `id` FROM `hg_admin_menu` WHERE `name`='imTenantApplicationIndex' LIMIT 1);
SET @tenantEntryFeedbackId := (SELECT `id` FROM `hg_admin_menu` WHERE `name`='imTenantEntryFeedbackIndex' LIMIT 1);
UPDATE `hg_admin_menu` SET `pid`=@settingsId, `title`='企业注册', `path`='tenant-application/index', `permissions`='/imTenantApplication/list', `component`='/imTenantApplication/index', `active_menu`='imSystemSettings', `type`=2, `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' '), `sort`=80, `hidden`=0, `status`=1, `updated_at`=@now WHERE `id`=@tenantApplicationId;
UPDATE `hg_admin_menu` SET `pid`=@settingsId, `title`='企业反馈', `path`='tenant-entry-feedback/index', `permissions`='/imTenantEntryFeedback/list', `component`='/imTenantEntryFeedback/index', `active_menu`='imSystemSettings', `type`=2, `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' '), `sort`=90, `hidden`=0, `status`=1, `updated_at`=@now WHERE `id`=@tenantEntryFeedbackId;
INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT @tenantApplicationId, '企业注册详情', 'imTenantApplicationView', '', '', 3, '', '/imTenantApplication/view', '', '', 1, '', 0, 0, '', 0, 1, 0, 4, CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' tr_', @tenantApplicationId, ' '), 10, '', 1, @now, @now
WHERE @tenantApplicationId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name`='imTenantApplicationView');
INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT @tenantEntryFeedbackId, '企业反馈详情', 'imTenantEntryFeedbackView', '', '', 3, '', '/imTenantEntryFeedback/view', '', '', 1, '', 0, 0, '', 0, 1, 0, 4, CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' tr_', @tenantEntryFeedbackId, ' '), 10, '', 1, @now, @now
WHERE @tenantEntryFeedbackId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name`='imTenantEntryFeedbackView');
UPDATE `hg_admin_menu` SET `pid`=@tenantApplicationId, `title`='企业注册详情', `permissions`='/imTenantApplication/view', `type`=3, `level`=4, `tree`=CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' tr_', @tenantApplicationId, ' '), `hidden`=1, `status`=1, `updated_at`=@now WHERE `name`='imTenantApplicationView';
UPDATE `hg_admin_menu` SET `pid`=@tenantEntryFeedbackId, `title`='企业反馈详情', `permissions`='/imTenantEntryFeedback/view', `type`=3, `level`=4, `tree`=CONCAT('tr_', @imRootId, ' tr_', @settingsId, ' tr_', @tenantEntryFeedbackId, ' '), `hidden`=1, `status`=1, `updated_at`=@now WHERE `name`='imTenantEntryFeedbackView';
INSERT INTO `hg_admin_role_menu` (`role_id`, `menu_id`)
SELECT DISTINCT rm.`role_id`, menu.`id`
FROM `hg_admin_role_menu` rm
JOIN `hg_admin_menu` menu ON menu.`name` IN ('imTenantApplicationIndex', 'imTenantApplicationView', 'imTenantEntryFeedbackIndex', 'imTenantEntryFeedbackView')
LEFT JOIN `hg_admin_role_menu` existing ON existing.`role_id`=rm.`role_id` AND existing.`menu_id`=menu.`id`
WHERE rm.`menu_id`=@settingsId AND existing.`menu_id` IS NULL;
-- Remove one-page wrapper directories after their page has been moved.
UPDATE `hg_admin_menu` SET `status`=2, `hidden`=1, `updated_at`=@now
WHERE `name` IN ('imUser', 'imGroup', 'imTenant', 'imRealname', 'imWorkOrder', 'imAuditLog', 'imOutbox');
+19
View File
@@ -0,0 +1,19 @@
import { http } from '@/utils/http/axios';
// 获取企业注册申请列表
export function List(params) {
return http.request({
url: '/imTenantApplication/list',
method: 'get',
params,
});
}
// 获取企业注册申请详情
export function View(params) {
return http.request({
url: '/imTenantApplication/view',
method: 'GET',
params,
});
}
@@ -0,0 +1,19 @@
import { http } from '@/utils/http/axios';
// 获取企业反馈列表
export function List(params) {
return http.request({
url: '/imTenantEntryFeedback/list',
method: 'get',
params,
});
}
// 获取企业反馈详情
export function View(params) {
return http.request({
url: '/imTenantEntryFeedback/view',
method: 'GET',
params,
});
}
@@ -0,0 +1,77 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="企业注册" />
</div>
<n-card :bordered="false" class="proCard">
<BasicForm
ref="searchFormRef"
@register="register"
@submit="reloadTable"
@reset="reloadTable"
@keyup.enter="reloadTable"
/>
<BasicTable
ref="actionRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
:actionColumn="actionColumn"
:scroll-x="scrollX"
:resizeHeightOffset="-10000"
/>
</n-card>
<ViewModal ref="viewRef" />
</div>
</template>
<script lang="ts" setup>
import { computed, h, reactive, ref } from 'vue';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
import { List } from '@/api/imTenantApplication';
import { columns, schemas, State } from './model';
import { adaTableScrollX } from '@/utils/hotgo';
import ViewModal from './view.vue';
const actionRef = ref();
const searchFormRef = ref<any>({});
const viewRef = ref();
const actionColumn = reactive({
width: 100,
title: '操作',
key: 'action',
fixed: 'right',
render(record: State) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '详情',
onClick: () => viewRef.value.openModal(record),
auth: ['/imTenantApplication/view'],
},
],
});
},
});
const scrollX = computed(() => adaTableScrollX(columns, actionColumn.width));
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 100,
schemas,
});
const loadDataTable = async (res) => {
return await List({ ...searchFormRef.value?.formModel, ...res });
};
function reloadTable() {
actionRef.value?.reload();
}
</script>
<style lang="less" scoped></style>
+128
View File
@@ -0,0 +1,128 @@
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { defRangeShortcuts } from '@/utils/dateUtil';
export interface PublicUpload {
id: number;
fileId: string;
purpose: string;
mimeType: string;
size: number;
etag: string;
location: string;
url: string;
expiresAt: string;
createdAt: string;
uploadedAt: string;
}
export class State {
public id = 0;
public applicationId = '';
public creditCode = '';
public enterpriseEmail = '';
public licenseFileId = '';
public tenantCodeHint = '';
public status = '';
public estimatedReviewHour = 0;
public submittedAt = '';
public createdAt = '';
public updatedAt = '';
public clientContext = '';
public idempotencyKey = '';
public licenseFile: PublicUpload | null = null;
constructor(state?: Partial<State>) {
if (state) Object.assign(this, state);
}
}
export function newState(state: State | Record<string, any> | null): State {
if (state !== null) {
if (state instanceof State) return cloneDeep(state);
return new State(state);
}
return new State();
}
export function statusLabel(status: string) {
const map: Record<string, string> = {
received: '已接收',
pending_review: '待审核',
approved: '已通过',
rejected: '已拒绝',
closed: '已关闭',
};
return map[status] || status || '-';
}
export const schemas = ref<FormSchema[]>([
{
field: 'applicationId',
component: 'NInput',
label: '申请ID',
componentProps: { placeholder: '请输入申请ID' },
},
{
field: 'creditCode',
component: 'NInput',
label: '信用代码',
componentProps: { placeholder: '请输入统一社会信用代码' },
},
{
field: 'enterpriseEmail',
component: 'NInput',
label: '企业邮箱',
componentProps: { placeholder: '请输入企业邮箱' },
},
{
field: 'tenantCodeHint',
component: 'NInput',
label: '商户线索',
componentProps: { placeholder: '请输入商户编码线索' },
},
{
field: 'status',
component: 'NInput',
label: '状态',
componentProps: { placeholder: '请输入状态' },
},
{
field: 'keyword',
component: 'NInput',
label: '关键词',
componentProps: { placeholder: '请输入申请ID/信用代码/邮箱' },
},
{
field: 'createdAt',
component: 'NDatePicker',
label: '创建时间',
componentProps: {
type: 'datetimerange',
clearable: true,
shortcuts: defRangeShortcuts(),
},
},
]);
export const columns = [
{ title: 'ID', key: 'id', align: 'left', width: 80 },
{ title: '申请ID', key: 'applicationId', align: 'left', width: 210 },
{ title: '统一社会信用代码', key: 'creditCode', align: 'left', width: 190 },
{ title: '企业邮箱', key: 'enterpriseEmail', align: 'left', width: 210 },
{ title: '营业执照文件', key: 'licenseFileId', align: 'left', width: 180 },
{ title: '商户线索', key: 'tenantCodeHint', align: 'left', width: 140 },
{
title: '状态',
key: 'status',
align: 'left',
width: 120,
render(row: State) {
return statusLabel(row.status);
},
},
{ title: '预计审核小时', key: 'estimatedReviewHour', align: 'left', width: 130 },
{ title: '提交时间', key: 'submittedAt', align: 'left', width: 180 },
{ title: '创建时间', key: 'createdAt', align: 'left', width: 180 },
];
+125
View File
@@ -0,0 +1,125 @@
<template>
<div>
<n-modal
v-model:show="showModal"
:mask-closable="false"
:show-icon="false"
preset="dialog"
transform-origin="center"
:title="'企业注册详情 ' + formValue.applicationId"
:style="{ width: dialogWidth }"
>
<n-scrollbar class="tenant-entry-scrollbar">
<n-spin :show="loading" description="请稍候...">
<n-descriptions label-placement="left" bordered :column="settingStore.isMobile ? 1 : 2">
<n-descriptions-item label="申请ID">{{ formValue.applicationId }}</n-descriptions-item>
<n-descriptions-item label="状态">{{
statusLabel(formValue.status)
}}</n-descriptions-item>
<n-descriptions-item label="统一社会信用代码">{{
formValue.creditCode || '-'
}}</n-descriptions-item>
<n-descriptions-item label="企业邮箱">{{
formValue.enterpriseEmail || '-'
}}</n-descriptions-item>
<n-descriptions-item label="商户线索">{{
formValue.tenantCodeHint || '-'
}}</n-descriptions-item>
<n-descriptions-item label="预计审核小时">{{
formValue.estimatedReviewHour || '-'
}}</n-descriptions-item>
<n-descriptions-item label="提交时间">{{
formValue.submittedAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="创建时间">{{
formValue.createdAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="修改时间">{{
formValue.updatedAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="营业执照文件ID">{{
formValue.licenseFileId || '-'
}}</n-descriptions-item>
<n-descriptions-item label="营业执照" :span="2">
<div v-if="formValue.licenseFile?.url" class="image-list">
<n-image
object-fit="cover"
:src="formValue.licenseFile.url"
:alt="formValue.licenseFile.fileId"
class="entry-image"
/>
<n-text depth="3">{{ formValue.licenseFile.location }}</n-text>
</div>
<n-empty v-else size="small" description="暂无图片" />
</n-descriptions-item>
<n-descriptions-item label="客户端上下文" :span="2">
<n-code :code="formValue.clientContext || '-'" language="json" word-wrap />
</n-descriptions-item>
<n-descriptions-item label="幂等键" :span="2">{{
formValue.idempotencyKey || '-'
}}</n-descriptions-item>
</n-descriptions>
</n-spin>
</n-scrollbar>
<template #action>
<n-space>
<n-button @click="closeForm">关闭</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { View } from '@/api/imTenantApplication';
import { newState, State, statusLabel } from './model';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { adaModalWidth } from '@/utils/hotgo';
const settingStore = useProjectSettingStore();
const loading = ref(false);
const showModal = ref(false);
const formValue = ref<State>(newState(null));
const dialogWidth = computed(() => adaModalWidth(980));
function closeForm() {
showModal.value = false;
loading.value = false;
}
function openModal(state: State) {
showModal.value = true;
loading.value = true;
View({ id: state.id })
.then((res) => {
formValue.value = newState(res);
})
.finally(() => {
loading.value = false;
});
}
defineExpose({
openModal,
});
</script>
<style lang="less" scoped>
.tenant-entry-scrollbar {
max-height: 87vh;
padding-right: 20px;
}
.image-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.entry-image {
width: 180px;
height: 120px;
border-radius: 6px;
}
</style>
@@ -0,0 +1,77 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="企业反馈" />
</div>
<n-card :bordered="false" class="proCard">
<BasicForm
ref="searchFormRef"
@register="register"
@submit="reloadTable"
@reset="reloadTable"
@keyup.enter="reloadTable"
/>
<BasicTable
ref="actionRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
:actionColumn="actionColumn"
:scroll-x="scrollX"
:resizeHeightOffset="-10000"
/>
</n-card>
<ViewModal ref="viewRef" />
</div>
</template>
<script lang="ts" setup>
import { computed, h, reactive, ref } from 'vue';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
import { List } from '@/api/imTenantEntryFeedback';
import { columns, schemas, State } from './model';
import { adaTableScrollX } from '@/utils/hotgo';
import ViewModal from './view.vue';
const actionRef = ref();
const searchFormRef = ref<any>({});
const viewRef = ref();
const actionColumn = reactive({
width: 100,
title: '操作',
key: 'action',
fixed: 'right',
render(record: State) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '详情',
onClick: () => viewRef.value.openModal(record),
auth: ['/imTenantEntryFeedback/view'],
},
],
});
},
});
const scrollX = computed(() => adaTableScrollX(columns, actionColumn.width));
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 100,
schemas,
});
const loadDataTable = async (res) => {
return await List({ ...searchFormRef.value?.formModel, ...res });
};
function reloadTable() {
actionRef.value?.reload();
}
</script>
<style lang="less" scoped></style>
@@ -0,0 +1,107 @@
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { defRangeShortcuts } from '@/utils/dateUtil';
import { PublicUpload } from '@/views/imTenantApplication/model';
export class State {
public id = 0;
public feedbackId = '';
public description = '';
public contact = '';
public tenantCodeHint = '';
public status = '';
public submittedAt = '';
public createdAt = '';
public updatedAt = '';
public screenshotFileIds = '';
public clientContext = '';
public idempotencyKey = '';
public screenshotFiles: PublicUpload[] = [];
constructor(state?: Partial<State>) {
if (state) Object.assign(this, state);
}
}
export function newState(state: State | Record<string, any> | null): State {
if (state !== null) {
if (state instanceof State) return cloneDeep(state);
return new State(state);
}
return new State();
}
export function statusLabel(status: string) {
const map: Record<string, string> = {
received: '已接收',
pending_review: '待处理',
processing: '处理中',
resolved: '已解决',
rejected: '已拒绝',
closed: '已关闭',
};
return map[status] || status || '-';
}
export const schemas = ref<FormSchema[]>([
{
field: 'feedbackId',
component: 'NInput',
label: '反馈ID',
componentProps: { placeholder: '请输入反馈ID' },
},
{
field: 'contact',
component: 'NInput',
label: '联系方式',
componentProps: { placeholder: '请输入联系方式' },
},
{
field: 'tenantCodeHint',
component: 'NInput',
label: '商户线索',
componentProps: { placeholder: '请输入商户编码线索' },
},
{
field: 'status',
component: 'NInput',
label: '状态',
componentProps: { placeholder: '请输入状态' },
},
{
field: 'keyword',
component: 'NInput',
label: '关键词',
componentProps: { placeholder: '请输入反馈ID/描述/联系方式' },
},
{
field: 'createdAt',
component: 'NDatePicker',
label: '创建时间',
componentProps: {
type: 'datetimerange',
clearable: true,
shortcuts: defRangeShortcuts(),
},
},
]);
export const columns = [
{ title: 'ID', key: 'id', align: 'left', width: 80 },
{ title: '反馈ID', key: 'feedbackId', align: 'left', width: 210 },
{ title: '问题描述', key: 'description', align: 'left', width: 320 },
{ title: '联系方式', key: 'contact', align: 'left', width: 170 },
{ title: '商户线索', key: 'tenantCodeHint', align: 'left', width: 140 },
{
title: '状态',
key: 'status',
align: 'left',
width: 120,
render(row: State) {
return statusLabel(row.status);
},
},
{ title: '提交时间', key: 'submittedAt', align: 'left', width: 180 },
{ title: '创建时间', key: 'createdAt', align: 'left', width: 180 },
];
@@ -0,0 +1,131 @@
<template>
<div>
<n-modal
v-model:show="showModal"
:mask-closable="false"
:show-icon="false"
preset="dialog"
transform-origin="center"
:title="'企业反馈详情 ' + formValue.feedbackId"
:style="{ width: dialogWidth }"
>
<n-scrollbar class="tenant-entry-scrollbar">
<n-spin :show="loading" description="请稍候...">
<n-descriptions label-placement="left" bordered :column="settingStore.isMobile ? 1 : 2">
<n-descriptions-item label="反馈ID">{{ formValue.feedbackId }}</n-descriptions-item>
<n-descriptions-item label="状态">{{
statusLabel(formValue.status)
}}</n-descriptions-item>
<n-descriptions-item label="联系方式">{{
formValue.contact || '-'
}}</n-descriptions-item>
<n-descriptions-item label="商户线索">{{
formValue.tenantCodeHint || '-'
}}</n-descriptions-item>
<n-descriptions-item label="提交时间">{{
formValue.submittedAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="创建时间">{{
formValue.createdAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="修改时间">{{
formValue.updatedAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="问题描述" :span="2">{{
formValue.description || '-'
}}</n-descriptions-item>
<n-descriptions-item label="截图" :span="2">
<n-image-group v-if="formValue.screenshotFiles?.length">
<n-space>
<div
v-for="file in formValue.screenshotFiles"
:key="file.fileId"
class="image-list"
>
<n-image
object-fit="cover"
:src="file.url"
:alt="file.fileId"
class="entry-image"
/>
<n-text depth="3">{{ file.location }}</n-text>
</div>
</n-space>
</n-image-group>
<n-empty v-else size="small" description="暂无图片" />
</n-descriptions-item>
<n-descriptions-item label="截图文件ID" :span="2">
<n-code :code="formValue.screenshotFileIds || '-'" language="json" word-wrap />
</n-descriptions-item>
<n-descriptions-item label="客户端上下文" :span="2">
<n-code :code="formValue.clientContext || '-'" language="json" word-wrap />
</n-descriptions-item>
<n-descriptions-item label="幂等键" :span="2">{{
formValue.idempotencyKey || '-'
}}</n-descriptions-item>
</n-descriptions>
</n-spin>
</n-scrollbar>
<template #action>
<n-space>
<n-button @click="closeForm">关闭</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { View } from '@/api/imTenantEntryFeedback';
import { newState, State, statusLabel } from './model';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { adaModalWidth } from '@/utils/hotgo';
const settingStore = useProjectSettingStore();
const loading = ref(false);
const showModal = ref(false);
const formValue = ref<State>(newState(null));
const dialogWidth = computed(() => adaModalWidth(980));
function closeForm() {
showModal.value = false;
loading.value = false;
}
function openModal(state: State) {
showModal.value = true;
loading.value = true;
View({ id: state.id })
.then((res) => {
formValue.value = newState(res);
})
.finally(() => {
loading.value = false;
});
}
defineExpose({
openModal,
});
</script>
<style lang="less" scoped>
.tenant-entry-scrollbar {
max-height: 87vh;
padding-right: 20px;
}
.image-list {
display: flex;
flex-direction: column;
gap: 8px;
max-width: 220px;
}
.entry-image {
width: 180px;
height: 120px;
border-radius: 6px;
}
</style>