更新小程序和管理后台配置,将 API 地址切换为生产环境。新增撤回打款功能,允许用户在特定状态下撤回打款请求,并优化相关页面交互。更新文档以反映新的流程图,确保用户体验一致性。
This commit is contained in:
@@ -13,8 +13,8 @@ const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
|
|||||||
App({
|
App({
|
||||||
globalData: {
|
globalData: {
|
||||||
// API 基础地址:开发时修改下面一行切换环境
|
// API 基础地址:开发时修改下面一行切换环境
|
||||||
// baseUrl: "https://soulapi.quwanzhi.com",
|
baseUrl: "https://soulapi.quwanzhi.com",
|
||||||
baseUrl: 'http://localhost:8080', // 开发
|
// baseUrl: 'http://localhost:8080', // 开发
|
||||||
// baseUrl: 'https://souldev.quwanzhi.com', // 测试
|
// baseUrl: 'https://souldev.quwanzhi.com', // 测试
|
||||||
// 小程序配置 - 真实AppID
|
// 小程序配置 - 真实AppID
|
||||||
appId: DEFAULT_APP_ID,
|
appId: DEFAULT_APP_ID,
|
||||||
|
|||||||
@@ -4,6 +4,48 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 〇、总览(全流程)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([触发]) --> Trigger{触发来源}
|
||||||
|
Trigger -->|onLaunch/onShow/登录| Main
|
||||||
|
Trigger -->|登录返回 isNewUser| LoginBranch
|
||||||
|
Trigger -->|VIP支付成功| VipPay
|
||||||
|
|
||||||
|
LoginBranch[登录成功] --> NewUser{isNewUser?}
|
||||||
|
NewUser -->|否| Main
|
||||||
|
NewUser -->|是| NeedAN1{头像昵称未改?}
|
||||||
|
NeedAN1 -->|是| ForceA[redirectTo avatar-nickname<br/>无弹窗]
|
||||||
|
NeedAN1 -->|否| Main
|
||||||
|
|
||||||
|
VipPay[VIP支付成功] --> Sync[同步权益] --> ModalV[弹窗:请填写好资料] --> RedirV[redirectTo profile-edit?from=vip]
|
||||||
|
|
||||||
|
Main[checkVipContactRequiredAndGuide] --> Skip{在 profile-edit<br/>或 avatar-nickname?}
|
||||||
|
Skip -->|是| End1([结束])
|
||||||
|
Skip -->|否| Fetch[请求 VIP + profile]
|
||||||
|
Fetch --> IsVip{VIP?}
|
||||||
|
|
||||||
|
IsVip -->|否| AvatarFlow[checkAvatarNicknameAndGuide]
|
||||||
|
AvatarFlow --> A1{头像昵称已完善?}
|
||||||
|
A1 -->|是| End2([结束])
|
||||||
|
A1 -->|否| A2{今日已提示?}
|
||||||
|
A2 -->|是| End2
|
||||||
|
A2 -->|否| MA[弹窗:请设置头像和昵称] --> NavA[navigateTo avatar-nickname]
|
||||||
|
|
||||||
|
IsVip -->|是| B1{头像昵称未改?}
|
||||||
|
B1 -->|是| M1[弹窗:请完善资料] --> R1[redirectTo profile-edit]
|
||||||
|
B1 -->|否| B2{有手机号?}
|
||||||
|
B2 -->|否| M2[弹窗:VIP需完善手机号] --> R2[redirectTo profile-edit]
|
||||||
|
B2 -->|是| B3{有微信号?}
|
||||||
|
B3 -->|否| M3[弹窗:请完善微信号] --> N1[navigateTo profile-edit]
|
||||||
|
B3 -->|是| End3([结束])
|
||||||
|
```
|
||||||
|
|
||||||
|
**触发时机**:onLaunch 1.5s | onShow 0.5s(节流 5min)| 登录成功 1.2s | VIP 支付成功 即时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 一、整体流程(冷启动 / onShow)
|
## 一、整体流程(冷启动 / onShow)
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# 对接后端 base URL(不改 API 路径,仅改此处即可切换 Next → Gin)
|
# 对接后端 base URL(不改 API 路径,仅改此处即可切换 Next → Gin)
|
||||||
# 宝塔部署:若 API 站点开启了强制 HTTPS,这里必须用 https,否则预检请求会被重定向导致 CORS 报错
|
# 宝塔部署:若 API 站点开启了强制 HTTPS,这里必须用 https,否则预检请求会被重定向导致 CORS 报错
|
||||||
# VITE_API_BASE_URL=http://localhost:3006
|
# VITE_API_BASE_URL=http://localhost:3006
|
||||||
VITE_API_BASE_URL=http://localhost:8080
|
# VITE_API_BASE_URL=http://localhost:8080
|
||||||
# VITE_API_BASE_URL=https://soulapi.quwanzhi.com
|
VITE_API_BASE_URL=https://soulapi.quwanzhi.com
|
||||||
|
|
||||||
# VITE_API_BASE_URL=https://souldev.quwanzhi.com
|
# VITE_API_BASE_URL=https://souldev.quwanzhi.com
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
2
soul-admin/dist/index.html
vendored
2
soul-admin/dist/index.html
vendored
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>管理后台 - Soul创业派对</title>
|
<title>管理后台 - Soul创业派对</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DCoaVA6V.js"></script>
|
<script type="module" crossorigin src="/assets/index-ByhYXHAh.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DGXqHqcA.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DGXqHqcA.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
import { get, put } from '@/api/client'
|
import { get, put, post } from '@/api/client'
|
||||||
|
|
||||||
interface TodayClicksByPageItem {
|
interface TodayClicksByPageItem {
|
||||||
page: string
|
page: string
|
||||||
@@ -387,6 +387,25 @@ export function DistributionPage() {
|
|||||||
setRejectReason('')
|
setRejectReason('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleCancelWithdrawal(id: string) {
|
||||||
|
if (!confirm('确认撤回该笔打款?资金将退回商户运营账户。')) return
|
||||||
|
try {
|
||||||
|
const res = await post<{ success?: boolean; error?: string; message?: string }>(
|
||||||
|
'/api/admin/withdrawals/cancel',
|
||||||
|
{ id },
|
||||||
|
)
|
||||||
|
if (!res?.success) {
|
||||||
|
toast.error(res?.error || res?.message || '撤回失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toast.success('已撤回打款')
|
||||||
|
await refreshCurrentTab()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
toast.error('撤回失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submitRejectWithdrawal() {
|
async function submitRejectWithdrawal() {
|
||||||
const id = rejectWithdrawalId
|
const id = rejectWithdrawalId
|
||||||
if (!id) return
|
if (!id) return
|
||||||
@@ -1354,6 +1373,19 @@ export function DistributionPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(withdrawal.status === 'pending_confirm' || withdrawal.status === 'processing') && (
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleCancelWithdrawal(withdrawal.id)}
|
||||||
|
className="border-amber-500/50 text-amber-400 hover:bg-amber-500/20"
|
||||||
|
>
|
||||||
|
<Undo2 className="w-4 h-4 mr-1" />
|
||||||
|
撤回打款
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -351,6 +351,45 @@ func AdminWithdrawalsAction(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminWithdrawalsCancel POST /api/admin/withdrawals/cancel 撤回打款(仅 processing/pending_confirm 可撤回)
|
||||||
|
func AdminWithdrawalsCancel(c *gin.Context) {
|
||||||
|
var body struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&body); err != nil || body.ID == "" {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db := database.DB()
|
||||||
|
var w model.Withdrawal
|
||||||
|
if err := db.Where("id = ?", body.ID).First(&w).Error; err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": false, "error": "提现记录不存在"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st := ""
|
||||||
|
if w.Status != nil {
|
||||||
|
st = *w.Status
|
||||||
|
}
|
||||||
|
if st != "processing" && st != "pending_confirm" {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": false, "error": "当前状态不允许撤回,仅待确认收款时可撤回"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outBillNo := body.ID
|
||||||
|
if w.DetailNo != nil && *w.DetailNo != "" {
|
||||||
|
outBillNo = *w.DetailNo
|
||||||
|
}
|
||||||
|
if err := wechat.CancelTransferByOutBill(outBillNo); err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": false, "error": "撤回失败: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
reason := "商户已撤回"
|
||||||
|
_ = db.Model(&w).Updates(map[string]interface{}{
|
||||||
|
"status": "failed", "fail_reason": reason, "error_message": reason, "processed_at": now,
|
||||||
|
}).Error
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "已撤回打款"})
|
||||||
|
}
|
||||||
|
|
||||||
// AdminWithdrawalsSync POST /api/admin/withdrawals/sync 主动向微信查询转账结果并更新状态(无回调时的备选)
|
// AdminWithdrawalsSync POST /api/admin/withdrawals/sync 主动向微信查询转账结果并更新状态(无回调时的备选)
|
||||||
// body: { "id": "提现记录id" } 同步单条;不传 id 或 id 为空则同步所有 processing/pending_confirm
|
// body: { "id": "提现记录id" } 同步单条;不传 id 或 id 为空则同步所有 processing/pending_confirm
|
||||||
func AdminWithdrawalsSync(c *gin.Context) {
|
func AdminWithdrawalsSync(c *gin.Context) {
|
||||||
|
|||||||
@@ -118,7 +118,14 @@ func WithdrawPost(c *gin.Context) {
|
|||||||
if err := db.Where("config_key = ?", "referral_config").First(&refCfg).Error; err == nil {
|
if err := db.Where("config_key = ?", "referral_config").First(&refCfg).Error; err == nil {
|
||||||
var config map[string]interface{}
|
var config map[string]interface{}
|
||||||
if _ = json.Unmarshal(refCfg.ConfigValue, &config); config != nil {
|
if _ = json.Unmarshal(refCfg.ConfigValue, &config); config != nil {
|
||||||
if enabled, ok := config["enableAutoWithdraw"].(bool); ok && enabled {
|
enabled := false
|
||||||
|
switch v := config["enableAutoWithdraw"].(type) {
|
||||||
|
case bool:
|
||||||
|
enabled = v
|
||||||
|
case float64:
|
||||||
|
enabled = v != 0
|
||||||
|
}
|
||||||
|
if enabled {
|
||||||
go func(id string) {
|
go func(id string) {
|
||||||
if _, e := doApproveWithdrawal(db, id); e != nil {
|
if _, e := doApproveWithdrawal(db, id); e != nil {
|
||||||
fmt.Printf("[WithdrawPost] 自动审批失败 id=%s: %v\n", id, e)
|
fmt.Printf("[WithdrawPost] 自动审批失败 id=%s: %v\n", id, e)
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ func Setup(cfg *config.Config) *gin.Engine {
|
|||||||
admin.DELETE("/referral", handler.AdminReferral)
|
admin.DELETE("/referral", handler.AdminReferral)
|
||||||
admin.GET("/withdrawals", handler.AdminWithdrawalsList)
|
admin.GET("/withdrawals", handler.AdminWithdrawalsList)
|
||||||
admin.PUT("/withdrawals", handler.AdminWithdrawalsAction)
|
admin.PUT("/withdrawals", handler.AdminWithdrawalsAction)
|
||||||
|
admin.POST("/withdrawals/cancel", handler.AdminWithdrawalsCancel)
|
||||||
admin.POST("/withdrawals/sync", handler.AdminWithdrawalsSync)
|
admin.POST("/withdrawals/sync", handler.AdminWithdrawalsSync)
|
||||||
admin.GET("/withdrawals/auto-approve", handler.AdminWithdrawalsAutoApproveGet)
|
admin.GET("/withdrawals/auto-approve", handler.AdminWithdrawalsAutoApproveGet)
|
||||||
admin.PUT("/withdrawals/auto-approve", handler.AdminWithdrawalsAutoApprovePut)
|
admin.PUT("/withdrawals/auto-approve", handler.AdminWithdrawalsAutoApprovePut)
|
||||||
|
|||||||
@@ -213,6 +213,17 @@ func InitiateTransferByFundApp(params FundAppTransferParams) (*FundAppTransferRe
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelTransferByOutBill 撤销转账(用户确认收款前,商户可主动撤回)
|
||||||
|
// 接口:POST /v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel
|
||||||
|
func CancelTransferByOutBill(outBillNo string) error {
|
||||||
|
if paymentApp == nil || paymentApp.FundApp == nil {
|
||||||
|
return fmt.Errorf("支付/转账未初始化")
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := paymentApp.FundApp.Cancel(ctx, outBillNo)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// QueryTransferByOutBill 按商户单号查询单笔转账结果(FundApp 接口,用于 sync)
|
// QueryTransferByOutBill 按商户单号查询单笔转账结果(FundApp 接口,用于 sync)
|
||||||
func QueryTransferByOutBill(outBillNo string) (state, transferBillNo, failReason string, err error) {
|
func QueryTransferByOutBill(outBillNo string) (state, transferBillNo, failReason string, err error) {
|
||||||
if paymentApp == nil || paymentApp.FundApp == nil {
|
if paymentApp == nil || paymentApp.FundApp == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user