业务更名为撞车并切换crash路由

用户可见文案与页面/API路径改为撞车与crash,保留mohong表与权限码兼容;移动端入口去掉租/撞图标。
This commit is contained in:
yml2213
2026-07-16 21:19:35 +08:00
parent 8b637cf499
commit a4189db4ca
38 changed files with 232 additions and 179 deletions
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"gorm.io/datatypes" "gorm.io/datatypes"
) )
// MohongCategory 摸大红商品分类。 // MohongCategory 撞车商品分类。
type MohongCategory struct { type MohongCategory struct {
ID uint64 `gorm:"primaryKey" json:"id"` ID uint64 `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:64;not null;uniqueIndex" json:"name"` Name string `gorm:"size:64;not null;uniqueIndex" json:"name"`
@@ -31,7 +31,7 @@ func (MohongProductCategory) TableName() string {
return "mohong_product_categories" return "mohong_product_categories"
} }
// MohongProduct 摸大红商品。 // MohongProduct 撞车商品。
type MohongProduct struct { type MohongProduct struct {
ID uint64 `gorm:"primaryKey" json:"id"` ID uint64 `gorm:"primaryKey" json:"id"`
CategoryID *uint64 `gorm:"column:category_id;index" json:"category_id"` // 主分类(展示用) CategoryID *uint64 `gorm:"column:category_id;index" json:"category_id"` // 主分类(展示用)
@@ -55,7 +55,7 @@ func (MohongProduct) TableName() string {
return "mohong_products" return "mohong_products"
} }
// MohongOrder 摸大红订单。 // MohongOrder 撞车订单。
type MohongOrder struct { type MohongOrder struct {
ID uint64 `gorm:"primaryKey" json:"id"` ID uint64 `gorm:"primaryKey" json:"id"`
OrderNo string `gorm:"size:64;not null;uniqueIndex" json:"order_no"` OrderNo string `gorm:"size:64;not null;uniqueIndex" json:"order_no"`
@@ -169,6 +169,22 @@ func (r *Repository) EnsureSupportConversation(ctx context.Context, userID uint6
_ = tx.Model(&existing).Update("support_scene", SupportSceneGeneral).Error _ = tx.Model(&existing).Update("support_scene", SupportSceneGeneral).Error
} }
} }
// 兼容旧摸大红场景码 mohong → crash
if existing.ID == 0 && scene == SupportSceneCrash {
err = tx.Table("chat_conversations AS c").
Select("c.*").
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id").
Where(
"c.type = ? AND c.support_scene = ? AND cp.participant_type = ? AND cp.participant_id = ?",
ConversationTypeGeneralSupport, SupportSceneMohong, "user", userID,
).
Order("c.id ASC").
Limit(1).
Find(&existing).Error
if err != nil {
return err
}
}
if existing.ID > 0 { if existing.ID > 0 {
conversationID = existing.ID conversationID = existing.ID
return nil return nil
+1 -1
View File
@@ -35,7 +35,7 @@ type ConversationDTO struct {
// EnsureSupportRequest 创建/复用客服会话。 // EnsureSupportRequest 创建/复用客服会话。
type EnsureSupportRequest struct { type EnsureSupportRequest struct {
// Scene 业务场景:general(默认)/ mohong(摸大红 // Scene 业务场景:general(默认)/ crash(撞车,兼容旧值 mohong
Scene string `json:"scene"` Scene string `json:"scene"`
} }
@@ -11,18 +11,20 @@ import (
// 客服入口业务场景(与前端 POST /chats/support 的 scene 对齐)。 // 客服入口业务场景(与前端 POST /chats/support 的 scene 对齐)。
const ( const (
SupportSceneGeneral = "general" SupportSceneGeneral = "general"
SupportSceneMohong = "mohong" SupportSceneCrash = "crash"
// SupportSceneMohong 兼容旧 scene 值,与 crash 同一客服分组。
SupportSceneMohong = "mohong"
) )
// resolveSupportScene 规范化场景并返回标题、客服分组 code、欢迎语。 // resolveSupportScene 规范化场景并返回标题、客服分组 code、欢迎语。
// groupCode 为空时表示不走分组,使用默认客服选取逻辑。 // groupCode 为空时表示不走分组,使用默认客服选取逻辑。
func resolveSupportScene(raw string) (scene, title, groupCode, welcome string) { func resolveSupportScene(raw string) (scene, title, groupCode, welcome string) {
switch strings.ToLower(strings.TrimSpace(raw)) { switch strings.ToLower(strings.TrimSpace(raw)) {
case SupportSceneMohong: case SupportSceneCrash, SupportSceneMohong:
return SupportSceneMohong, return SupportSceneCrash,
"摸大红客服", "撞车客服",
supportgroup.GroupCodeMohong, supportgroup.GroupCodeMohong, // 分组 code 仍为 mohong,后台可改显示名
"您好,这里是摸大红客服。请直接描述问题,可将订单信息复制后发送。" "您好,这里是撞车客服。请直接描述问题,可将订单信息复制后发送。"
default: default:
// 未知场景回落平台客服,避免随意 scene 绕开分配策略 // 未知场景回落平台客服,避免随意 scene 绕开分配策略
return SupportSceneGeneral, return SupportSceneGeneral,
+1 -1
View File
@@ -70,7 +70,7 @@ func (h *Handler) writeObject(c *gin.Context, publicOnly bool) {
!strings.HasPrefix(key, "avatar/") && !strings.HasPrefix(key, "avatar/") &&
!strings.HasPrefix(key, "payment-cert/") && !strings.HasPrefix(key, "payment-cert/") &&
!strings.HasPrefix(key, "announcement/") && !strings.HasPrefix(key, "announcement/") &&
!strings.HasPrefix(key, "mohong/") { !strings.HasPrefix(key, "mohong/") && !strings.HasPrefix(key, "crash/") {
response.Error(c, http.StatusNotFound, "not_found", "文件不存在或暂不可访问") response.Error(c, http.StatusNotFound, "not_found", "文件不存在或暂不可访问")
return return
} }
+2 -2
View File
@@ -106,7 +106,7 @@ func normalizeContentType(contentType string, data []byte) string {
func fileURLForScene(scene string, key string) string { func fileURLForScene(scene string, key string) string {
fileURL := "/api/files/object?key=" + url.QueryEscape(key) fileURL := "/api/files/object?key=" + url.QueryEscape(key)
if scene == "home-banner" || scene == "avatar" || scene == "payment-cert" || scene == "announcement" || scene == "mohong" { if scene == "home-banner" || scene == "avatar" || scene == "payment-cert" || scene == "announcement" || scene == "mohong" || scene == "crash" {
fileURL = "/api/public/files/object?key=" + url.QueryEscape(key) fileURL = "/api/public/files/object?key=" + url.QueryEscape(key)
} }
return fileURL return fileURL
@@ -115,7 +115,7 @@ func fileURLForScene(scene string, key string) string {
func normalizeScene(scene string) string { func normalizeScene(scene string) string {
scene = strings.TrimSpace(strings.ToLower(scene)) scene = strings.TrimSpace(strings.ToLower(scene))
switch scene { switch scene {
case "listing", "handoff", "dispute", "realname", "avatar", "home-banner", "chat", "payment-cert", "announcement", "qrcode", "mohong": case "listing", "handoff", "dispute", "realname", "avatar", "home-banner", "chat", "payment-cert", "announcement", "qrcode", "mohong", "crash":
return scene return scene
default: default:
return "misc" return "misc"
+3 -3
View File
@@ -14,7 +14,7 @@ const (
configKeyDefaultQrcodeURL = "mohong.default_qrcode_url" configKeyDefaultQrcodeURL = "mohong.default_qrcode_url"
configKeyOrderCopyTemplate = "mohong.order_copy_template" configKeyOrderCopyTemplate = "mohong.order_copy_template"
defaultOrderCopyTemplate = "【摸大红订单】\n订单号:{{order_no}}\n下单时间:{{created_at}}\n商品明细:\n{{items}}\n数量合计:{{quantity}}\n金额:{{amount}}\n下单人:{{buyer_name}}{{buyer_phone}}" defaultOrderCopyTemplate = "【撞车订单】\n订单号:{{order_no}}\n下单时间:{{created_at}}\n商品明细:\n{{items}}\n数量合计:{{quantity}}\n金额:{{amount}}\n下单人:{{buyer_name}}{{buyer_phone}}"
) )
func (r *Repository) GetConfig(ctx context.Context) (*ConfigDTO, error) { func (r *Repository) GetConfig(ctx context.Context) (*ConfigDTO, error) {
@@ -42,12 +42,12 @@ func (r *Repository) UpdateConfig(ctx context.Context, req UpdateConfigRequest)
} }
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if req.DefaultQrcodeURL != nil { if req.DefaultQrcodeURL != nil {
if err := upsertConfig(tx, configKeyDefaultQrcodeURL, strings.TrimSpace(*req.DefaultQrcodeURL), "摸大红全局默认固定二维码图片URL"); err != nil { if err := upsertConfig(tx, configKeyDefaultQrcodeURL, strings.TrimSpace(*req.DefaultQrcodeURL), "撞车全局默认固定二维码图片URL"); err != nil {
return err return err
} }
} }
if req.OrderCopyTemplate != nil { if req.OrderCopyTemplate != nil {
if err := upsertConfig(tx, configKeyOrderCopyTemplate, strings.TrimSpace(*req.OrderCopyTemplate), "摸大红订单一键复制文案模板"); err != nil { if err := upsertConfig(tx, configKeyOrderCopyTemplate, strings.TrimSpace(*req.OrderCopyTemplate), "撞车订单一键复制文案模板"); err != nil {
return err return err
} }
} }
@@ -111,7 +111,7 @@ func (r *Repository) CreateOrder(ctx context.Context, userID uint64, req CreateO
if err := notification.Append(tx, notification.Entry{ if err := notification.Append(tx, notification.Entry{
UserID: userID, UserID: userID,
Type: "order", Type: "order",
Title: "摸大红订单已创建", Title: "撞车订单已创建",
Content: "订单已创建,请在有效时间内完成支付。", Content: "订单已创建,请在有效时间内完成支付。",
BizType: "mohong_order", BizType: "mohong_order",
BizID: &orderID, BizID: &orderID,
@@ -311,8 +311,8 @@ func (r *Repository) AdminCompleteOrder(ctx context.Context, orderID uint64, rem
return r.AdminFindOrder(ctx, orderID) return r.AdminFindOrder(ctx, orderID)
} }
// ConfirmPaidFromChannelTx 支付成功后推进摸大红订单:固化二维码快照与可复制文案。 // ConfirmPaidFromChannelTx 支付成功后推进撞车订单:固化二维码快照与可复制文案。
// 返回值固定为 0,兼容支付模块会话通知签名(摸大红无订单群)。 // 返回值固定为 0,兼容支付模块会话通知签名(撞车无订单群)。
func (r *Repository) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint64, error) { func (r *Repository) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint64, error) {
var order model.MohongOrder var order model.MohongOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
@@ -348,7 +348,7 @@ func (r *Repository) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint
if err := notification.Append(tx, notification.Entry{ if err := notification.Append(tx, notification.Entry{
UserID: order.UserID, UserID: order.UserID,
Type: "order", Type: "order",
Title: "摸大红订单支付成功", Title: "撞车订单支付成功",
Content: "支付已完成,请在订单详情扫码联系客服,并复制订单信息发送。", Content: "支付已完成,请在订单详情扫码联系客服,并复制订单信息发送。",
BizType: "mohong_order", BizType: "mohong_order",
BizID: &orderIDCopy, BizID: &orderIDCopy,
@@ -358,7 +358,7 @@ func (r *Repository) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint
return 0, nil return 0, nil
} }
// NotifyNewConversation 兼容支付模块接口;摸大红不创建会话,空实现。 // NotifyNewConversation 兼容支付模块接口;撞车不创建会话,空实现。
func (r *Repository) NotifyNewConversation(conversationID uint64) {} func (r *Repository) NotifyNewConversation(conversationID uint64) {}
func (r *Repository) cancelPendingOrderTx(tx *gorm.DB, order *model.MohongOrder, reason string) error { func (r *Repository) cancelPendingOrderTx(tx *gorm.DB, order *model.MohongOrder, reason string) error {
@@ -2,7 +2,7 @@ package mohong
import "gorm.io/gorm" import "gorm.io/gorm"
// Repository 摸大红数据访问。 // Repository 撞车数据访问。
type Repository struct { type Repository struct {
db *gorm.DB db *gorm.DB
} }
@@ -13,7 +13,7 @@ import (
const bizTypeMohongPay = "mohong_pay" const bizTypeMohongPay = "mohong_pay"
// StartMohong 发起摸大红订单支付。 // StartMohong 发起撞车订单支付。
func (r *Repository) StartMohong(ctx context.Context, userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) { func (r *Repository) StartMohong(ctx context.Context, userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) {
req.PayWay = normalizePayWay(req.PayWay) req.PayWay = normalizePayWay(req.PayWay)
defaultConfig, err := r.defaultRuntimeConfig(ctx, req.PayWay) defaultConfig, err := r.defaultRuntimeConfig(ctx, req.PayWay)
@@ -73,7 +73,7 @@ func (r *Repository) StartMohong(ctx context.Context, userID uint64, orderID uin
NotifyURL: runtimeConfig.NotifyURL, NotifyURL: runtimeConfig.NotifyURL,
JumpURL: runtimeConfig.JumpURL, JumpURL: runtimeConfig.JumpURL,
ClientIP: clientIP, ClientIP: clientIP,
Body: "摸大红订单 " + orderRow.OrderNo, Body: "撞车订单 " + orderRow.OrderNo,
Attach: orderRow.OrderNo, Attach: orderRow.OrderNo,
}) })
if err != nil { if err != nil {
@@ -202,7 +202,7 @@ func newMohongPayment(row model.MohongOrder, req StartPaymentRequest, runtimeCon
return payment, nil return payment, nil
} }
// QueryMohong 查询摸大红订单支付状态。 // QueryMohong 查询撞车订单支付状态。
func (r *Repository) QueryMohong(ctx context.Context, userID uint64, orderID uint64) (*PaymentDTO, error) { func (r *Repository) QueryMohong(ctx context.Context, userID uint64, orderID uint64) (*PaymentDTO, error) {
var payment model.PaymentOrder var payment model.PaymentOrder
if err := r.db.WithContext(ctx). if err := r.db.WithContext(ctx).
@@ -13,7 +13,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// MohongOrderConfirmer 摸大红订单支付确认接口,避免 payment 与 mohong 循环依赖。 // MohongOrderConfirmer 撞车订单支付确认接口,避免 payment 与 mohong 循环依赖。
type MohongOrderConfirmer interface { type MohongOrderConfirmer interface {
ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint64, error) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint64, error)
NotifyNewConversation(conversationID uint64) NotifyNewConversation(conversationID uint64)
@@ -89,7 +89,7 @@ func WithLogger(logger *zap.Logger) RepositoryOption {
} }
} }
// WithMohongRepo 注入摸大红订单确认器。 // WithMohongRepo 注入撞车订单确认器。
func WithMohongRepo(repo MohongOrderConfirmer) RepositoryOption { func WithMohongRepo(repo MohongOrderConfirmer) RepositoryOption {
return func(r *Repository) { return func(r *Repository) {
r.mohongRepo = repo r.mohongRepo = repo
+37 -30
View File
@@ -428,23 +428,26 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
sellerPickupRoutes.GET("", pickupHandler.ListForSeller) sellerPickupRoutes.GET("", pickupHandler.ListForSeller)
} }
// 摸大红:商品公开,下单需登录+实名 // 撞车(对外路径 /crash;兼容旧路径 /mohong:商品公开,下单需登录+实名
if mohongHandler != nil { if mohongHandler != nil {
mohongPublic := api.Group("/mohong") registerCrashPublic := func(g *gin.RouterGroup) {
{ g.GET("/categories", mohongHandler.ListCategories)
mohongPublic.GET("/categories", mohongHandler.ListCategories) g.GET("/products", mohongHandler.ListProducts)
mohongPublic.GET("/products", mohongHandler.ListProducts) g.GET("/products/:id", mohongHandler.GetProduct)
mohongPublic.GET("/products/:id", mohongHandler.GetProduct)
} }
mohongAuth := api.Group("/mohong", requireAuth) registerCrashAuth := func(g *gin.RouterGroup) {
{ g.POST("/orders", requireRealname, mohongHandler.CreateOrder)
mohongAuth.POST("/orders", requireRealname, mohongHandler.CreateOrder) g.GET("/orders", mohongHandler.ListMyOrders)
mohongAuth.GET("/orders", mohongHandler.ListMyOrders) g.GET("/orders/:id", mohongHandler.GetMyOrder)
mohongAuth.GET("/orders/:id", mohongHandler.GetMyOrder) g.POST("/orders/:id/cancel", mohongHandler.CancelMyOrder)
mohongAuth.POST("/orders/:id/cancel", mohongHandler.CancelMyOrder) g.POST("/orders/:id/start-payment", requireRealname, paymentHandler.StartMohong)
mohongAuth.POST("/orders/:id/start-payment", requireRealname, paymentHandler.StartMohong) g.GET("/orders/:id/query-payment", paymentHandler.QueryMohong)
mohongAuth.GET("/orders/:id/query-payment", paymentHandler.QueryMohong)
} }
registerCrashPublic(api.Group("/crash"))
registerCrashAuth(api.Group("/crash", requireAuth))
// 兼容旧 API 前缀
registerCrashPublic(api.Group("/mohong"))
registerCrashAuth(api.Group("/mohong", requireAuth))
} }
orderRoutes := api.Group("/orders", requireAuth) orderRoutes := api.Group("/orders", requireAuth)
@@ -599,24 +602,28 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
adminRoutes.POST("/listings/:id/offline", requirePerm("listing:offline"), listingHandler.AdminOffline) adminRoutes.POST("/listings/:id/offline", requirePerm("listing:offline"), listingHandler.AdminOffline)
adminRoutes.POST("/listings/:id/mark-abnormal", requirePerm("listing:offline"), listingHandler.AdminMarkAbnormal) adminRoutes.POST("/listings/:id/mark-abnormal", requirePerm("listing:offline"), listingHandler.AdminMarkAbnormal)
// 摸大红 // 撞车后台(/admin/crash;兼容 /admin/mohong
if mohongHandler != nil { if mohongHandler != nil {
// 商品编辑需要拉分类列表,故 GET 用 product_view // 商品编辑需要拉分类列表,故 GET 用 product_view
adminRoutes.GET("/mohong/categories", requirePerm("mohong:product_view"), mohongHandler.AdminListCategories) registerCrashAdmin := func(prefix string) {
adminRoutes.POST("/mohong/categories", requirePerm("mohong:category"), mohongHandler.AdminCreateCategory) adminRoutes.GET(prefix+"/categories", requirePerm("mohong:product_view"), mohongHandler.AdminListCategories)
adminRoutes.PUT("/mohong/categories/:id", requirePerm("mohong:category"), mohongHandler.AdminUpdateCategory) adminRoutes.POST(prefix+"/categories", requirePerm("mohong:category"), mohongHandler.AdminCreateCategory)
adminRoutes.DELETE("/mohong/categories/:id", requirePerm("mohong:category"), mohongHandler.AdminDeleteCategory) adminRoutes.PUT(prefix+"/categories/:id", requirePerm("mohong:category"), mohongHandler.AdminUpdateCategory)
adminRoutes.GET("/mohong/products", requirePerm("mohong:product_view"), mohongHandler.AdminListProducts) adminRoutes.DELETE(prefix+"/categories/:id", requirePerm("mohong:category"), mohongHandler.AdminDeleteCategory)
adminRoutes.GET("/mohong/products/:id", requirePerm("mohong:product_view"), mohongHandler.AdminGetProduct) adminRoutes.GET(prefix+"/products", requirePerm("mohong:product_view"), mohongHandler.AdminListProducts)
adminRoutes.POST("/mohong/products", requirePerm("mohong:product_manage"), mohongHandler.AdminCreateProduct) adminRoutes.GET(prefix+"/products/:id", requirePerm("mohong:product_view"), mohongHandler.AdminGetProduct)
adminRoutes.PUT("/mohong/products/:id", requirePerm("mohong:product_manage"), mohongHandler.AdminUpdateProduct) adminRoutes.POST(prefix+"/products", requirePerm("mohong:product_manage"), mohongHandler.AdminCreateProduct)
adminRoutes.DELETE("/mohong/products/:id", requirePerm("mohong:product_manage"), mohongHandler.AdminDeleteProduct) adminRoutes.PUT(prefix+"/products/:id", requirePerm("mohong:product_manage"), mohongHandler.AdminUpdateProduct)
adminRoutes.GET("/mohong/orders", requirePerm("mohong:order_view"), mohongHandler.AdminListOrders) adminRoutes.DELETE(prefix+"/products/:id", requirePerm("mohong:product_manage"), mohongHandler.AdminDeleteProduct)
adminRoutes.GET("/mohong/orders/:id", requirePerm("mohong:order_view"), mohongHandler.AdminGetOrder) adminRoutes.GET(prefix+"/orders", requirePerm("mohong:order_view"), mohongHandler.AdminListOrders)
adminRoutes.POST("/mohong/orders/:id/complete", requirePerm("mohong:order_manage"), mohongHandler.AdminCompleteOrder) adminRoutes.GET(prefix+"/orders/:id", requirePerm("mohong:order_view"), mohongHandler.AdminGetOrder)
adminRoutes.POST("/mohong/orders/:id/cancel", requirePerm("mohong:order_manage"), mohongHandler.AdminCancelOrder) adminRoutes.POST(prefix+"/orders/:id/complete", requirePerm("mohong:order_manage"), mohongHandler.AdminCompleteOrder)
adminRoutes.GET("/mohong/config", requirePerm("mohong:config"), mohongHandler.AdminGetConfig) adminRoutes.POST(prefix+"/orders/:id/cancel", requirePerm("mohong:order_manage"), mohongHandler.AdminCancelOrder)
adminRoutes.PUT("/mohong/config", requirePerm("mohong:config"), mohongHandler.AdminUpdateConfig) adminRoutes.GET(prefix+"/config", requirePerm("mohong:config"), mohongHandler.AdminGetConfig)
adminRoutes.PUT(prefix+"/config", requirePerm("mohong:config"), mohongHandler.AdminUpdateConfig)
}
registerCrashAdmin("/crash")
registerCrashAdmin("/mohong")
} }
adminRoutes.GET("/disputes", requirePerm("dispute:view"), disputeHandler.AdminList) adminRoutes.GET("/disputes", requirePerm("dispute:view"), disputeHandler.AdminList)
@@ -0,0 +1,31 @@
-- +goose Up
UPDATE chat_support_groups
SET name = '撞车-客服',
description = '用户从撞车入口联系客服时,按本组成员在线与负载分配'
WHERE code = 'mohong';
UPDATE system_configs
SET description = REPLACE(description, '摸大红', '撞车'),
`value` = REPLACE(`value`, '摸大红', '撞车')
WHERE `key` LIKE 'mohong.%';
UPDATE permissions
SET name = REPLACE(name, '摸大红', '撞车')
WHERE code LIKE 'mohong:%';
-- +goose Down
UPDATE chat_support_groups
SET name = '摸大红-客服',
description = '用户从摸大红入口联系客服时,按本组成员在线与负载分配'
WHERE code = 'mohong';
UPDATE system_configs
SET description = REPLACE(description, '撞车', '摸大红'),
`value` = REPLACE(`value`, '撞车', '摸大红')
WHERE `key` LIKE 'mohong.%';
UPDATE permissions
SET name = REPLACE(name, '撞车', '摸大红')
WHERE code LIKE 'mohong:%';
@@ -96,7 +96,7 @@ async function handleDelete(item: MohongCategory) {
<div class="admin-page"> <div class="admin-page">
<div class="page-head"> <div class="page-head">
<div> <div>
<h2>摸大红分类</h2> <h2>撞车分类</h2>
<p>大红 / 四格大红 / 炫彩等分类前台左侧筛选使用</p> <p>大红 / 四格大红 / 炫彩等分类前台左侧筛选使用</p>
</div> </div>
<div class="actions"> <div class="actions">
@@ -49,7 +49,7 @@ async function handleSave() {
async function uploadQrcode(file: File) { async function uploadQrcode(file: File) {
try { try {
const uploaded = await uploadAdminFile(file, 'mohong') const uploaded = await uploadAdminFile(file, 'crash')
form.default_qrcode_url = uploaded.url form.default_qrcode_url = uploaded.url
ElMessage.success('上传成功') ElMessage.success('上传成功')
} catch (error) { } catch (error) {
@@ -63,7 +63,7 @@ async function uploadQrcode(file: File) {
<div v-loading="loading" class="admin-page"> <div v-loading="loading" class="admin-page">
<div class="page-head"> <div class="page-head">
<div> <div>
<h2>摸大红配置</h2> <h2>撞车配置</h2>
<p>全局默认固定二维码与订单复制模板商品可覆盖默认二维码支付后用户在订单详情扫码并复制信息联系客服</p> <p>全局默认固定二维码与订单复制模板商品可覆盖默认二维码支付后用户在订单详情扫码并复制信息联系客服</p>
</div> </div>
<el-button type="primary" :loading="saving" @click="handleSave">保存配置</el-button> <el-button type="primary" :loading="saving" @click="handleSave">保存配置</el-button>
@@ -85,7 +85,7 @@ async function copyText(text: string) {
<div class="admin-page"> <div class="admin-page">
<div class="page-head"> <div class="page-head">
<div> <div>
<h2>摸大红订单</h2> <h2>撞车订单</h2>
<p>查看订单完成履约复制用户订单信息</p> <p>查看订单完成履约复制用户订单信息</p>
</div> </div>
<el-button :icon="Refresh" @click="load">刷新</el-button> <el-button :icon="Refresh" @click="load">刷新</el-button>
@@ -177,7 +177,7 @@ async function handleDelete(item: MohongProduct) {
async function uploadImage(file: File, target: 'cover' | 'gallery' | 'qrcode') { async function uploadImage(file: File, target: 'cover' | 'gallery' | 'qrcode') {
try { try {
const uploaded = await uploadAdminFile(file, 'mohong') const uploaded = await uploadAdminFile(file, 'crash')
if (target === 'cover') form.cover_url = uploaded.url if (target === 'cover') form.cover_url = uploaded.url
else if (target === 'qrcode') form.qrcode_image_url = uploaded.url else if (target === 'qrcode') form.qrcode_image_url = uploaded.url
else form.image_urls.push(uploaded.url) else form.image_urls.push(uploaded.url)
@@ -197,7 +197,7 @@ function removeGallery(url: string) {
<div class="admin-page"> <div class="admin-page">
<div class="page-head"> <div class="page-head">
<div> <div>
<h2>摸大红商品</h2> <h2>撞车商品</h2>
<p>管理商品图片价格库存与专属二维码</p> <p>管理商品图片价格库存与专属二维码</p>
</div> </div>
<div class="actions"> <div class="actions">
+7 -3
View File
@@ -14,7 +14,7 @@ export interface ChatParticipant {
joined_at: string joined_at: string
} }
export type SupportScene = 'general' | 'mohong' export type SupportScene = 'general' | 'crash'
export interface ChatConversation { export interface ChatConversation {
id: number id: number
@@ -42,15 +42,19 @@ export interface ChatConversation {
updated_at: string updated_at: string
} }
/** 根据当前路由推断客服场景:摸大红页 → mohong,其它 → general */ /** 根据当前路由推断客服场景:撞车页 → crash,其它 → general */
export function resolveSupportScene(path: string): SupportScene { export function resolveSupportScene(path: string): SupportScene {
if ( if (
path === '/crash' ||
path.startsWith('/crash/') ||
path === '/m/crash' ||
path.startsWith('/m/crash/') ||
path === '/mohong' || path === '/mohong' ||
path.startsWith('/mohong/') || path.startsWith('/mohong/') ||
path === '/m/mohong' || path === '/m/mohong' ||
path.startsWith('/m/mohong/') path.startsWith('/m/mohong/')
) { ) {
return 'mohong' return 'crash'
} }
return 'general' return 'general'
} }
@@ -260,8 +260,8 @@ loadHome()
<strong>租号大厅</strong> <strong>租号大厅</strong>
<small>高哈夫币 · 安全交接 · 随租随玩</small> <small>高哈夫币 · 安全交接 · 随租随玩</small>
</RouterLink> </RouterLink>
<RouterLink class="biz-entry mohong" to="/mohong"> <RouterLink class="biz-entry crash" to="/crash">
<strong>摸大红 <em>NEW</em></strong> <strong>撞车 <em>NEW</em></strong>
<small>选购商品 · 支付后进群联系客服</small> <small>选购商品 · 支付后进群联系客服</small>
</RouterLink> </RouterLink>
</div> </div>
@@ -372,7 +372,7 @@ loadHome()
font-size: 13px; font-size: 13px;
} }
.biz-entry.mohong strong em { .biz-entry.crash strong em {
margin-left: 6px; margin-left: 6px;
padding: 1px 6px; padding: 1px 6px;
border-radius: 999px; border-radius: 999px;
@@ -364,28 +364,6 @@
box-shadow: 0 4px 14px rgba(23, 35, 61, 0.04); box-shadow: 0 4px 14px rgba(23, 35, 61, 0.04);
} }
.biz-entry-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 9px;
font-size: 13px;
font-weight: 800;
margin-bottom: 2px;
}
.biz-entry-icon.rental {
background: #fff1e6;
color: #ff6a00;
}
.biz-entry-icon.mohong {
background: #fee2e2;
color: #dc2626;
}
.biz-entry strong { .biz-entry strong {
color: #17233d; color: #17233d;
font-size: 15px; font-size: 15px;
@@ -818,13 +818,11 @@ syncMobileHomeQuery()
<div class="biz-entry-grid" aria-label="业务入口"> <div class="biz-entry-grid" aria-label="业务入口">
<RouterLink class="biz-entry" to="/"> <RouterLink class="biz-entry" to="/">
<span class="biz-entry-icon rental"></span>
<strong>租号大厅</strong> <strong>租号大厅</strong>
<small>高哈夫币 · 随租随玩</small> <small>高哈夫币 · 随租随玩</small>
</RouterLink> </RouterLink>
<RouterLink class="biz-entry" to="/mohong"> <RouterLink class="biz-entry" to="/crash">
<span class="biz-entry-icon mohong"></span> <strong>撞车</strong>
<strong>摸大红</strong>
<small>选购商品 · 一键找客服</small> <small>选购商品 · 一键找客服</small>
<em class="biz-entry-badge">NEW</em> <em class="biz-entry-badge">NEW</em>
</RouterLink> </RouterLink>
+24 -24
View File
@@ -92,7 +92,7 @@ export interface Paginated<T> {
} }
export async function fetchMohongCategories() { export async function fetchMohongCategories() {
const { data } = await apiClient.get<ApiResponse<MohongCategory[]>>('/mohong/categories') const { data } = await apiClient.get<ApiResponse<MohongCategory[]>>('/crash/categories')
return data.data || [] return data.data || []
} }
@@ -102,14 +102,14 @@ export async function fetchMohongProducts(params?: {
page?: number page?: number
page_size?: number page_size?: number
}) { }) {
const { data } = await apiClient.get<ApiResponse<Paginated<MohongProduct>>>('/mohong/products', { const { data } = await apiClient.get<ApiResponse<Paginated<MohongProduct>>>('/crash/products', {
params, params,
}) })
return data.data return data.data
} }
export async function fetchMohongProduct(id: number | string) { export async function fetchMohongProduct(id: number | string) {
const { data } = await apiClient.get<ApiResponse<MohongProduct>>(`/mohong/products/${id}`) const { data } = await apiClient.get<ApiResponse<MohongProduct>>(`/crash/products/${id}`)
return data.data return data.data
} }
@@ -121,7 +121,7 @@ export async function createMohongOrder(
const payload = Array.isArray(productIdOrItems) const payload = Array.isArray(productIdOrItems)
? { items: productIdOrItems } ? { items: productIdOrItems }
: { product_id: productIdOrItems, quantity } : { product_id: productIdOrItems, quantity }
const { data } = await apiClient.post<ApiResponse<MohongOrder>>('/mohong/orders', payload) const { data } = await apiClient.post<ApiResponse<MohongOrder>>('/crash/orders', payload)
return data.data return data.data
} }
@@ -130,19 +130,19 @@ export async function fetchMyMohongOrders(params?: {
page?: number page?: number
page_size?: number page_size?: number
}) { }) {
const { data } = await apiClient.get<ApiResponse<Paginated<MohongOrder>>>('/mohong/orders', { const { data } = await apiClient.get<ApiResponse<Paginated<MohongOrder>>>('/crash/orders', {
params, params,
}) })
return data.data return data.data
} }
export async function fetchMyMohongOrder(id: number | string) { export async function fetchMyMohongOrder(id: number | string) {
const { data } = await apiClient.get<ApiResponse<MohongOrder>>(`/mohong/orders/${id}`) const { data } = await apiClient.get<ApiResponse<MohongOrder>>(`/crash/orders/${id}`)
return data.data return data.data
} }
export async function cancelMyMohongOrder(id: number | string, reason?: string) { export async function cancelMyMohongOrder(id: number | string, reason?: string) {
const { data } = await apiClient.post<ApiResponse<MohongOrder>>(`/mohong/orders/${id}/cancel`, { const { data } = await apiClient.post<ApiResponse<MohongOrder>>(`/crash/orders/${id}/cancel`, {
reason: reason || '', reason: reason || '',
}) })
return data.data return data.data
@@ -154,7 +154,7 @@ export async function startMohongPayment(
jsPayFlag?: string jsPayFlag?: string
) { ) {
const { data } = await apiClient.post<ApiResponse<PaymentOrder>>( const { data } = await apiClient.post<ApiResponse<PaymentOrder>>(
`/mohong/orders/${orderId}/start-payment`, `/crash/orders/${orderId}/start-payment`,
{ {
pay_way: payWay || 'ZFBZF', pay_way: payWay || 'ZFBZF',
jspay_flag: jsPayFlag || '', jspay_flag: jsPayFlag || '',
@@ -165,14 +165,14 @@ export async function startMohongPayment(
export async function queryMohongPayment(orderId: number) { export async function queryMohongPayment(orderId: number) {
const { data } = await apiClient.get<ApiResponse<PaymentOrder>>( const { data } = await apiClient.get<ApiResponse<PaymentOrder>>(
`/mohong/orders/${orderId}/query-payment` `/crash/orders/${orderId}/query-payment`
) )
return data.data return data.data
} }
// Admin APIs // Admin APIs
export async function fetchAdminMohongCategories() { export async function fetchAdminMohongCategories() {
const { data } = await apiClient.get<ApiResponse<MohongCategory[]>>('/admin/mohong/categories') const { data } = await apiClient.get<ApiResponse<MohongCategory[]>>('/admin/crash/categories')
return data.data || [] return data.data || []
} }
@@ -183,7 +183,7 @@ export async function createAdminMohongCategory(payload: {
status?: string status?: string
}) { }) {
const { data } = await apiClient.post<ApiResponse<MohongCategory>>( const { data } = await apiClient.post<ApiResponse<MohongCategory>>(
'/admin/mohong/categories', '/admin/crash/categories',
payload payload
) )
return data.data return data.data
@@ -194,7 +194,7 @@ export async function updateAdminMohongCategory(
payload: Partial<{ name: string; code: string; sort_order: number; status: string }> payload: Partial<{ name: string; code: string; sort_order: number; status: string }>
) { ) {
const { data } = await apiClient.put<ApiResponse<MohongCategory>>( const { data } = await apiClient.put<ApiResponse<MohongCategory>>(
`/admin/mohong/categories/${id}`, `/admin/crash/categories/${id}`,
payload payload
) )
return data.data return data.data
@@ -202,7 +202,7 @@ export async function updateAdminMohongCategory(
export async function deleteAdminMohongCategory(id: number) { export async function deleteAdminMohongCategory(id: number) {
const { data } = await apiClient.delete<ApiResponse<{ message: string }>>( const { data } = await apiClient.delete<ApiResponse<{ message: string }>>(
`/admin/mohong/categories/${id}` `/admin/crash/categories/${id}`
) )
return data.data return data.data
} }
@@ -215,25 +215,25 @@ export async function fetchAdminMohongProducts(params?: {
page_size?: number page_size?: number
}) { }) {
const { data } = await apiClient.get<ApiResponse<Paginated<MohongProduct>>>( const { data } = await apiClient.get<ApiResponse<Paginated<MohongProduct>>>(
'/admin/mohong/products', '/admin/crash/products',
{ params } { params }
) )
return data.data return data.data
} }
export async function fetchAdminMohongProduct(id: number | string) { export async function fetchAdminMohongProduct(id: number | string) {
const { data } = await apiClient.get<ApiResponse<MohongProduct>>(`/admin/mohong/products/${id}`) const { data } = await apiClient.get<ApiResponse<MohongProduct>>(`/admin/crash/products/${id}`)
return data.data return data.data
} }
export async function createAdminMohongProduct(payload: Partial<MohongProduct> & { title: string; price_cent: number }) { export async function createAdminMohongProduct(payload: Partial<MohongProduct> & { title: string; price_cent: number }) {
const { data } = await apiClient.post<ApiResponse<MohongProduct>>('/admin/mohong/products', payload) const { data } = await apiClient.post<ApiResponse<MohongProduct>>('/admin/crash/products', payload)
return data.data return data.data
} }
export async function updateAdminMohongProduct(id: number, payload: Record<string, unknown>) { export async function updateAdminMohongProduct(id: number, payload: Record<string, unknown>) {
const { data } = await apiClient.put<ApiResponse<MohongProduct>>( const { data } = await apiClient.put<ApiResponse<MohongProduct>>(
`/admin/mohong/products/${id}`, `/admin/crash/products/${id}`,
payload payload
) )
return data.data return data.data
@@ -241,7 +241,7 @@ export async function updateAdminMohongProduct(id: number, payload: Record<strin
export async function deleteAdminMohongProduct(id: number) { export async function deleteAdminMohongProduct(id: number) {
const { data } = await apiClient.delete<ApiResponse<{ message: string }>>( const { data } = await apiClient.delete<ApiResponse<{ message: string }>>(
`/admin/mohong/products/${id}` `/admin/crash/products/${id}`
) )
return data.data return data.data
} }
@@ -252,20 +252,20 @@ export async function fetchAdminMohongOrders(params?: {
page?: number page?: number
page_size?: number page_size?: number
}) { }) {
const { data } = await apiClient.get<ApiResponse<Paginated<MohongOrder>>>('/admin/mohong/orders', { const { data } = await apiClient.get<ApiResponse<Paginated<MohongOrder>>>('/admin/crash/orders', {
params, params,
}) })
return data.data return data.data
} }
export async function fetchAdminMohongOrder(id: number | string) { export async function fetchAdminMohongOrder(id: number | string) {
const { data } = await apiClient.get<ApiResponse<MohongOrder>>(`/admin/mohong/orders/${id}`) const { data } = await apiClient.get<ApiResponse<MohongOrder>>(`/admin/crash/orders/${id}`)
return data.data return data.data
} }
export async function completeAdminMohongOrder(id: number, remark?: string) { export async function completeAdminMohongOrder(id: number, remark?: string) {
const { data } = await apiClient.post<ApiResponse<MohongOrder>>( const { data } = await apiClient.post<ApiResponse<MohongOrder>>(
`/admin/mohong/orders/${id}/complete`, `/admin/crash/orders/${id}/complete`,
{ remark: remark || '' } { remark: remark || '' }
) )
return data.data return data.data
@@ -273,19 +273,19 @@ export async function completeAdminMohongOrder(id: number, remark?: string) {
export async function cancelAdminMohongOrder(id: number, reason?: string) { export async function cancelAdminMohongOrder(id: number, reason?: string) {
const { data } = await apiClient.post<ApiResponse<MohongOrder>>( const { data } = await apiClient.post<ApiResponse<MohongOrder>>(
`/admin/mohong/orders/${id}/cancel`, `/admin/crash/orders/${id}/cancel`,
{ reason: reason || '' } { reason: reason || '' }
) )
return data.data return data.data
} }
export async function fetchAdminMohongConfig() { export async function fetchAdminMohongConfig() {
const { data } = await apiClient.get<ApiResponse<MohongConfig>>('/admin/mohong/config') const { data } = await apiClient.get<ApiResponse<MohongConfig>>('/admin/crash/config')
return data.data return data.data
} }
export async function updateAdminMohongConfig(payload: Partial<MohongConfig>) { export async function updateAdminMohongConfig(payload: Partial<MohongConfig>) {
const { data } = await apiClient.put<ApiResponse<MohongConfig>>('/admin/mohong/config', payload) const { data } = await apiClient.put<ApiResponse<MohongConfig>>('/admin/crash/config', payload)
return data.data return data.data
} }
@@ -69,7 +69,7 @@ async function handleCheckout() {
clearCart() clearCart()
closeCart() closeCart()
const payment = await startMohongPayment(order.id, payWay) const payment = await startMohongPayment(order.id, payWay)
const orderPath = props.mobile ? `/m/mohong/orders/${order.id}` : `/mohong/orders/${order.id}` const orderPath = props.mobile ? `/m/crash/orders/${order.id}` : `/crash/orders/${order.id}`
if (payment.paid || payment.status === 'paid') { if (payment.paid || payment.status === 'paid') {
ElMessage.success('支付成功') ElMessage.success('支付成功')
await router.push(orderPath) await router.push(orderPath)
@@ -18,7 +18,7 @@ const stockLabel = computed(() => {
</script> </script>
<template> <template>
<RouterLink class="mohong-card" :to="`/mohong/${product.id}`"> <RouterLink class="mohong-card" :to="`/crash/${product.id}`">
<div class="card-cover"> <div class="card-cover">
<img <img
v-if="showImage" v-if="showImage"
@@ -1,7 +1,7 @@
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import type { MohongProduct } from '@/features/mohong/api/mohong' import type { MohongProduct } from '@/features/mohong/api/mohong'
const STORAGE_KEY = 'mohong_cart_v1' const STORAGE_KEY = 'crash_cart_v1'
export interface MohongCartLine { export interface MohongCartLine {
product_id: number product_id: number
@@ -123,11 +123,11 @@ async function handleBuy() {
const payment = await startMohongPayment(order.id, payWay) const payment = await startMohongPayment(order.id, payWay)
if (payment.paid || payment.status === 'paid') { if (payment.paid || payment.status === 'paid') {
showToast({ message: '支付成功', icon: 'passed' }) showToast({ message: '支付成功', icon: 'passed' })
await router.push(`/m/mohong/orders/${order.id}`) await router.push(`/m/crash/orders/${order.id}`)
return return
} }
await openMobilePaymentCashier(payment, async () => { await openMobilePaymentCashier(payment, async () => {
await router.push(`/m/mohong/orders/${order.id}`) await router.push(`/m/crash/orders/${order.id}`)
}) })
} catch (error) { } catch (error) {
showToast({ message: readError(error, '下单失败'), icon: 'cross' }) showToast({ message: readError(error, '下单失败'), icon: 'cross' })
@@ -165,11 +165,11 @@ function syncQuery() {
const query: Record<string, string> = {} const query: Record<string, string> = {}
if (keyword.value.trim()) query.keyword = keyword.value.trim() if (keyword.value.trim()) query.keyword = keyword.value.trim()
if (activeCategoryId.value) query.category_id = String(activeCategoryId.value) if (activeCategoryId.value) query.category_id = String(activeCategoryId.value)
router.replace({ path: '/mohong', query }) router.replace({ path: '/crash', query })
} }
function goDetail(id: number) { function goDetail(id: number) {
router.push(`/mohong/${id}`) router.push(`/crash/${id}`)
} }
function markCoverFailed(id: number) { function markCoverFailed(id: number) {
@@ -188,7 +188,7 @@ async function handleSupportClick() {
if (supportLoading.value) return if (supportLoading.value) return
supportLoading.value = true supportLoading.value = true
try { try {
const chat = await ensureSupportChat('mohong') const chat = await ensureSupportChat('crash')
router.push(`/m/chats/${chat.id}`) router.push(`/m/chats/${chat.id}`)
} catch { } catch {
showToast({ message: '联系客服失败,请稍后重试', icon: 'cross' }) showToast({ message: '联系客服失败,请稍后重试', icon: 'cross' })
@@ -224,7 +224,7 @@ async function handleSupportClick() {
> >
{{ supportLoading ? '...' : '客服' }} {{ supportLoading ? '...' : '客服' }}
</button> </button>
<button type="button" class="orders-btn" @click="router.push('/mohong/orders')">订单</button> <button type="button" class="orders-btn" @click="router.push('/crash/orders')">订单</button>
</header> </header>
<div class="shop-body"> <div class="shop-body">
@@ -35,7 +35,7 @@ async function loadOrders() {
<button type="button" class="back-btn" @click="router.back()"> <button type="button" class="back-btn" @click="router.back()">
<van-icon name="arrow-left" :size="20" /> <van-icon name="arrow-left" :size="20" />
</button> </button>
<h1>摸大红订单</h1> <h1>撞车订单</h1>
</header> </header>
<van-loading v-if="loading" class="state-loading" size="24px" vertical>加载中...</van-loading> <van-loading v-if="loading" class="state-loading" size="24px" vertical>加载中...</van-loading>
@@ -46,7 +46,7 @@ async function loadOrders() {
:key="item.id" :key="item.id"
type="button" type="button"
class="card" class="card"
@click="router.push(`/mohong/orders/${item.id}`)" @click="router.push(`/crash/orders/${item.id}`)"
> >
<div class="top"> <div class="top">
<span>{{ item.order_no }}</span> <span>{{ item.order_no }}</span>
@@ -146,11 +146,11 @@ async function handleBuy() {
const payment = await startMohongPayment(order.id, payWay) const payment = await startMohongPayment(order.id, payWay)
if (payment.paid || payment.status === 'paid') { if (payment.paid || payment.status === 'paid') {
ElMessage.success('支付成功') ElMessage.success('支付成功')
await router.push(`/mohong/orders/${order.id}`) await router.push(`/crash/orders/${order.id}`)
return return
} }
await cashier.openPaymentCashier(payment, async () => { await cashier.openPaymentCashier(payment, async () => {
await router.push(`/mohong/orders/${order.id}`) await router.push(`/crash/orders/${order.id}`)
}) })
} catch (error) { } catch (error) {
ElMessage.error(readError(error, '下单失败')) ElMessage.error(readError(error, '下单失败'))
@@ -184,7 +184,7 @@ async function handleBuy() {
</div> </div>
<div class="info-panel"> <div class="info-panel">
<p class="eyebrow">摸大红商品</p> <p class="eyebrow">撞车商品</p>
<h1>{{ product.title }}</h1> <h1>{{ product.title }}</h1>
<p class="desc">{{ product.description || '暂无说明' }}</p> <p class="desc">{{ product.description || '暂无说明' }}</p>
@@ -238,7 +238,7 @@ async function handleBuy() {
<el-button size="large" class="btn-outline" @click="openCart"> <el-button size="large" class="btn-outline" @click="openCart">
购物车{{ cartCount > 0 ? `(${cartCount})` : '' }} 购物车{{ cartCount > 0 ? `(${cartCount})` : '' }}
</el-button> </el-button>
<el-button size="large" class="btn-outline" @click="router.push('/mohong')"> <el-button size="large" class="btn-outline" @click="router.push('/crash')">
返回列表 返回列表
</el-button> </el-button>
</div> </div>
@@ -180,7 +180,7 @@ function syncQuery() {
const query: Record<string, string> = {} const query: Record<string, string> = {}
if (keyword.value.trim()) query.keyword = keyword.value.trim() if (keyword.value.trim()) query.keyword = keyword.value.trim()
if (activeCategoryId.value) query.category_id = String(activeCategoryId.value) if (activeCategoryId.value) query.category_id = String(activeCategoryId.value)
router.replace({ path: '/mohong', query }) router.replace({ path: '/crash', query })
} }
const activeCategoryName = () => { const activeCategoryName = () => {
@@ -196,7 +196,7 @@ async function handleSupportClick() {
if (supportLoading.value) return if (supportLoading.value) return
supportLoading.value = true supportLoading.value = true
try { try {
const chat = await ensureSupportChat('mohong') const chat = await ensureSupportChat('crash')
router.push(`/messages/${chat.id}`) router.push(`/messages/${chat.id}`)
} catch { } catch {
ElMessage.error('联系客服失败,请稍后重试') ElMessage.error('联系客服失败,请稍后重试')
@@ -210,7 +210,7 @@ async function handleSupportClick() {
<section class="mohong-pc-page"> <section class="mohong-pc-page">
<header class="toolbar"> <header class="toolbar">
<div class="title-block"> <div class="title-block">
<h1>摸大红</h1> <h1>撞车</h1>
<span class="count">{{ total }} </span> <span class="count">{{ total }} </span>
<span class="tip">{{ activeCategoryName() }} · 支付后扫码联系客服</span> <span class="tip">{{ activeCategoryName() }} · 支付后扫码联系客服</span>
</div> </div>
@@ -228,7 +228,7 @@ async function handleSupportClick() {
<el-icon><ShoppingCart /></el-icon> <el-icon><ShoppingCart /></el-icon>
<span>购物车{{ cartCount > 0 ? `(${cartCount})` : '' }}</span> <span>购物车{{ cartCount > 0 ? `(${cartCount})` : '' }}</span>
</button> </button>
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong/orders')"> <button type="button" class="toolbar-btn outline" @click="router.push('/crash/orders')">
<el-icon><Tickets /></el-icon> <el-icon><Tickets /></el-icon>
<span>我的订单</span> <span>我的订单</span>
</button> </button>
@@ -135,7 +135,7 @@ async function copyText() {
<h1>{{ statusText }}</h1> <h1>{{ statusText }}</h1>
<p class="sub">订单号 {{ order.order_no }} · {{ formatDateTime(order.created_at) }}</p> <p class="sub">订单号 {{ order.order_no }} · {{ formatDateTime(order.created_at) }}</p>
</div> </div>
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong/orders')"> <button type="button" class="toolbar-btn outline" @click="router.push('/crash/orders')">
返回列表 返回列表
</button> </button>
</header> </header>
@@ -207,7 +207,7 @@ async function copyText() {
<button v-if="order.copy_text" type="button" class="btn outline" @click="copyText"> <button v-if="order.copy_text" type="button" class="btn outline" @click="copyText">
复制订单信息 复制订单信息
</button> </button>
<button type="button" class="btn outline" @click="router.push('/mohong')"> <button type="button" class="btn outline" @click="router.push('/crash')">
继续选购 继续选购
</button> </button>
</aside> </aside>
@@ -34,10 +34,10 @@ async function loadOrders() {
<section class="mohong-orders-page"> <section class="mohong-orders-page">
<header class="page-header"> <header class="page-header">
<div> <div>
<h1>摸大红订单</h1> <h1>撞车订单</h1>
<p class="sub">查看支付状态复制订单信息扫码联系客服</p> <p class="sub">查看支付状态复制订单信息扫码联系客服</p>
</div> </div>
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong')"> <button type="button" class="toolbar-btn outline" @click="router.push('/crash')">
返回商品列表 返回商品列表
</button> </button>
</header> </header>
@@ -69,7 +69,7 @@ async function loadOrders() {
</el-table-column> </el-table-column>
<el-table-column label="操作" width="100" fixed="right" align="center"> <el-table-column label="操作" width="100" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<button type="button" class="link-btn" @click="router.push(`/mohong/orders/${row.id}`)"> <button type="button" class="link-btn" @click="router.push(`/crash/orders/${row.id}`)">
详情 详情
</button> </button>
</template> </template>
@@ -43,7 +43,7 @@ export interface UseOrderPaymentCashierOptions {
/** 弹窗显隐控制方式。PC 用 el-dialogv-model 一个 ref),Mobile 用 van-popup。返回 true 表示用 reffalse 表示 composable 内部 ref。 */ /** 弹窗显隐控制方式。PC 用 el-dialogv-model 一个 ref),Mobile 用 van-popup。返回 true 表示用 reffalse 表示 composable 内部 ref。 */
/** /**
* 自定义支付状态查询。默认走租号订单 `/orders/:id/query-payment`。 * 自定义支付状态查询。默认走租号订单 `/orders/:id/query-payment`。
* 摸大红等独立业务可注入自己的查询函数。 * 撞车等独立业务可注入自己的查询函数。
*/ */
queryPayment?: (orderId: number) => Promise<PaymentOrder> queryPayment?: (orderId: number) => Promise<PaymentOrder>
} }
+8 -8
View File
@@ -105,26 +105,26 @@ const allNavGroups: NavGroup[] = [
permission: 'listing:approve', permission: 'listing:approve',
}, },
{ {
label: '摸大红分类', label: '撞车分类',
to: adminPath('mohong/categories'), to: adminPath('crash/categories'),
icon: Present, icon: Present,
permission: 'mohong:category', permission: 'mohong:category',
}, },
{ {
label: '摸大红商品', label: '撞车商品',
to: adminPath('mohong/products'), to: adminPath('crash/products'),
icon: Present, icon: Present,
permission: 'mohong:product_view', permission: 'mohong:product_view',
}, },
{ {
label: '摸大红订单', label: '撞车订单',
to: adminPath('mohong/orders'), to: adminPath('crash/orders'),
icon: Tickets, icon: Tickets,
permission: 'mohong:order_view', permission: 'mohong:order_view',
}, },
{ {
label: '摸大红配置', label: '撞车配置',
to: adminPath('mohong/config'), to: adminPath('crash/config'),
icon: Setting, icon: Setting,
permission: 'mohong:config', permission: 'mohong:config',
}, },
+8 -8
View File
@@ -73,26 +73,26 @@ export const adminRoutes: RouteRecordRaw[] = [
meta: adminMeta, meta: adminMeta,
}, },
{ {
path: adminPath('mohong/categories'), path: adminPath('crash/categories'),
name: 'admin-mohong-categories', name: 'admin-crash-categories',
component: () => import('@/features/admin/views/AdminMohongCategoriesView.vue'), component: () => import('@/features/admin/views/AdminMohongCategoriesView.vue'),
meta: adminMeta, meta: adminMeta,
}, },
{ {
path: adminPath('mohong/products'), path: adminPath('crash/products'),
name: 'admin-mohong-products', name: 'admin-crash-products',
component: () => import('@/features/admin/views/AdminMohongProductsView.vue'), component: () => import('@/features/admin/views/AdminMohongProductsView.vue'),
meta: adminMeta, meta: adminMeta,
}, },
{ {
path: adminPath('mohong/orders'), path: adminPath('crash/orders'),
name: 'admin-mohong-orders', name: 'admin-crash-orders',
component: () => import('@/features/admin/views/AdminMohongOrdersView.vue'), component: () => import('@/features/admin/views/AdminMohongOrdersView.vue'),
meta: adminMeta, meta: adminMeta,
}, },
{ {
path: adminPath('mohong/config'), path: adminPath('crash/config'),
name: 'admin-mohong-config', name: 'admin-crash-config',
component: () => import('@/features/admin/views/AdminMohongConfigView.vue'), component: () => import('@/features/admin/views/AdminMohongConfigView.vue'),
meta: adminMeta, meta: adminMeta,
}, },
+9 -2
View File
@@ -23,9 +23,13 @@ export function getMobilePath(path: string): string {
if (path === '/listings' || path.startsWith('/listings/')) { if (path === '/listings' || path.startsWith('/listings/')) {
return `/m${path}` return `/m${path}`
} }
if (path === '/mohong' || path.startsWith('/mohong/')) { if (path === '/crash' || path.startsWith('/crash/')) {
return `/m${path}` return `/m${path}`
} }
// 旧摸大红路径 → 移动撞车
if (path === '/mohong' || path.startsWith('/mohong/')) {
return `/m${path.replace(/^\/mohong/, '/crash')}`
}
if (path === '/orders' || path.startsWith('/orders/')) { if (path === '/orders' || path.startsWith('/orders/')) {
return `/m${path}` return `/m${path}`
} }
@@ -71,9 +75,12 @@ export function getPcPath(path: string): string {
if (subPath === '/listings' || subPath.startsWith('/listings/')) { if (subPath === '/listings' || subPath.startsWith('/listings/')) {
return subPath return subPath
} }
if (subPath === '/mohong' || subPath.startsWith('/mohong/')) { if (subPath === '/crash' || subPath.startsWith('/crash/')) {
return subPath return subPath
} }
if (subPath === '/mohong' || subPath.startsWith('/mohong/')) {
return subPath.replace(/^\/mohong/, '/crash')
}
if (subPath === '/orders' || subPath.startsWith('/orders/')) { if (subPath === '/orders' || subPath.startsWith('/orders/')) {
return subPath return subPath
} }
+13 -8
View File
@@ -92,29 +92,34 @@ export const mobileRoutes: RouteRecordRaw[] = [
meta: { layout: 'blank', requiresAuth: true }, meta: { layout: 'blank', requiresAuth: true },
}, },
{ {
path: '/m/mohong', path: '/m/crash',
name: 'mobile-mohong-list', name: 'mobile-crash-list',
component: () => import('@/features/mohong/views/MobileMohongListView.vue'), component: () => import('@/features/mohong/views/MobileMohongListView.vue'),
meta: { layout: 'blank' }, meta: { layout: 'blank' },
}, },
{ {
path: '/m/mohong/orders', path: '/m/crash/orders',
name: 'mobile-mohong-orders', name: 'mobile-crash-orders',
component: () => import('@/features/mohong/views/MobileMohongOrdersView.vue'), component: () => import('@/features/mohong/views/MobileMohongOrdersView.vue'),
meta: { layout: 'blank', requiresAuth: true }, meta: { layout: 'blank', requiresAuth: true },
}, },
{ {
path: '/m/mohong/orders/:id', path: '/m/crash/orders/:id',
name: 'mobile-mohong-order-detail', name: 'mobile-crash-order-detail',
component: () => import('@/features/mohong/views/MobileMohongOrderDetailView.vue'), component: () => import('@/features/mohong/views/MobileMohongOrderDetailView.vue'),
meta: { layout: 'blank', requiresAuth: true }, meta: { layout: 'blank', requiresAuth: true },
}, },
{ {
path: '/m/mohong/:id', path: '/m/crash/:id',
name: 'mobile-mohong-detail', name: 'mobile-crash-detail',
component: () => import('@/features/mohong/views/MobileMohongDetailView.vue'), component: () => import('@/features/mohong/views/MobileMohongDetailView.vue'),
meta: { layout: 'blank' }, meta: { layout: 'blank' },
}, },
// 旧路径兼容
{ path: '/m/mohong', redirect: '/m/crash' },
{ path: '/m/mohong/orders', redirect: '/m/crash/orders' },
{ path: '/m/mohong/orders/:id', redirect: to => `/m/crash/orders/${to.params.id}` },
{ path: '/m/mohong/:id', redirect: to => `/m/crash/${to.params.id}` },
{ {
path: '/m/orders', path: '/m/orders',
name: 'mobile-orders', name: 'mobile-orders',
+14 -9
View File
@@ -16,27 +16,32 @@ export const publicRoutes: RouteRecordRaw[] = [
name: 'listing-detail', name: 'listing-detail',
component: () => import('@/features/listings/views/ListingDetailView.vue'), component: () => import('@/features/listings/views/ListingDetailView.vue'),
}, },
// 摸大红 PC 端;移动端访问会被守卫重写到 /m/mohong* // 撞车 PC 端;移动端访问会被守卫重写到 /m/crash*
{ {
path: '/mohong', path: '/crash',
name: 'mohong-list', name: 'crash-list',
component: () => import('@/features/mohong/views/MohongListView.vue'), component: () => import('@/features/mohong/views/MohongListView.vue'),
}, },
{ {
path: '/mohong/orders', path: '/crash/orders',
name: 'mohong-orders', name: 'crash-orders',
component: () => import('@/features/mohong/views/MohongOrdersView.vue'), component: () => import('@/features/mohong/views/MohongOrdersView.vue'),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
}, },
{ {
path: '/mohong/orders/:id', path: '/crash/orders/:id',
name: 'mohong-order-detail', name: 'crash-order-detail',
component: () => import('@/features/mohong/views/MohongOrderDetailView.vue'), component: () => import('@/features/mohong/views/MohongOrderDetailView.vue'),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
}, },
{ {
path: '/mohong/:id', path: '/crash/:id',
name: 'mohong-detail', name: 'crash-detail',
component: () => import('@/features/mohong/views/MohongDetailView.vue'), component: () => import('@/features/mohong/views/MohongDetailView.vue'),
}, },
// 旧路径兼容
{ path: '/mohong', redirect: '/crash' },
{ path: '/mohong/orders', redirect: '/crash/orders' },
{ path: '/mohong/orders/:id', redirect: to => `/crash/orders/${to.params.id}` },
{ path: '/mohong/:id', redirect: to => `/crash/${to.params.id}` },
] ]