二维码自动补发
This commit is contained in:
@@ -76,6 +76,21 @@ func (ChatQrCode) TableName() string {
|
|||||||
return "chat_qrcode_pool"
|
return "chat_qrcode_pool"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChatQrCodeDeliveryTask struct {
|
||||||
|
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||||
|
ConversationID uint64 `gorm:"column:conversation_id;not null;uniqueIndex" json:"conversation_id"`
|
||||||
|
Status string `gorm:"size:16;not null;default:'pending';index" json:"status"`
|
||||||
|
QrCodeID *uint64 `gorm:"column:qrcode_id;index" json:"qrcode_id"`
|
||||||
|
ErrorMessage string `gorm:"column:error_message;size:255;not null;default:''" json:"error_message"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
SentAt *time.Time `gorm:"column:sent_at" json:"sent_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ChatQrCodeDeliveryTask) TableName() string {
|
||||||
|
return "chat_qrcode_delivery_tasks"
|
||||||
|
}
|
||||||
|
|
||||||
type ChatSupportGroup struct {
|
type ChatSupportGroup struct {
|
||||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||||
Code string `gorm:"size:64;not null;uniqueIndex" json:"code"`
|
Code string `gorm:"size:64;not null;uniqueIndex" json:"code"`
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func EnsureListingConversation(tx *gorm.DB, listing model.RentalListing, preferr
|
|||||||
|
|
||||||
// 6. 发二维码图片(如果有)
|
// 6. 发二维码图片(如果有)
|
||||||
if qrcodeImageURL != "" {
|
if qrcodeImageURL != "" {
|
||||||
if err := sendQrCodeImage(tx, conversation.ID, qrcodeImageURL); err != nil {
|
if _, err := sendQrCodeImage(tx, conversation.ID, qrcodeImageURL); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -121,6 +121,9 @@ func EnsureListingConversation(tx *gorm.DB, listing model.RentalListing, preferr
|
|||||||
if err := sendSystemMessage(tx, conversation.ID, noQrTip); err != nil {
|
if err := sendSystemMessage(tx, conversation.ID, noQrTip); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := createQrCodeDeliveryTask(tx, conversation.ID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 库存预警检查
|
// 7. 库存预警检查
|
||||||
@@ -262,7 +265,7 @@ func sendSystemMessage(tx *gorm.DB, conversationID uint64, content string) error
|
|||||||
return updateConversationLastMessage(tx, conversationID, &message)
|
return updateConversationLastMessage(tx, conversationID, &message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendQrCodeImage(tx *gorm.DB, conversationID uint64, imageURL string) error {
|
func sendQrCodeImage(tx *gorm.DB, conversationID uint64, imageURL string) (*model.ChatMessage, error) {
|
||||||
content := "👇 请扫码加入企业微信群"
|
content := "👇 请扫码加入企业微信群"
|
||||||
|
|
||||||
message := model.ChatMessage{
|
message := model.ChatMessage{
|
||||||
@@ -275,11 +278,14 @@ func sendQrCodeImage(tx *gorm.DB, conversationID uint64, imageURL string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Create(&message).Error; err != nil {
|
if err := tx.Create(&message).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新会话的最后消息
|
// 更新会话的最后消息
|
||||||
return updateConversationLastMessage(tx, conversationID, &message)
|
if err := updateConversationLastMessage(tx, conversationID, &message); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &message, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateConversationLastMessage(tx *gorm.DB, conversationID uint64, message *model.ChatMessage) error {
|
func updateConversationLastMessage(tx *gorm.DB, conversationID uint64, message *model.ChatMessage) error {
|
||||||
@@ -326,6 +332,14 @@ func markQrCodeAsUsed(tx *gorm.DB, qrcodeID uint64, conversationID uint64) error
|
|||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createQrCodeDeliveryTask(tx *gorm.DB, conversationID uint64) error {
|
||||||
|
task := model.ChatQrCodeDeliveryTask{
|
||||||
|
ConversationID: conversationID,
|
||||||
|
Status: "pending",
|
||||||
|
}
|
||||||
|
return tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&task).Error
|
||||||
|
}
|
||||||
|
|
||||||
// pushAlert 事务提交后需要发送的外部推送。
|
// pushAlert 事务提交后需要发送的外部推送。
|
||||||
type pushAlert struct {
|
type pushAlert struct {
|
||||||
providers []push.Provider
|
providers []push.Provider
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
"hfb_sys/backend/internal/model"
|
"hfb_sys/backend/internal/model"
|
||||||
|
"hfb_sys/backend/internal/modules/chathub"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QrCodeStatus 二维码状态常量
|
// QrCodeStatus 二维码状态常量
|
||||||
@@ -83,24 +84,35 @@ type QrCodeOCRConfig struct {
|
|||||||
|
|
||||||
// CreateQrCode 创建二维码
|
// CreateQrCode 创建二维码
|
||||||
func (r *Repository) CreateQrCode(ctx context.Context, adminID uint64, req CreateQrCodeRequest) (*model.ChatQrCode, error) {
|
func (r *Repository) CreateQrCode(ctx context.Context, adminID uint64, req CreateQrCodeRequest) (*model.ChatQrCode, error) {
|
||||||
qrcode := model.ChatQrCode{
|
var qrcode model.ChatQrCode
|
||||||
ImageURL: req.ImageURL,
|
var deliveredMessages []model.ChatMessage
|
||||||
GroupName: req.GroupName,
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
Status: QrCodeStatusUnused,
|
qrcode = model.ChatQrCode{
|
||||||
CreatedBy: adminID,
|
ImageURL: req.ImageURL,
|
||||||
Note: req.Note,
|
GroupName: req.GroupName,
|
||||||
ExpiresAt: req.ExpiresAt,
|
Status: QrCodeStatusUnused,
|
||||||
}
|
CreatedBy: adminID,
|
||||||
|
Note: req.Note,
|
||||||
|
ExpiresAt: req.ExpiresAt,
|
||||||
|
}
|
||||||
|
|
||||||
// 如果未指定过期时间,默认7天后过期
|
// 如果未指定过期时间,默认7天后过期
|
||||||
if qrcode.ExpiresAt == nil {
|
if qrcode.ExpiresAt == nil {
|
||||||
expires := time.Now().Add(7 * 24 * time.Hour)
|
expires := time.Now().Add(7 * 24 * time.Hour)
|
||||||
qrcode.ExpiresAt = &expires
|
qrcode.ExpiresAt = &expires
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.db.WithContext(ctx).Create(&qrcode).Error; err != nil {
|
if err := tx.Create(&qrcode).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
deliveredMessages, err = deliverPendingQrCodes(tx)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
r.notifyDeliveredQrCodeMessages(deliveredMessages)
|
||||||
|
|
||||||
return &qrcode, nil
|
return &qrcode, nil
|
||||||
}
|
}
|
||||||
@@ -108,26 +120,36 @@ func (r *Repository) CreateQrCode(ctx context.Context, adminID uint64, req Creat
|
|||||||
// BatchCreateQrCode 批量创建二维码
|
// BatchCreateQrCode 批量创建二维码
|
||||||
func (r *Repository) BatchCreateQrCode(ctx context.Context, adminID uint64, req BatchCreateQrCodeRequest) ([]model.ChatQrCode, error) {
|
func (r *Repository) BatchCreateQrCode(ctx context.Context, adminID uint64, req BatchCreateQrCodeRequest) ([]model.ChatQrCode, error) {
|
||||||
var qrcodes []model.ChatQrCode
|
var qrcodes []model.ChatQrCode
|
||||||
|
var deliveredMessages []model.ChatMessage
|
||||||
defaultExpires := time.Now().Add(7 * 24 * time.Hour)
|
defaultExpires := time.Now().Add(7 * 24 * time.Hour)
|
||||||
|
|
||||||
for _, item := range req.Items {
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
qr := model.ChatQrCode{
|
for _, item := range req.Items {
|
||||||
ImageURL: item.ImageURL,
|
qr := model.ChatQrCode{
|
||||||
GroupName: item.GroupName,
|
ImageURL: item.ImageURL,
|
||||||
Status: QrCodeStatusUnused,
|
GroupName: item.GroupName,
|
||||||
CreatedBy: adminID,
|
Status: QrCodeStatusUnused,
|
||||||
Note: item.Note,
|
CreatedBy: adminID,
|
||||||
ExpiresAt: item.ExpiresAt,
|
Note: item.Note,
|
||||||
|
ExpiresAt: item.ExpiresAt,
|
||||||
|
}
|
||||||
|
if qr.ExpiresAt == nil {
|
||||||
|
qr.ExpiresAt = &defaultExpires
|
||||||
|
}
|
||||||
|
qrcodes = append(qrcodes, qr)
|
||||||
}
|
}
|
||||||
if qr.ExpiresAt == nil {
|
|
||||||
qr.ExpiresAt = &defaultExpires
|
|
||||||
}
|
|
||||||
qrcodes = append(qrcodes, qr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.db.WithContext(ctx).Create(&qrcodes).Error; err != nil {
|
if err := tx.Create(&qrcodes).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
deliveredMessages, err = deliverPendingQrCodes(tx)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
r.notifyDeliveredQrCodeMessages(deliveredMessages)
|
||||||
|
|
||||||
return qrcodes, nil
|
return qrcodes, nil
|
||||||
}
|
}
|
||||||
@@ -342,3 +364,82 @@ func (r *Repository) markQrCodeAsUsed(tx *gorm.DB, qrcodeID uint64, conversation
|
|||||||
"used_at": now,
|
"used_at": now,
|
||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deliverPendingQrCodes(tx *gorm.DB) ([]model.ChatMessage, error) {
|
||||||
|
deliveredMessages := make([]model.ChatMessage, 0)
|
||||||
|
for {
|
||||||
|
var task model.ChatQrCodeDeliveryTask
|
||||||
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Where("status = ?", "pending").
|
||||||
|
Order("created_at ASC, id ASC").
|
||||||
|
First(&task).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return deliveredMessages, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
qrcode, err := fetchUnusedQrCode(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if qrcode == nil {
|
||||||
|
return deliveredMessages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := markQrCodeAsUsed(tx, qrcode.ID, task.ConversationID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
message, err := sendQrCodeImage(tx, task.ConversationID, qrcode.ImageURL)
|
||||||
|
if err != nil {
|
||||||
|
now := time.Now()
|
||||||
|
if updateErr := tx.Model(&task).Updates(map[string]interface{}{
|
||||||
|
"status": "failed",
|
||||||
|
"qrcode_id": qrcode.ID,
|
||||||
|
"error_message": err.Error(),
|
||||||
|
"updated_at": now,
|
||||||
|
}).Error; updateErr != nil {
|
||||||
|
return nil, updateErr
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deliveredMessages = append(deliveredMessages, *message)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if err := tx.Model(&task).Updates(map[string]interface{}{
|
||||||
|
"status": "sent",
|
||||||
|
"qrcode_id": qrcode.ID,
|
||||||
|
"error_message": "",
|
||||||
|
"sent_at": now,
|
||||||
|
"updated_at": now,
|
||||||
|
}).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) notifyDeliveredQrCodeMessages(messages []model.ChatMessage) {
|
||||||
|
if r.hub == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, msg := range messages {
|
||||||
|
event := &chathub.ChatEvent{
|
||||||
|
Type: "new_message",
|
||||||
|
ConversationID: msg.ConversationID,
|
||||||
|
Message: &chathub.MessageData{
|
||||||
|
ID: msg.ID,
|
||||||
|
ConversationID: msg.ConversationID,
|
||||||
|
SenderType: msg.SenderType,
|
||||||
|
SenderID: msg.SenderID,
|
||||||
|
SenderRole: msg.SenderRole,
|
||||||
|
SenderName: "系统",
|
||||||
|
ContentType: msg.ContentType,
|
||||||
|
Content: msg.Content,
|
||||||
|
AttachmentURLS: decodeStringList(msg.AttachmentURLS),
|
||||||
|
CreatedAt: msg.CreatedAt.Format(time.RFC3339),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.hub.NotifyConversation(msg.ConversationID, event)
|
||||||
|
r.hub.NotifyAllAdmins(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,7 +21,19 @@ func setupQrCodeTestDB(t *testing.T) *gorm.DB {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("无法创建测试数据库: %v", err)
|
t.Fatalf("无法创建测试数据库: %v", err)
|
||||||
}
|
}
|
||||||
if err := db.AutoMigrate(&model.ChatQrCode{}, &model.ChatConversation{}, &model.SystemConfig{}); err != nil {
|
if err := db.AutoMigrate(
|
||||||
|
&model.ChatQrCode{},
|
||||||
|
&model.ChatConversation{},
|
||||||
|
&model.ChatParticipant{},
|
||||||
|
&model.ChatMessage{},
|
||||||
|
&model.ChatQrCodeDeliveryTask{},
|
||||||
|
&model.ChatSupportGroup{},
|
||||||
|
&model.ChatSupportGroupMember{},
|
||||||
|
&model.AdminUser{},
|
||||||
|
&model.AdminUserRole{},
|
||||||
|
&model.Role{},
|
||||||
|
&model.SystemConfig{},
|
||||||
|
); err != nil {
|
||||||
t.Fatalf("数据库迁移失败: %v", err)
|
t.Fatalf("数据库迁移失败: %v", err)
|
||||||
}
|
}
|
||||||
return db
|
return db
|
||||||
@@ -218,3 +230,86 @@ func TestUpdateQrCodeGroupNameAndRenameFlag(t *testing.T) {
|
|||||||
t.Fatal("企微改名标记未保存")
|
t.Fatal("企微改名标记未保存")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnsureListingConversationCreatesQrCodeDeliveryTaskWhenStockEmpty(t *testing.T) {
|
||||||
|
db := setupQrCodeTestDB(t)
|
||||||
|
listing := model.RentalListing{
|
||||||
|
ID: 101,
|
||||||
|
ListingNo: "L202606260001",
|
||||||
|
OwnerID: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
_, err := EnsureListingConversation(tx, listing, 0)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("建发布群失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var task model.ChatQrCodeDeliveryTask
|
||||||
|
if err := db.First(&task).Error; err != nil {
|
||||||
|
t.Fatalf("未创建补发任务: %v", err)
|
||||||
|
}
|
||||||
|
if task.Status != "pending" {
|
||||||
|
t.Fatalf("补发任务状态 = %q, want pending", task.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchCreateQrCodeDeliversPendingTask(t *testing.T) {
|
||||||
|
db := setupQrCodeTestDB(t)
|
||||||
|
repo := NewRepository(db, nil)
|
||||||
|
conversation := model.ChatConversation{
|
||||||
|
Title: "账号群 L202606260002",
|
||||||
|
Type: ConversationTypeListingGroup,
|
||||||
|
Status: "active",
|
||||||
|
}
|
||||||
|
if err := db.Create(&conversation).Error; err != nil {
|
||||||
|
t.Fatalf("创建群聊失败: %v", err)
|
||||||
|
}
|
||||||
|
task := model.ChatQrCodeDeliveryTask{
|
||||||
|
ConversationID: conversation.ID,
|
||||||
|
Status: "pending",
|
||||||
|
}
|
||||||
|
if err := db.Create(&task).Error; err != nil {
|
||||||
|
t.Fatalf("创建补发任务失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := repo.BatchCreateQrCode(t.Context(), 1, BatchCreateQrCodeRequest{
|
||||||
|
Items: []CreateQrCodeRequest{
|
||||||
|
{ImageURL: "/api/files/object?key=qrcode/replenish.png", GroupName: "补发群"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("批量创建二维码失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var savedTask model.ChatQrCodeDeliveryTask
|
||||||
|
if err := db.First(&savedTask, task.ID).Error; err != nil {
|
||||||
|
t.Fatalf("查询补发任务失败: %v", err)
|
||||||
|
}
|
||||||
|
if savedTask.Status != "sent" {
|
||||||
|
t.Fatalf("补发任务状态 = %q, want sent", savedTask.Status)
|
||||||
|
}
|
||||||
|
if savedTask.QrCodeID == nil {
|
||||||
|
t.Fatal("补发任务未记录二维码 ID")
|
||||||
|
}
|
||||||
|
if savedTask.SentAt == nil {
|
||||||
|
t.Fatal("补发任务未记录 sent_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
var qrcode model.ChatQrCode
|
||||||
|
if err := db.First(&qrcode, *savedTask.QrCodeID).Error; err != nil {
|
||||||
|
t.Fatalf("查询二维码失败: %v", err)
|
||||||
|
}
|
||||||
|
if qrcode.Status != QrCodeStatusUsed {
|
||||||
|
t.Fatalf("二维码状态 = %q, want used", qrcode.Status)
|
||||||
|
}
|
||||||
|
if qrcode.ConversationID == nil || *qrcode.ConversationID != conversation.ID {
|
||||||
|
t.Fatalf("二维码绑定会话 = %v, want %d", qrcode.ConversationID, conversation.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var message model.ChatMessage
|
||||||
|
if err := db.Where("conversation_id = ? AND content_type = ?", conversation.ID, "image").First(&message).Error; err != nil {
|
||||||
|
t.Fatalf("未生成二维码图片消息: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS chat_qrcode_delivery_tasks (
|
||||||
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
conversation_id BIGINT UNSIGNED NOT NULL COMMENT '待补发二维码的会话ID',
|
||||||
|
status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '任务状态: pending待补发, sent已补发, failed失败',
|
||||||
|
qrcode_id BIGINT UNSIGNED NULL COMMENT '补发使用的二维码ID',
|
||||||
|
error_message VARCHAR(255) NOT NULL DEFAULT '' COMMENT '失败原因',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
sent_at DATETIME NULL COMMENT '补发完成时间',
|
||||||
|
UNIQUE KEY uk_qrcode_delivery_conversation (conversation_id),
|
||||||
|
KEY idx_qrcode_delivery_status_created (status, created_at),
|
||||||
|
KEY idx_qrcode_delivery_qrcode (qrcode_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='发布群二维码补发任务';
|
||||||
|
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS chat_qrcode_delivery_tasks;
|
||||||
|
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
f4b37a1b65df663bd30ab33e23a8e90472595792
|
||||||
@@ -486,14 +486,17 @@ onMounted(reloadAll)
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="过期时间" class-name="expire-col" align="center">
|
<el-table-column label="时间" class-name="time-col" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span
|
<div class="time-stack">
|
||||||
class="expire-cell"
|
<span>上传 {{ formatDateTime(row.created_at) }}</span>
|
||||||
:class="{ 'expired-tag': row.expires_at && new Date(row.expires_at) < new Date() }"
|
<span>{{ row.used_at ? `使用 ${formatDateTime(row.used_at)}` : '使用 -' }}</span>
|
||||||
>
|
<span
|
||||||
{{ row.expires_at ? formatDateTime(row.expires_at) : '永久' }}
|
:class="{ 'expired-tag': row.expires_at && new Date(row.expires_at) < new Date() }"
|
||||||
</span>
|
>
|
||||||
|
{{ row.expires_at ? `过期 ${formatDateTime(row.expires_at)}` : '过期 永久' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" class-name="action-col" align="center">
|
<el-table-column label="操作" class-name="action-col" align="center">
|
||||||
@@ -819,7 +822,7 @@ onMounted(reloadAll)
|
|||||||
width: 8%;
|
width: 8%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qrcode-table :deep(.expire-col) {
|
.qrcode-table :deep(.time-col) {
|
||||||
width: 12%;
|
width: 12%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -843,9 +846,15 @@ onMounted(reloadAll)
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expire-cell {
|
.time-stack {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
color: #5f6b7a;
|
color: #5f6b7a;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-cell-button {
|
.copy-cell-button {
|
||||||
|
|||||||
Reference in New Issue
Block a user