Browse Source

fix:提现

super-yimizi 14 hours ago
parent
commit
c2e5e31f06

+ 173 - 0
application/admin/controller/withdraw/Config.php

@@ -0,0 +1,173 @@
+<?php
+
+namespace app\admin\controller\withdraw;
+
+use app\common\controller\Backend;
+use app\common\Service\ShopConfigService;
+use app\common\Enum\WithdrawEnum;
+use think\exception\PDOException;
+use think\exception\ValidateException;
+
+/**
+ * 提现配置管理
+ *
+ * @icon fa fa-credit-card
+ */
+class Config extends Backend
+{
+
+    /**
+     * Config模型对象
+     * 
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+    }
+
+    /**
+     * 查看提现配置
+     */
+    public function index()
+    {
+        // 获取提现配置
+        $withdrawConfig = ShopConfigService::getConfigs('shop.withdraw');
+        
+        // 设置默认值
+        $defaultConfig = [
+            'min_amount' => '100.00',           // 单次最小提现金额
+            'max_amount' => '1000.00',          // 单次最大提现金额
+            'charge_rate' => '1',               // 手续费率(%)
+            'max_num' => '10',                  // 最多提现次数
+            'num_unit' => 'month',              // 提现次数单位(day/month)
+            'auto_arrival' => 0,                // 支付宝自动到账开关 (0=关闭, 1=开启)
+            'methods' => ['wechat', 'alipay', 'bank']  // 支持的提现方式
+        ];
+        
+        $withdrawConfig = array_merge($defaultConfig, $withdrawConfig ?: []);
+        
+        // 确保数组类型的配置项是数组
+        if (!is_array($withdrawConfig['methods'])) {
+            $withdrawConfig['methods'] = ['wechat', 'alipay', 'bank'];
+        }
+        
+        // 确保布尔值类型正确
+        $withdrawConfig['auto_arrival'] = (int)$withdrawConfig['auto_arrival'];
+        
+        // 获取提现方式和状态列表用于前端显示
+        $this->view->assign([
+            'withdrawConfig' => $withdrawConfig,
+            'withdrawTypeList' => WithdrawEnum::getTypeList(),
+            'statusList' => WithdrawEnum::getStatusList()
+        ]);
+        
+        return $this->view->fetch();
+    }
+    
+    /**
+     * 编辑配置
+     */
+    public function edit($ids = null)
+    {
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a", [], 'trim');
+            
+            if ($params) {
+                // try {
+                    // 处理布尔类型的自动到账开关
+                    $params['auto_arrival'] = isset($params['auto_arrival']) ? (int)$params['auto_arrival'] : 0;
+                    
+                    // 处理数组类型的提现方式配置
+                    if (isset($params['methods']) && is_array($params['methods'])) {
+                        $params['methods'] = $params['methods'];
+                    } else {
+                        $params['methods'] = [];
+                    }
+
+                    // 验证数据
+                    $this->validateConfig($params);
+
+                    // 更新配置
+                    ShopConfigService::setConfigs('shop.withdraw', $params);
+                    $this->success('配置保存成功');
+                // } catch (\Exception $e) {
+                //     $this->error($e->getMessage());
+                // }
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+    }
+    
+    /**
+     * 验证配置参数
+     * @param array $params
+     * @throws ValidateException
+     */
+    private function validateConfig($params)
+    {
+        // 验证最小金额
+        if (isset($params['min_amount']) && !is_numeric($params['min_amount'])) {
+            $this->error('最小提现金额必须为数字');
+        }
+        
+        // 验证最大金额
+        if (isset($params['max_amount']) && !is_numeric($params['max_amount'])) {
+            $this->error('最大提现金额必须为数字');
+        }
+        
+        // 验证最小金额不能大于最大金额
+        if (isset($params['min_amount']) && isset($params['max_amount'])) {
+            $minAmount = $params['min_amount'];
+            $maxAmount = $params['max_amount'];
+            
+            // 确保都是有效的数字
+            if (is_numeric($minAmount) && is_numeric($maxAmount) && $minAmount > 0 && $maxAmount > 0) {
+                if (bccomp((string)$minAmount, (string)$maxAmount, 2) > 0) {
+                    throw new ValidateException('最小提现金额不能大于最大提现金额');
+                }
+            }
+        }
+        
+        // 验证手续费率
+        if (isset($params['charge_rate'])) {
+            $chargeRate = (float)$params['charge_rate'];
+            if (!is_numeric($params['charge_rate']) || $chargeRate < 0 || $chargeRate > 100) {
+                $this->error('手续费率必须为0-100之间的数字');
+            }
+        }
+        
+        // 验证提现次数
+        if (isset($params['max_num'])) {
+            $maxNum = (int)$params['max_num'];
+            if (!is_numeric($params['max_num']) || $maxNum < 0) {
+                $this->error('提现次数必须为正整数');
+            }
+        }
+        
+        // 验证提现次数单位
+        if (isset($params['num_unit']) && !in_array($params['num_unit'], ['day', 'month'])) {
+            $this->error('提现次数单位只能为day或month');
+        }
+        
+        // 验证提现方式
+        if (isset($params['methods']) && is_array($params['methods'])) {
+            $validMethods = array_keys(WithdrawEnum::getTypeList());
+            foreach ($params['methods'] as $method) {
+                if (!in_array($method, $validMethods)) {
+                    $this->error('请至少选择一种提现方式');
+                }
+            }
+        }
+        
+        // 验证自动到账开关
+        if (isset($params['auto_arrival'])) {
+            $autoArrival = (int)$params['auto_arrival'];
+            if (!in_array($autoArrival, [0, 1])) {
+                $this->error('自动到账设置不合法');
+            }
+        }
+    }
+}

+ 287 - 0
application/admin/controller/withdraw/Withdraw.php

@@ -0,0 +1,287 @@
+<?php
+
+namespace app\admin\controller\withdraw;
+
+use app\common\controller\Backend;
+use app\common\model\Withdraw as WithdrawModel;
+use app\common\model\WithdrawLog as WithdrawLogModel;
+use app\common\model\User;
+use app\admin\model\Admin as AdminModel;
+use app\common\Service\Withdraw as WithdrawService;
+use app\common\Enum\WithdrawEnum;
+use app\common\library\Operator;
+use think\Db;
+use think\exception\HttpResponseException;
+use app\common\exception\BusinessException;
+
+/**
+ * 提现管理
+ *
+ * @icon fa fa-money
+ */
+class Withdraw extends Backend
+{
+    /**
+     * Withdraw模型对象
+     * @var \app\common\model\Withdraw
+     */
+    protected $model = null;
+
+    protected $logModel = null;
+    protected $noNeedRight = ['checkWechatTransferResult'];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new WithdrawModel;
+        $this->logModel = new WithdrawLogModel;
+        
+        // 只显示需要的状态
+        $statusList = [
+            '0' => '待审核',
+            '1' => '处理中',
+            '2' => '已处理',
+            '-1' => '已拒绝'
+        ];
+        $this->view->assign("statusList", $statusList);
+        $this->view->assign("typeList", WithdrawEnum::getTypeList());
+        $this->view->assign("wechatTransferStateList", WithdrawEnum::getWechatTransferStateList());
+    }
+
+
+    /**
+     * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法
+     * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
+     * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
+     */
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //当前是否为关联查询
+        $this->relationSearch = true;
+        //设置过滤方法
+        $this->request->filter(['strip_tags', 'trim']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            
+            $total = $this->model
+                ->with(['user'])
+                ->where($where)
+                ->order($sort, $order)
+                ->count();
+
+            $list = $this->model
+                ->with(['user'])
+                ->where($where)
+                ->order($sort, $order)
+                ->limit($offset, $limit)
+                ->select();
+
+            // 处理数据显示
+            foreach ($list as $row) {
+                $row->status_text = WithdrawEnum::getStatusName($row->status);
+               // $row->withdraw_type_text = WithdrawEnum::getTypeName($row->withdraw_type);
+                $row->wechat_transfer_state_text = WithdrawEnum::getWechatTransferStateName($row->wechat_transfer_state);
+                // 安全处理手续费率格式化
+                $chargeRate = $row->charge_rate;
+                if (is_numeric($chargeRate) && $chargeRate !== null && $chargeRate !== '') {
+                    $row->charge_rate_format = bcmul((string)$chargeRate, '100', 1) . '%';
+                } else {
+                    $row->charge_rate_format = '0%';
+                }
+                
+                // 处理用户信息
+                if ($row->user) {
+                    $row->user->nickname = $row->user->nickname ?: '未知用户';
+                }
+            }
+
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 处理提现申请
+     */
+    public function handle($ids = null)
+    {
+        if ($this->request->isPost()) {
+            $params = $this->request->param();
+            $action = $params['action'] ?? null;
+            
+            if (!in_array($action, ['agree', 'withdraw', 'agree_and_withdraw', 'refuse', 'refund_commission', 'cancel'])) {
+                $this->error('您的操作有误');
+            }
+
+            $refuse_msg = $params['refuse_msg'] ?? '';
+            if ($action === 'refuse' && !$refuse_msg) {
+                $this->error('请输入拒绝原因');
+            }
+
+            $ids = is_array($ids) ? $ids : explode(',', $ids);
+            
+            foreach ($ids as $id) {
+                Db::startTrans();
+                try {
+                    $withdraw = $this->model->lock(true)->where('id', $id)->find();
+                    if (!$withdraw) {
+                        throw new BusinessException(__('No Results were found'));
+                    }
+                    
+                    $withdrawService = new WithdrawService($withdraw->user_id);
+
+                    switch ($action) {
+                        case 'agree':
+                            $withdraw = $withdrawService->handleAgree($withdraw);
+                            break;
+                        case 'withdraw':
+                            $withdraw = $withdrawService->handleWithdraw($withdraw);
+                            break;
+                        case 'agree_and_withdraw':
+                            $withdraw = $withdrawService->handleAgree($withdraw);
+                            $withdraw = $withdrawService->handleWithdraw($withdraw);
+                            break;
+                        case 'refuse':
+                            $withdraw = $withdrawService->handleRefuse($withdraw, $refuse_msg);
+                            break;
+                        case 'refund_commission':
+                            $withdraw = $withdrawService->handleFail($withdraw, '管理员退回佣金', -3);
+                            break;
+                        case 'cancel':
+                            $withdraw = $withdrawService->handleFail($withdraw, '管理员撤销提现', -3);
+                            break;
+                    }
+
+                    Db::commit();
+                } catch (BusinessException $e) {
+                    // 不回滚,记录错误日志
+                    Db::commit();
+                    $this->error($e->getMessage());
+                } catch (HttpResponseException $e) {
+                    $data = $e->getResponse()->getData();
+                    $message = $data ? ($data['msg'] ?? '') : $e->getMessage();
+                    $this->error($message);
+                } catch (\Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+            }
+
+            $this->success('处理成功');
+        }
+        
+        // GET请求 - 显示处理表单(用于拒绝操作)
+        $withdraw = $this->model->get($ids);
+        if (!$withdraw) {
+            $this->error(__('No Results were found'));
+        }
+        
+        $this->view->assign("withdraw", $withdraw);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 查看提现日志
+     */
+    public function log($ids = null)
+    {
+        $withdraw = $this->model->get($ids);
+        if (!$withdraw) {
+            $this->error(__('No Results were found'));
+        }
+
+        // 直接获取日志数据,不使用 Ajax
+        $logs = $this->logModel->where('withdraw_id', $ids)->order('id desc')->select();
+        
+        // 处理操作人信息
+        foreach ($logs as $log) {
+            switch ($log->oper_type) {
+                case 'user':
+                    $oper = User::get($log->oper_id);
+                    $log->oper_info = [
+                        'type' => 'user',
+                        'id' => $log->oper_id,
+                        'name' => $oper ? $oper->nickname : '未知用户',
+                        'avatar' => $oper ? $oper->avatar : ''
+                    ];
+                    break;
+                case 'admin':
+                case 'system':
+                    $oper = AdminModel::get($log->oper_id);
+                    $log->oper_info = [
+                        'type' => $log->oper_type,
+                        'id' => $log->oper_id,
+                        'name' => $oper ? $oper->username : '系统',
+                        'avatar' => $oper ? $oper->avatar : ''
+                    ];
+                    break;
+                default:
+                    $log->oper_info = [
+                        'type' => 'system',
+                        'id' => 0,
+                        'name' => '系统',
+                        'avatar' => ''
+                    ];
+                    break;
+            }
+        }
+        
+        $this->view->assign("withdraw", $withdraw);
+        $this->view->assign("logs", $logs);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 检查微信转账结果
+     */
+    public function checkWechatTransferResult($id)
+    {
+        $withdraw = $this->model->lock(true)->where('id', $id)->find();
+        if (!$withdraw) {
+            $this->error(__('No Results were found'));
+        }
+
+        try {
+            $withdrawService = new WithdrawService($withdraw->user_id);
+            $withdrawService->checkWechatTransferResult($withdraw);
+            $this->success('检查成功');
+        } catch (\Exception $e) {
+            $this->error($e->getMessage());
+        }
+    }
+
+    /**
+     * 批量删除
+     */
+    public function del($ids = "")
+    {
+        $this->error('提现记录不允许删除');
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        $this->error('提现记录不允许手动添加');
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $this->error('提现记录不允许编辑');
+    }
+}

+ 54 - 0
application/admin/lang/zh-cn/withdraw/config.php

@@ -0,0 +1,54 @@
+<?php
+
+return [
+    'Id'                                                    => 'ID',
+    'Withdraw Config'                                       => '提现配置',
+    'Basic configuration'                                   => '基础配置',
+    'Withdrawal Setting'                                    => '提现设置',
+    'Method setting'                                        => '方式设置',
+    
+    // 配置项目
+    'Minimum amount'                                        => '最低提现金额',
+    'Maximum amount'                                        => '最高提现金额',
+    'Charge rate'                                          => '手续费率',
+    'Max withdraw times'                                    => '最大提现次数',
+    'Times unit'                                           => '次数单位',
+    'Withdrawal methods'                                    => '提现方式',
+    'Auto arrival'                                          => '自动到账',
+    
+    // 提示文字
+    'Minimum amount tips'                                   => '设置为0表示不限制最小金额',
+    'Maximum amount tips'                                   => '设置为0表示不限制最大金额',
+    'Charge rate tips'                                     => '例如:设置1表示收取1%的手续费',
+    'Max withdraw times tips'                              => '设置为0表示不限制提现次数',
+    'Times unit tips'                                      => '提现次数限制的时间单位',
+    'Withdrawal methods tips'                              => '选择支持的提现方式',
+    'Auto arrival tips'                                    => '开启后支付宝提现将直接审核通过并自动打款;微信零钱提现只支持用户自助提现,自动到账;手动打款需商户自行线下打款',
+    
+    // 单位和选项
+    'Yuan'                                                 => '元',
+    'Percent'                                              => '%',
+    'Times'                                                => '次',
+    'Day'                                                  => '每天',
+    'Month'                                                => '每月',
+    
+    // 提现方式
+    'Wechat'                                               => '微信零钱',
+    'Alipay'                                               => '支付宝账户',
+    'Bank'                                                 => '银行卡',
+    
+    // 按钮和操作
+    'Enable'                                               => '开启',
+    'Disable'                                              => '关闭',
+    'Save'                                                 => '保存',
+    'Reset'                                                => '重置',
+    
+    // 消息提示
+    'Config saved successfully'                            => '配置保存成功',
+    'Config save failed'                                   => '配置保存失败',
+    'Invalid configuration'                                => '配置参数不合法',
+    'Minimum amount must be greater than 0'               => '最低提现金额必须大于0',
+    'Maximum amount must be greater than minimum amount'   => '最高提现金额必须大于最低提现金额',
+    'Charge rate must be between 0 and 100'              => '手续费率必须在0-100之间',
+    'Max withdraw times must be greater than 0'           => '最大提现次数必须大于0',
+];

+ 95 - 0
application/admin/lang/zh-cn/withdraw/withdraw.php

@@ -0,0 +1,95 @@
+<?php
+
+return [
+    'Id'                                                    => 'ID',
+    'Withdraw'                                              => '提现',
+    'Withdraw List'                                         => '提现列表',
+    'Withdraw Handle'                                       => '提现处理',
+    'Withdraw Log'                                          => '提现日志',
+    
+    // 字段名称
+    'User id'                                               => '用户ID',
+    'Username'                                              => '用户名',
+    'Nickname'                                              => '昵称',
+    'Mobile'                                                => '手机号',
+    'Amount'                                                => '提现金额',
+    'Fee'                                                   => '手续费',
+    'Real amount'                                           => '实际到账',
+    'Type'                                                  => '提现方式',
+    'Account'                                               => '提现账户',
+    'Realname'                                              => '真实姓名',
+    'Status'                                                => '状态',
+    'Reason'                                                => '原因',
+    'Remark'                                                => '备注',
+    'Create time'                                           => '申请时间',
+    'Update time'                                           => '处理时间',
+    'Handle time'                                           => '处理时间',
+    'Handle admin'                                          => '处理管理员',
+    
+    // 提现方式
+    'Wechat'                                                => '微信零钱',
+    'Alipay'                                                => '支付宝',
+    'Bank'                                                  => '银行卡',
+    
+    // 状态
+    'Status list'                                           => '状态列表',
+    'Canceled'                                              => '撤销提现',
+    'Failed'                                                => '提现失败',
+    'Rejected'                                              => '已拒绝',
+    'Pending'                                               => '待审核',
+    'Processing'                                            => '处理中',
+    'Completed'                                             => '已处理',
+    
+    // 微信转账状态
+    'Wechat transfer state'                                 => '微信转账状态',
+    'Transfer processing'                                   => '处理中',
+    'Transfer success'                                      => '转账成功',
+    'Transfer failed'                                       => '转账失败',
+    'Transfer closed'                                       => '转账关闭',
+    
+    // 操作按钮
+    'Handle'                                                => '处理',
+    'Agree'                                                 => '同意',
+    'Refuse'                                                => '拒绝',
+    'Cancel'                                                => '撤销',
+    'Withdraw operation'                                    => '提现操作',
+    'Refund commission'                                     => '退还佣金',
+    'Check wechat result'                                   => '查询微信结果',
+    'View log'                                              => '查看日志',
+    
+    // 处理表单
+    'Handle withdraw'                                       => '处理提现',
+    'Agree withdraw'                                        => '同意提现',
+    'Refuse withdraw'                                       => '拒绝提现',
+    'Refuse reason'                                         => '拒绝原因',
+    'Handle remark'                                         => '处理备注',
+    
+    // 提示消息
+    'Withdraw handled successfully'                         => '提现处理成功',
+    'Withdraw handle failed'                                => '提现处理失败',
+    'Withdraw agreed successfully'                          => '提现同意成功',
+    'Withdraw refused successfully'                         => '提现拒绝成功',
+    'Withdraw canceled successfully'                        => '提现撤销成功',
+    'Commission refunded successfully'                      => '佣金退还成功',
+    'Wechat result checked successfully'                    => '微信结果查询成功',
+    'Invalid withdraw id'                                   => '无效的提现ID',
+    'Withdraw not found'                                    => '提现记录不存在',
+    'Withdraw status error'                                 => '提现状态错误',
+    'Please enter refuse reason'                            => '请输入拒绝原因',
+    'Operation not allowed'                                 => '不允许此操作',
+    
+    // 搜索和筛选
+    'Search'                                                => '搜索',
+    'Filter'                                                => '筛选',
+    'All status'                                            => '全部状态',
+    'All types'                                             => '全部方式',
+    'Start date'                                            => '开始日期',
+    'End date'                                              => '结束日期',
+    'Amount range'                                          => '金额范围',
+    
+    // 统计信息
+    'Total amount'                                          => '总金额',
+    'Total count'                                           => '总数量',
+    'Success rate'                                          => '成功率',
+    'Average amount'                                        => '平均金额',
+];

+ 108 - 0
application/admin/view/withdraw/config/index.html

@@ -0,0 +1,108 @@
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="active"><a href="#one" data-toggle="tab">{:__('Basic configuration')}</a></li>
+        </ul>
+    </div>
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <form id="config-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="{:url('withdraw/config/edit')}">
+                    {:token()}
+                    <!-- 基础设置 -->
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Minimum amount')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <input type="number" step="0.01" min="0" name="row[min_amount]" value="{$withdrawConfig.min_amount|default=''}" class="form-control" placeholder="单次最小提现金额(元)">
+                                <span class="help-block">{:__('Minimum amount tips')}</span>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Maximum amount')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <input type="number" step="0.01" min="0" name="row[max_amount]" value="{$withdrawConfig.max_amount|default=''}" class="form-control" placeholder="单次最大提现金额(元)">
+                                <span class="help-block">{:__('Maximum amount tips')}</span>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Charge rate')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="input-group">
+                                    <input type="number" step="0.1" min="0" max="100" name="row[charge_rate]" value="{$withdrawConfig.charge_rate|default=''}" class="form-control" placeholder="手续费率">
+                                    <span class="input-group-addon">%</span>
+                                </div>
+                                <span class="help-block">{:__('Charge rate tips')}</span>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Max withdraw times')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="row">
+                                    <div class="col-sm-6">
+                                        <input type="number" min="0" name="row[max_num]" value="{$withdrawConfig.max_num|default=''}" class="form-control" placeholder="最多提现次数">
+                                    </div>
+                                    <div class="col-sm-6">
+                                        <select name="row[num_unit]" class="form-control">
+                                            <option value="day" {if condition="$withdrawConfig.num_unit eq 'day'"}selected{/if}>{:__('Day')}</option>
+                                            <option value="month" {if condition="$withdrawConfig.num_unit eq 'month'"}selected{/if}>{:__('Month')}</option>
+                                        </select>
+                                    </div>
+                                </div>
+                                <span class="help-block">{:__('Max withdraw times tips')}</span>
+                            </div>
+                        </div>
+                    
+                    <!-- 提现方式设置 -->
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Withdrawal methods')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                {foreach name="withdrawTypeList" item="type" key="key"}
+                                <label class="checkbox-inline">
+                                    <input type="checkbox" name="row[methods][]" value="{$key}" {if condition="is_array($withdrawConfig.methods) && in_array($key, $withdrawConfig.methods)"}checked{/if}> {$type}
+                                </label>
+                                {/foreach}
+                                <span class="help-block">{:__('Withdrawal methods tips')}</span>
+                            </div>
+                        </div>
+                        
+                        <!-- <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Auto arrival')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="radio">
+                                    <label for="row[auto_arrival]-1">
+                                        <input id="row[auto_arrival]-1" name="row[auto_arrival]" type="radio" value="1" 
+                                               {if condition="$withdrawConfig.auto_arrival eq '1' or $withdrawConfig.auto_arrival eq 1"}checked{/if}> 
+                                        {:__('Enable')}
+                                    </label> 
+                                    <label for="row[auto_arrival]-0">
+                                        <input id="row[auto_arrival]-0" name="row[auto_arrival]" type="radio" value="0" 
+                                               {if condition="$withdrawConfig.auto_arrival eq '0' or $withdrawConfig.auto_arrival eq 0 or !$withdrawConfig.auto_arrival"}checked{/if}> 
+                                        {:__('Disable')}
+                                    </label> 
+                                </div>
+                                
+                                <div class="help-block">
+                                    <span class="text-muted">{:__('Auto arrival tips')}</span>
+                                </div>
+                            </div>
+                        </div> -->
+
+                    <!-- 操作按钮 -->
+                    <div class="form-group">
+                        <label class="control-label col-xs-12 col-sm-2"></label>
+                        <div class="col-xs-12 col-sm-8">
+                            <button type="submit" class="btn btn-success btn-embossed">{:__('OK')}</button>
+                            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>

+ 92 - 0
application/admin/view/withdraw/withdraw/handle.html

@@ -0,0 +1,92 @@
+<form id="handle-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="panel panel-default panel-intro">
+
+        <div class="panel-heading">
+            <h3 class="panel-title">处理提现申请</h3>
+        </div>
+
+        <div class="panel-body">
+            <!-- 提现信息 -->
+            <div class="alert alert-info">
+                <h5><i class="fa fa-info-circle"></i> 提现信息</h5>
+                <p>
+                    <strong>提现单号:</strong>{$withdraw.withdraw_sn}<br>
+                    <strong>申请用户:</strong>{$withdraw.user.nickname|default='未知用户'}<br>
+                    <strong>提现金额:</strong>¥{$withdraw.amount}<br>
+                    <strong>手续费:</strong>¥{$withdraw.charge_fee}<br>
+                    <strong>实际到账:</strong>¥{$withdraw.paid_fee}<br>
+                    <strong>提现方式:</strong>
+                    {switch name="withdraw.withdraw_type"}
+                        {case value="wechat"}
+                            <img src="/assets/img/wechat.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                        {/case}
+                        {case value="alipay"}
+                            <img src="/assets/img/alipay.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                        {/case}
+                        {case value="bank"}
+                            <img src="/assets/img/bank.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                        {/case}
+                        {default /}
+                            <img src="/assets/img/bank.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                    {/switch}<br>
+                    <strong>申请时间:</strong>{$withdraw.createtime|date='Y-m-d H:i:s',###}<br>
+                    <strong>当前状态:</strong><span class="label label-{$withdraw.status_style}">{$withdraw.status_text}</span>
+                </p>
+            </div>
+
+            <!-- 提现账户信息 -->
+            {if condition="$withdraw.withdraw_info"}
+            <div class="alert alert-warning">
+                <h5><i class="fa fa-credit-card"></i> 提现账户信息</h5>
+                <p>
+                    {foreach name="withdraw.withdraw_info" item="value" key="key"}
+                    <strong>{$key}:</strong>{$value}<br>
+                    {/foreach}
+                </p>
+            </div>
+            {/if}
+
+            <!-- 处理表单 -->
+            <div class="form-group">
+                <label for="action" class="control-label col-xs-12 col-sm-2">处理操作:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <select name="action" id="action" class="form-control selectpicker" data-rule-required="true">
+                        <option value="">请选择处理操作</option>
+                        {if condition="$withdraw.status eq 0"}
+                        <option value="agree">同意</option>
+                        <option value="withdraw">立即打款</option>
+                        <option value="agree_and_withdraw">同意并打款</option>
+                        <option value="refuse">拒绝</option>
+                        {elseif condition="$withdraw.status eq 1"}
+                        {if condition="$withdraw.withdraw_type neq 'wechat'"}
+                        <option value="withdraw">立即打款</option>
+                        {/if}
+                        <option value="cancel">撤销</option>
+                        <option value="refund_commission">退回佣金</option>
+                        {/if}
+                    </select>
+                </div>
+            </div>
+
+            <!-- 拒绝理由 -->
+            <div class="form-group refuse-reason" style="display: none;">
+                <label for="refuse_msg" class="control-label col-xs-12 col-sm-2">拒绝理由:</label>
+                <div class="col-xs-12 col-sm-8">
+                    <textarea name="refuse_msg" id="refuse_msg" class="form-control" rows="3" placeholder="请输入拒绝理由"></textarea>
+                </div>
+            </div>
+        </div>
+        
+        <div class="panel-footer">
+            <div class="row">
+                <div class="col-xs-12 col-sm-8 col-sm-offset-2">
+                    <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+                    <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                </div>
+            </div>
+        </div>
+
+    </div>
+</form>
+

+ 46 - 0
application/admin/view/withdraw/withdraw/index.html

@@ -0,0 +1,46 @@
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <div class="panel-lead"><em>提现管理</em>用于管理用户提现申请、审核和处理</div>
+        {:build_heading(null,FALSE)}
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="{:$Think.get.status === null ? 'active' : ''}"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li>
+            {foreach name="statusList" item="vo"}
+            <li class="{:$Think.get.status === (string)$key ? 'active' : ''}"><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li>
+            {/foreach}
+        </ul>
+    </div>
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+
+                <!-- 工具栏 -->
+                <div id="toolbar" class="toolbar">
+                    <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                    <!-- <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('withdraw/withdraw/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                    <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('withdraw/withdraw/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                    <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('withdraw/withdraw/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+
+                    <div class="dropdown btn-group {:$auth->check('withdraw/withdraw/handle')?'':'hide'}">
+                        <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
+                        <ul class="dropdown-menu text-left" role="menu">
+                            <li><a class="btn-handle-agree" href="javascript:;"><i class="fa fa-check-circle text-success"></i> 同意</a></li>
+                            <li><a class="btn-handle-withdraw" href="javascript:;"><i class="fa fa-credit-card text-primary"></i> 打款</a></li>
+                            <li><a class="btn-handle-agree-withdraw" href="javascript:;"><i class="fa fa-check text-warning"></i> 同意并打款</a></li>
+                            <li><a class="btn-handle-refuse" href="javascript:;"><i class="fa fa-times-circle text-danger"></i> 拒绝</a></li>
+                            <li><a class="btn-handle-cancel" href="javascript:;"><i class="fa fa-ban text-muted"></i> 撤销</a></li>
+                            <li><a class="btn-handle-refund" href="javascript:;"><i class="fa fa-undo text-warning"></i> 退回佣金</a></li>
+                        </ul>
+                    </div> -->
+                </div>
+
+                <!-- 数据表格 -->
+                <table id="table" class="table table-striped table-bordered table-hover table-nowrap" 
+                       data-operate-edit="{:$auth->check('withdraw/withdraw/edit')}" 
+                       data-operate-del="{:$auth->check('withdraw/withdraw/del')}" 
+                       width="100%">
+                </table>
+            </div>
+        </div>
+    </div>
+</div>

+ 85 - 0
application/admin/view/withdraw/withdraw/log.html

@@ -0,0 +1,85 @@
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <h3 class="panel-title">提现日志 - {$withdraw.withdraw_sn}</h3>
+    </div>
+
+    <div class="panel-body">
+        <!-- 提现基本信息 -->
+        <div class="alert alert-info">
+            <div class="row">
+                <div class="col-md-6">
+                    <p><strong>提现单号:</strong>{$withdraw.withdraw_sn}</p>
+                    <p><strong>申请用户:</strong>{$withdraw.user.nickname|default='未知用户'}</p>
+                    <p><strong>提现金额:</strong>¥{$withdraw.amount}</p>
+                </div>
+                <div class="col-md-6">
+                    <p><strong>提现方式:</strong>
+                        {switch name="withdraw.withdraw_type"}
+                            {case value="wechat"}
+                                <img src="/assets/img/wechat.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                            {/case}
+                            {case value="alipay"}
+                                <img src="/assets/img/alipay.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                            {/case}
+                            {case value="bank"}
+                                <img src="/assets/img/bank.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                            {/case}
+                            {default /}
+                                <img src="/assets/img/bank.png" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;">{$withdraw.withdraw_type_text}
+                        {/switch}
+                    </p>
+                    <p><strong>当前状态:</strong><span class="label label-{$withdraw.status_style}">{$withdraw.status_text}</span></p>
+                    <p><strong>申请时间:</strong>{$withdraw.createtime|date='Y-m-d H:i:s',###}</p>
+                </div>
+            </div>
+        </div>
+
+        <!-- 日志表格 -->
+        <div class="table-responsive">
+            <table class="table table-striped table-bordered table-hover">
+                <thead>
+                    <tr>
+                        <th width="50%">事件内容</th>
+                        <th width="25%">操作人</th>
+                        <th width="25%">操作时间</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {if condition="$logs"}
+                        {foreach name="logs" item="log"}
+                        <tr>
+                            <td>{$log.content}</td>
+                            <td>
+                                {if condition="$log.oper_info"}
+                                    {switch name="log.oper_info.type"}
+                                        {case value="user"}
+                                            <span class="text-primary"><i class="fa fa-user"></i> 用户:{$log.oper_info.name}</span>
+                                        {/case}
+                                        {case value="admin"}
+                                            <span class="text-success"><i class="fa fa-user-circle"></i> 管理员:{$log.oper_info.name}</span>
+                                        {/case}
+                                        {case value="system"}
+                                            <span class="text-muted"><i class="fa fa-cog"></i> 系统:{$log.oper_info.name}</span>
+                                        {/case}
+                                        {default /}
+                                            <span class="text-muted"><i class="fa fa-cog"></i> 系统</span>
+                                    {/switch}
+                                {else/}
+                                    <span class="text-muted">-</span>
+                                {/if}
+                            </td>
+                            <td>{$log.createtime|date='Y-m-d H:i:s',###}</td>
+                        </tr>
+                        {/foreach}
+                    {else/}
+                        <tr>
+                            <td colspan="3" class="text-center text-muted">
+                                <i class="fa fa-info-circle"></i> 暂无日志记录
+                            </td>
+                        </tr>
+                    {/if}
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>

+ 1 - 0
application/common.php

@@ -665,3 +665,4 @@ EOT;
         return $icon;
     }
 }
+

+ 163 - 0
application/common/Enum/WithdrawEnum.php

@@ -0,0 +1,163 @@
+<?php
+
+namespace app\common\Enum;
+
+/**
+ * 提现相关枚举类
+ */
+class WithdrawEnum
+{
+    // 提现状态
+    const STATUS_CANCELED = -3;         // 撤销提现
+    const STATUS_FAILED = -2;           // 提现失败  
+    const STATUS_REJECTED = -1;         // 已拒绝
+    const STATUS_PENDING = 0;           // 待审核
+    const STATUS_PROCESSING = 1;        // 处理中
+    const STATUS_COMPLETED = 2;         // 已处理
+
+    // 提现类型
+    const TYPE_WECHAT = 'wechat';       // 微信零钱
+    const TYPE_ALIPAY = 'alipay';       // 支付宝账户
+    const TYPE_BANK = 'bank';           // 银行卡
+
+    // 微信转账状态
+    const WECHAT_TRANSFER_ACCEPTED = 'ACCEPTED';               // 单据已受理,请稍等
+    const WECHAT_TRANSFER_PROCESSING = 'PROCESSING';           // 单据处理中,请稍等
+    const WECHAT_TRANSFER_SUCCESS = 'SUCCESS';                 // 转账成功
+    const WECHAT_TRANSFER_FAIL = 'FAIL';                      // 转账失败
+    const WECHAT_TRANSFER_CANCELING = 'CANCELING';            // 单据撤销中
+    const WECHAT_TRANSFER_CANCELLED = 'CANCELLED';             // 单据已撤销
+    const WECHAT_TRANSFER_WAIT_USER_CONFIRM = 'WAIT_USER_CONFIRM'; // 待收款用户确认
+    const WECHAT_TRANSFER_TRANSFERING = 'TRANSFERING';         // 转账中
+    const WECHAT_TRANSFER_NOT_FOUND = 'NOT_FOUND';            // 未申请微信提现
+
+    /**
+     * 获取状态列表
+     * @return array
+     */
+    public static function getStatusList()
+    {
+        return [
+            self::STATUS_CANCELED => '撤销提现',
+            self::STATUS_FAILED => '提现失败',
+            self::STATUS_REJECTED => '已拒绝',
+            self::STATUS_PENDING => '待审核',
+            self::STATUS_PROCESSING => '处理中',
+            self::STATUS_COMPLETED => '已处理'
+        ];
+    }
+
+    /**
+     * 获取状态名称
+     * @param int $status
+     * @return string
+     */
+    public static function getStatusName($status)
+    {
+        $list = self::getStatusList();
+        return isset($list[$status]) ? $list[$status] : '';
+    }
+
+    /**
+     * 获取类型列表
+     * @return array
+     */
+    public static function getTypeList()
+    {
+        return [
+            // self::TYPE_WECHAT => '微信零钱',
+            // self::TYPE_ALIPAY => '支付宝账户',
+            self::TYPE_BANK => '银行卡'
+        ];
+    }
+
+    /**
+     * 获取类型名称
+     * @param string $type
+     * @return string
+     */
+    public static function getTypeName($type)
+    {
+        $list = self::getTypeList();
+        return isset($list[$type]) ? $list[$type] : '';
+    }
+
+    /**
+     * 获取微信转账状态列表
+     * @return array
+     */
+    public static function getWechatTransferStateList()
+    {
+        return [
+            self::WECHAT_TRANSFER_ACCEPTED => '单据已受理,请稍等',
+            self::WECHAT_TRANSFER_PROCESSING => '单据处理中,请稍等',
+            self::WECHAT_TRANSFER_SUCCESS => '转账成功',
+            self::WECHAT_TRANSFER_FAIL => '转账失败',
+            self::WECHAT_TRANSFER_CANCELING => '单据撤销中',
+            self::WECHAT_TRANSFER_CANCELLED => '单据已撤销',
+            self::WECHAT_TRANSFER_WAIT_USER_CONFIRM => '待收款用户确认',
+            self::WECHAT_TRANSFER_TRANSFERING => '转账中',
+            self::WECHAT_TRANSFER_NOT_FOUND => '未申请微信提现'
+        ];
+    }
+
+    /**
+     * 获取微信转账状态名称
+     * @param string $state
+     * @return string
+     */
+    public static function getWechatTransferStateName($state)
+    {
+        $list = self::getWechatTransferStateList();
+        return isset($list[$state]) ? $list[$state] : $state;
+    }
+
+    /**
+     * 获取可以安全退还佣金的微信转账状态
+     * @return array
+     */
+    public static function getCanCancelStates()
+    {
+        return [
+            self::WECHAT_TRANSFER_FAIL,
+            self::WECHAT_TRANSFER_WAIT_USER_CONFIRM,
+            self::WECHAT_TRANSFER_CANCELLED
+        ];
+    }
+
+    /**
+     * 获取状态样式类名 (用于label标签)
+     * @param int $status
+     * @return string
+     */
+    public static function getStatusStyle($status)
+    {
+        $styles = [
+            self::STATUS_CANCELED => 'default',        // 撤销提现
+            self::STATUS_FAILED => 'danger',           // 提现失败
+            self::STATUS_REJECTED => 'danger',         // 已拒绝
+            self::STATUS_PENDING => 'info',            // 待审核
+            self::STATUS_PROCESSING => 'warning',      // 处理中
+            self::STATUS_COMPLETED => 'success'        // 已处理
+        ];
+        return isset($styles[$status]) ? $styles[$status] : 'default';
+    }
+
+    /**
+     * 获取状态文本样式类名 (用于文本颜色)
+     * @param int $status
+     * @return string
+     */
+    public static function getStatusTextStyle($status)
+    {
+        $styles = [
+            self::STATUS_CANCELED => 'text-muted',      // 撤销提现
+            self::STATUS_FAILED => 'text-danger',       // 提现失败
+            self::STATUS_REJECTED => 'text-danger',     // 已拒绝
+            self::STATUS_PENDING => 'text-info',        // 待审核
+            self::STATUS_PROCESSING => 'text-warning',  // 处理中
+            self::STATUS_COMPLETED => 'text-success'    // 已处理
+        ];
+        return isset($styles[$status]) ? $styles[$status] : 'text-muted';
+    }
+}

+ 49 - 41
application/common/model/Withdraw.php

@@ -4,6 +4,7 @@ namespace app\common\model;
 
 use think\Model;
 use app\common\model\User;
+use app\common\Enum\WithdrawEnum;
 
 class Withdraw extends Model
 {
@@ -20,52 +21,47 @@ class Withdraw extends Model
     // 追加属性
     protected $append = [
         'status_text',
+        'status_style',
         'charge_rate_format',
         'withdraw_info_hidden',
         'withdraw_type_text',
         'wechat_transfer_state_text',
     ];
 
-    // 微信商家转账状态
-    const WECHAT_TRANSFER_STATE = [
-        'ACCEPTED' => '单据已受理,请稍等',
-        'PROCESSING' => '单据处理中,请稍等',
-        'SUCCESS' => '转账成功',
-        'FAIL' => '转账失败',
-        'CANCELING' => '单据撤销中',
-        'CANCELLED' => '单据已撤销',
-        'WAIT_USER_CONFIRM' => '待收款用户确认',
-        'TRANSFERING' => '转账中',
-        'NOT_FOUND' => '未申请微信提现',
-    ];
-
-    // 可以安全退还佣金的状态
-    const CAN_CANCEL_STATE = [
-        'FAIL',
-        'WAIT_USER_CONFIRM',
-        'CANCELLED'
-    ];
+    // 注:微信转账状态和可退还佣金状态已移至 WithdrawEnum 枚举类
 
     public function statusList()
     {
-        return [
-            -3 => '撤销提现',
-            -2 => '提现失败',
-            -1 => '已拒绝',
-            0 => '待审核',
-            1 => '处理中',
-            2 => '已处理'
-        ];
+        return WithdrawEnum::getStatusList();
     }
 
-
     public function withdrawTypeList()
     {
-        return [
-            'wechat' => '微信零钱',
-            'alipay' => '支付包账户',
-            'bank' => '银行卡',
-        ];
+        return WithdrawEnum::getTypeList();
+    }
+
+    /**
+     * 获取微信转账状态列表
+     */
+    public function wechatTransferStateList()
+    {
+        return WithdrawEnum::getWechatTransferStateList();
+    }
+
+    /**
+     * 获取可以安全退还佣金的状态列表
+     */
+    public function canCancelStates()
+    {
+        return WithdrawEnum::getCanCancelStates();
+    }
+
+    /**
+     * 检查是否可以退还佣金
+     */
+    public function canRefundCommission()
+    {
+        return in_array($this->wechat_transfer_state, WithdrawEnum::getCanCancelStates());
     }
 
     /**
@@ -75,8 +71,17 @@ class Withdraw extends Model
     {
         $value = $value ?: ($data['status'] ?? null);
         
-        $list = $this->statusList();
-        return isset($list[$value]) ? $list[$value] : '';
+        return WithdrawEnum::getStatusName($value);
+    }
+
+    /**
+     * 状态样式获取器
+     */
+    public function getStatusStyleAttr($value, $data)
+    {
+        $value = $value ?: ($data['status'] ?? null);
+        
+        return WithdrawEnum::getStatusStyle($value);
     }
 
     /**
@@ -86,16 +91,14 @@ class Withdraw extends Model
     {
         $value = $value ?: ($data['withdraw_type'] ?? null);
 
-        $list = $this->withdrawTypeList();
-        return isset($list[$value]) ? $list[$value] : '';
+        return WithdrawEnum::getTypeName($value);
     }
 
     public function getWechatTransferStateTextAttr($value, $data)
     {
         $value = $value ?: ($data['wechat_transfer_state'] ?? null);
 
-        $list = self::WECHAT_TRANSFER_STATE;
-        return isset($list[$value]) ? $list[$value] : $value;
+        return WithdrawEnum::getWechatTransferStateName($value);
     }
 
 
@@ -104,7 +107,12 @@ class Withdraw extends Model
     {
         $value = $value ?: ($data['charge_rate'] ?? null);
 
-        return bcmul((string)$value, '100', 1) . '%';
+        // 安全处理手续费率格式化
+        if (is_numeric($value) && $value !== null && $value !== '') {
+            return bcmul((string)$value, '100', 1) . '%';
+        } else {
+            return '0%';
+        }
     }
 
     public function getWithdrawInfoHiddenAttr($value, $data)
@@ -125,6 +133,6 @@ class Withdraw extends Model
 
     public function user()
     {
-        return $this->belongsTo(User::class, 'user_id', 'id')->field('id, nickname, avatar, total_consume');
+        return $this->belongsTo(User::class, 'user_id', 'id')->field('id, username, nickname, avatar, total_consume');
     }
 }

BIN
public/assets/img/alipay.png


BIN
public/assets/img/bank.png


BIN
public/assets/img/gender-0.png


BIN
public/assets/img/gender-1.png


BIN
public/assets/img/score.png


BIN
public/assets/img/wallet.png


BIN
public/assets/img/wechat.png


+ 57 - 0
public/assets/js/backend/withdraw/config.js

@@ -0,0 +1,57 @@
+define(['jquery', 'bootstrap', 'backend', 'form'], function ($, undefined, Backend, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表单
+            Form.api.bindevent($("form[role=form]"), function (data, ret) {
+                // 表单提交成功后的处理
+                Layer.alert(ret.msg, {
+                    icon: 1,
+                    time: 2000
+                }, function() {
+                    // 关闭弹窗后刷新页面
+                    location.reload();
+                });
+            });
+
+            // 实时验证金额
+            $('input[name="row[min_amount]"], input[name="row[max_amount]"]').on('blur', function() {
+                var minAmount = parseFloat($('input[name="row[min_amount]"]').val() || 0);
+                var maxAmount = parseFloat($('input[name="row[max_amount]"]').val() || 0);
+                
+                if (minAmount > 0 && maxAmount > 0 && minAmount >= maxAmount) {
+                    Layer.msg('最小提现金额不能大于或等于最大提现金额', {icon: 2});
+                    $(this).focus();
+                    return false;
+                }
+            });
+
+            // 实时验证手续费率
+            $('input[name="row[charge_rate]"]').on('blur', function() {
+                var rate = parseFloat($(this).val() || 0);
+                if (rate < 0 || rate > 100) {
+                    Layer.msg('手续费率必须在0-100之间', {icon: 2});
+                    $(this).focus();
+                    return false;
+                }
+            });
+
+            // 提现方式变更时自动调整自动到账选项
+            $('input[name="row[methods][]"]').on('change', function() {
+                var checkedMethods = [];
+                $('input[name="row[methods][]"]:checked').each(function() {
+                    checkedMethods.push($(this).val());
+                });
+                
+                // 取消未选中提现方式的自动到账选项
+                $('input[name="row[auto_arrival][]"]').each(function() {
+                    if (checkedMethods.indexOf($(this).val()) === -1) {
+                        $(this).prop('checked', false);
+                    }
+                });
+            });
+        }
+    };
+    
+    return Controller;
+});

+ 344 - 0
public/assets/js/backend/withdraw/withdraw.js

@@ -0,0 +1,344 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'withdraw/withdraw/index' + location.search,
+                    del_url: 'withdraw/withdraw/del',
+                    multi_url: 'withdraw/withdraw/multi',
+                    table: 'withdraw',
+                }
+            });
+
+            // 初始化表格
+            var table = $("#table");
+            
+            // 表格配置
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'id',
+                sortOrder: 'desc',
+                showToggle: false,
+                showColumns: false,
+                showExport: false,
+                commonSearch: true,
+                searchFormVisible: false,
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: 'ID', width: 80, sortable: true},
+                        {
+                            field: 'user.username',
+                            title: __('User'),
+                            operate: 'LIKE',
+                            formatter: function (value, row, index) {
+                                // 显示用户头像和用户名
+                                var avatar = row.user && row.user.avatar ? row.user.avatar : '/assets/img/avatar.png';
+                                var username = row.user && row.user.username ? row.user.username : '游客';
+                                var userId = row.user_id || '';
+                                
+                                // 处理头像URL
+                                var avatarUrl = avatar;
+                                if (avatar && !avatar.startsWith('http') && !avatar.startsWith('//')) {
+                                    avatarUrl = Fast.api.cdnurl ? Fast.api.cdnurl(avatar) : avatar;
+                                }
+                                
+                                return '<div style="display:flex;align-items:center;">' + 
+                                       '<img src="' + avatarUrl + '" style="width:40px;height:40px;border-radius:50%;margin-right:10px;" />' +
+                                       '<div>' +
+                                       '<div style="color:#337ab7;font-weight:bold;">' + username + '</div>' +
+                                       '<div style="color:#999;font-size:12px;">ID: ' + userId + '</div>' +
+                                       '</div>' +
+                                       '</div>';
+                            }
+                        },
+                        {field: 'amount', title: '提现金额', width: 100, formatter: function(value, row, index) {
+                            return '¥' + parseFloat(value).toFixed(2);
+                        }},
+                        {field: 'paid_fee', title: '实际到账', width: 100, formatter: function(value, row, index) {
+                            return '¥' + parseFloat(value).toFixed(2);
+                        }},
+                        {field: 'charge_fee', title: '手续费', width: 80, formatter: function(value, row, index) {
+                            return '¥' + parseFloat(value).toFixed(2);
+                        }},
+                        {field: 'charge_rate_format', title: '费率', width: 60},
+                        {field: 'withdraw_type_text', title: '提现方式', width: 120, formatter: function(value, row, index) {
+                            var iconMap = {
+                                'wechat': '/assets/img/wechat.png',
+                                'alipay': '/assets/img/alipay.png', 
+                                'bank': '/assets/img/bank.png'
+                            };
+                            var icon = iconMap[row.withdraw_type] || '/assets/img/bank.png';
+                            return '<div style="display:flex;align-items:center;">' + 
+                                   '<img src="' + icon + '" style="width:20px;height:20px;margin-right:8px;" />' +
+                                   '<span>' + value + '</span>' +
+                                   '</div>';
+                        }},
+                        {field: 'withdraw_sn', title: '商户单号', width: 200},
+                        {field: 'withdraw_info', title: '打款信息', width: 250, formatter: function(value, row, index) {
+                            if (!value) return '-';
+                            var html = '';
+                            $.each(value, function(key, val) {
+                                html += '<div><strong>' + key + ':</strong> ' + val + '</div>';
+                            });
+                            return html;
+                        }},
+                        {field: 'wechat_transfer_state_text', title: '微信转账状态', width: 120, visible: false},
+                        {field: 'createtime', title: '申请时间', width: 160, operate: 'RANGE', addclass: 'datetimerange', formatter: Table.api.formatter.datetime},
+                        {field: 'updatetime', title: '更新时间', width: 160, operate: 'RANGE', addclass: 'datetimerange', formatter: Table.api.formatter.datetime},
+                        {field: 'status', title: '状态', width: 80, searchList: {"-1":"已拒绝","0":"待审核","1":"处理中","2":"已处理"}, custom: {"all":"全部"}, formatter: function(value, row, index) {
+                            var statusClass = Controller.getStatusClass(value);
+                            var statusText = Controller.getStatusText(value);
+                            return '<span class="label label-' + statusClass + '">' + statusText + '</span>';
+                        }},
+                        {
+                            field: 'operate', 
+                            title: __('Operate'), 
+                            width: 200,
+                            table: table, 
+                            events: Table.api.events.operate, 
+                            buttons: [
+                                {
+                                    name: 'handle',
+                                    title: '处理',
+                                    classname: 'btn btn-xs btn-primary btn-dialog',
+                                    icon: 'fa fa-cog',
+                                    url: 'withdraw/withdraw/handle',
+                                    visible: function(row) {
+                                        return row.status == 0 || row.status == 1;
+                                    }
+                                },
+                                {
+                                    name: 'log', 
+                                    title: '日志',
+                                    classname: 'btn btn-xs btn-info btn-dialog',
+                                    icon: 'fa fa-list-alt',
+                                    url: 'withdraw/withdraw/log'
+                                },
+                                {
+                                    name: 'check_wechat',
+                                    title: '检查微信状态',
+                                    classname: 'btn btn-xs btn-warning btn-ajax',
+                                    icon: 'fa fa-refresh',
+                                    url: 'withdraw/withdraw/checkWechatTransferResult',
+                                    confirm: '确认要检查微信转账状态吗?',
+                                    success: function(data, ret) {
+                                        table.bootstrapTable('refresh');
+                                        Layer.msg(ret.msg);
+                                    },
+                                    visible: function(row) {
+                                        return row.withdraw_type == 'wechat' && row.status == 1;
+                                    }
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+
+            // 批量处理操作绑定
+            $(document).on("click", ".btn-handle-agree", function () {
+                Controller.handleBatch('agree');
+            });
+            
+            $(document).on("click", ".btn-handle-withdraw", function () {
+                Controller.handleBatch('withdraw');
+            });
+            
+            $(document).on("click", ".btn-handle-agree-withdraw", function () {
+                Controller.handleBatch('agree_and_withdraw');
+            });
+            
+            $(document).on("click", ".btn-handle-refuse", function () {
+                var ids = Table.api.selectedids(table);
+                if (ids.length === 0) {
+                    Layer.msg("请先选择要操作的记录");
+                    return false;
+                }
+                Fast.api.open("withdraw/withdraw/handle/ids/" + ids.join(","), "拒绝提现", {
+                    callback: function() {
+                        table.bootstrapTable('refresh');
+                    }
+                });
+            });
+            
+            $(document).on("click", ".btn-handle-cancel", function () {
+                Controller.handleBatch('cancel');
+            });
+            
+            $(document).on("click", ".btn-handle-refund", function () {
+                Controller.handleBatch('refund_commission');
+            });
+        },
+        
+        handle: function () {
+            // 处理页面的表单提交
+            Form.api.bindevent($("form[role=form]"));
+            
+            // 根据操作类型显示隐藏拒绝理由
+            $('#action').on('change', function() {
+                var action = $(this).val();
+                if (action === 'refuse') {
+                    $('.refuse-reason').show();
+                    $('#refuse_msg').attr('data-rule-required', 'true');
+                } else {
+                    $('.refuse-reason').hide();
+                    $('#refuse_msg').removeAttr('data-rule-required');
+                }
+            });
+            
+            // 表单提交前验证
+            var isSubmitting = false; // 防止重复提交
+            
+            $('#handle-form').on('submit', function(e) {
+                e.preventDefault(); // 阻止默认提交
+                
+                if (isSubmitting) {
+                    return false;
+                }
+                
+                var action = $('#action').val();
+                if (!action) {
+                    Layer.msg('请选择处理操作');
+                    return false;
+                }
+                
+                if (action === 'refuse') {
+                    var refuseMsg = $('#refuse_msg').val().trim();
+                    if (!refuseMsg) {
+                        Layer.msg('请输入拒绝理由');
+                        return false;
+                    }
+                }
+                
+                // 显示确认对话框
+                var actionText = Controller.getActionText(action);
+                var form = $(this);
+                
+                Layer.confirm('确认要' + actionText + '这笔提现申请吗?', function(index) {
+                    isSubmitting = true;
+                    Layer.close(index);
+                    
+                    // 使用 FastAdmin 的 ajax 提交
+                    Fast.api.ajax({
+                        url: form.attr('action') || 'withdraw/withdraw/handle',
+                        data: form.serialize(),
+                        type: 'POST'
+                    }, function(data, ret) {
+                        isSubmitting = false;
+                        parent.Layer.msg(ret.msg, {icon: 1});
+                        setTimeout(function() {
+                            var index = parent.Layer.getFrameIndex(window.name);
+                            parent.Layer.close(index);
+                            parent.$(".btn-refresh").trigger("click");
+                        }, 1000);
+                    }, function(data, ret) {
+                        isSubmitting = false;
+                        Layer.msg(ret.msg);
+                    });
+                });
+                
+                return false;
+            });
+        },
+        
+        log: function () {
+            // 日志页面使用服务器端渲染,无需 JavaScript 初始化
+            // 如果需要可以在这里添加其他交互功能
+        },
+
+        // 批量处理方法
+        handleBatch: function(action) {
+            var table = $("#table");
+            var ids = Table.api.selectedids(table);
+            if (ids.length === 0) {
+                Layer.msg("请先选择要操作的记录");
+                return false;
+            }
+            
+            var actionText = '';
+            switch(action) {
+                case 'agree': actionText = '同意'; break;
+                case 'withdraw': actionText = '打款'; break;
+                case 'agree_and_withdraw': actionText = '同意并打款'; break;
+                case 'cancel': actionText = '撤销'; break;
+                case 'refund_commission': actionText = '退回佣金'; break;
+            }
+            
+            Layer.confirm("确认要" + actionText + "选中的提现申请吗?", function(index) {
+                Fast.api.ajax({
+                    url: 'withdraw/withdraw/handle',
+                    data: {ids: ids, action: action}
+                }, function(data, ret) {
+                    table.bootstrapTable('refresh');
+                    Layer.close(index);
+                });
+            });
+        },
+
+        // 获取状态样式类名
+        getStatusClass: function(status) {
+            var classes = {
+                '-1': 'danger',     // 已拒绝
+                '0': 'info',        // 待审核
+                '1': 'warning',     // 处理中
+                '2': 'success'      // 已处理
+            };
+            return classes[status] || 'default';
+        },
+
+        // 获取状态文本
+        getStatusText: function(status) {
+            var statusTexts = {
+                '-1': '已拒绝',
+                '0': '待审核',
+                '1': '处理中',
+                '2': '已处理'
+            };
+            return statusTexts[status] || '未知状态';
+        },
+
+        // 获取操作文本
+        getActionText: function(action) {
+            var actionTexts = {
+                'agree': '同意',
+                'withdraw': '打款',
+                'agree_and_withdraw': '同意并打款',
+                'refuse': '拒绝',
+                'cancel': '撤销',
+                'refund_commission': '退回佣金'
+            };
+            return actionTexts[action] || '处理';
+        },
+
+        api: {
+            // 解析Config中的JSON字符串的辅助函数
+            parseConfigJson: function(configKey, defaultValue) {
+                var configValue = Config[configKey] || defaultValue || {};
+                
+                // 如果是字符串,尝试解析JSON
+                if (typeof configValue === 'string') {
+                    try {
+                        return JSON.parse(configValue);
+                    } catch (e) {
+                        return defaultValue || {};
+                    }
+                }
+                
+                return configValue;
+            },
+            bindevent: function () {
+                Form.api.bindevent($("form[role=form]"));
+            }
+        }
+    };
+    
+    return Controller;
+});