feat(shopro_huifu): 添加汇付进件管理功能

- 新增汇付进件管理模块,支持新建、编辑进件流程
- 实现用户角色绑定验证功能,确保进件角色的有效性
- 集成Vue.js前端界面,提供完整的表单操作体验
- 支持个人和企业两种账户类型的进件资料录入
- 添加银行支行搜索功能,方便结算账户信息填写
- 完善进件状态管理和审核流程控制
- 增加图片上传组件,支持身份证、营业执照等证件上传
```
这个提交包含在:
可乐加冰
2026-09-29 15:40:36 +08:00
父节点 f2bca20a22
当前提交 2e7cbefdcd
共有 11 个文件被更改,包括 1307 次插入 和 3 次删除

查看文件

@@ -2,7 +2,10 @@
namespace app\admin\controller\shopro_huifu; namespace app\admin\controller\shopro_huifu;
use addons\shopro_huifu\library\onboard\HuifuOnboardAdminService;
use addons\shopro_huifu\library\onboard\HuifuOnboardAdminView; use addons\shopro_huifu\library\onboard\HuifuOnboardAdminView;
use addons\shopro_huifu\library\onboard\HuifuOnboardBankBranch;
use addons\shopro_huifu\library\onboard\HuifuOnboardError;
use addons\shopro_huifu\library\onboard\HuifuOnboardQuery; use addons\shopro_huifu\library\onboard\HuifuOnboardQuery;
use addons\shopro_huifu\library\onboard\HuifuOnboardService; use addons\shopro_huifu\library\onboard\HuifuOnboardService;
use addons\shopro_huifu\model\Onboard as OnboardModel; use addons\shopro_huifu\model\Onboard as OnboardModel;
@@ -10,6 +13,8 @@ use app\admin\controller\shopro\Base;
class Onboard extends Base class Onboard extends Base
{ {
protected $noNeedRight = ['userRoles', 'searchBranch'];
public function index() public function index()
{ {
if ($this->request->isAjax()) { if ($this->request->isAjax()) {
@@ -74,6 +79,78 @@ class Onboard extends Base
} }
} }
public function add()
{
if ($this->request->isPost()) {
return $this->persistDraft();
}
return $this->view->fetch();
}
public function edit($ids = null)
{
$row = OnboardModel::get($ids);
if (!$row) {
$this->error('记录不存在');
}
if ($this->request->isPost()) {
return $this->persistDraft($row);
}
$this->assignconfig('onboardForm', HuifuOnboardAdminView::formDataFromRow($row));
return $this->view->fetch('shopro_huifu/onboard/add');
}
public function userRoles()
{
$userId = intval($this->request->get('user_id', 0));
if ($userId <= 0) {
$this->error('请选择用户');
}
$user = HuifuOnboardAdminView::enrichListRow(['user_id' => $userId, 'payload' => '']);
$this->success('ok', null, [
'user' => [
'id' => $userId,
'nickname' => $user['user_nickname'] ?? '',
'mobile' => $user['user_mobile'] ?? '',
],
'roles' => HuifuOnboardAdminView::listRolesForUser($userId),
]);
}
public function searchBranch()
{
$keyword = trim((string) $this->request->get('keyword', ''));
$areaId = trim((string) $this->request->get('area_id', ''));
$this->success('ok', null, HuifuOnboardBankBranch::search($keyword, $areaId, 20));
}
protected function persistDraft($row = null)
{
$userId = intval($this->request->post('user_id', 0));
$roleType = trim((string) $this->request->post('role_type', ''));
$roleRefId = intval($this->request->post('role_ref_id', 0));
$form = $this->request->post('form/a', []);
if ($row) {
$userId = intval($row['user_id']);
$roleType = $row['role_type'];
$roleRefId = intval($row['role_ref_id']);
}
if (!$form || !is_array($form)) {
$this->error('请填写进件资料');
}
try {
$service = new HuifuOnboardAdminService();
$saved = $service->saveDraft($userId, $roleType, $roleRefId, $form);
$this->success('进件资料已保存,状态为待后台审核', null, [
'id' => $saved ? $saved['id'] : 0,
]);
} catch (\InvalidArgumentException $e) {
$this->error($e->getMessage());
} catch (\Throwable $e) {
$this->error(HuifuOnboardError::translate($e->getMessage()));
}
}
public function retry($ids = null) public function retry($ids = null)
{ {
$row = OnboardModel::get($ids); $row = OnboardModel::get($ids);

查看文件

@@ -0,0 +1,180 @@
<link rel="stylesheet" href="__CDN__/assets/addons/shopro/libs/element/element.css">
<style>
[v-cloak] { display: none; }
#huifuOnboardAdd { padding: 16px 20px 24px; color: #444; max-height: 78vh; overflow-y: auto; }
#huifuOnboardAdd .form-input { width: 100%; }
#huifuOnboardAdd .section-title { font-size: 14px; font-weight: 600; margin: 18px 0 12px; padding-left: 8px; border-left: 3px solid #409EFF; }
#huifuOnboardAdd .field-tip { color: #909399; font-size: 12px; line-height: 1.5; margin-top: 4px; }
#huifuOnboardAdd .upload-box { width: 140px; height: 90px; border: 1px dashed #dcdfe6; border-radius: 4px; display: flex; align-items: center; justify-content: center; overflow: hidden; background: #fafafa; }
#huifuOnboardAdd .upload-box img { width: 100%; height: 100%; object-fit: cover; }
#huifuOnboardAdd .branch-item { padding: 8px 0; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
#huifuOnboardAdd .branch-item:hover { background: #f5f7fa; }
#huifuOnboardAdd .branch-panel { max-height: 180px; overflow-y: auto; border: 1px solid #ebeef5; border-radius: 4px; padding: 0 10px; margin-top: 6px; }
</style>
<script src="__CDN__/assets/addons/shopro/libs/vue.js"></script>
<script src="__CDN__/assets/addons/shopro/libs/element/element.js"></script>
<div id="huifuOnboardAdd" v-cloak>
<el-form label-width="120px" size="small">
<div class="section-title">用户与角色</div>
<el-form-item label="选择用户" v-if="!isEdit">
<el-select v-model="userId" filterable remote reserve-keyword placeholder="输入昵称/手机号/ID搜索" :remote-method="searchUser" :loading="userLoading" class="form-input" @change="onUserChange">
<el-option v-for="item in userOptions" :key="item.id" :label="item.nickname + ' (' + (item.mobile || item.id) + ')'" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="用户" v-else>
<span>{{ userLabel }}</span>
</el-form-item>
<el-form-item label="进件角色" v-if="!isEdit">
<el-select v-model="roleKey" placeholder="请先选择用户" class="form-input" :disabled="!roleOptions.length" @change="onRoleChange">
<el-option v-for="item in roleOptions" :key="item.role_type + '-' + item.role_ref_id" :label="item.role_label + (item.onboard_status ? ' [' + item.onboard_status + ']' : '')" :value="item.role_type + ':' + item.role_ref_id" :disabled="!item.can_save">
<span>{{ item.role_label }}</span>
<span style="color:#909399;font-size:12px;margin-left:8px;">{{ item.disabled_reason || (item.onboard_status ? '已有记录:' + item.onboard_status : '新建') }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="进件角色" v-else>
<span>{{ roleLabel }}</span>
</el-form-item>
<div class="section-title">账户类型</div>
<el-form-item label="结算账户类型">
<el-radio-group v-model="form.account_type">
<el-radio label="indv">个人</el-radio>
<el-radio label="ent">个体户/企业</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="银行卡类型" v-if="form.account_type === 'ent'">
<el-radio-group v-model="form.bank_account_type">
<el-radio label="0">对私</el-radio>
<el-radio label="1">对公</el-radio>
</el-radio-group>
</el-form-item>
<template v-if="form.account_type === 'indv'">
<div class="section-title">身份实名信息</div>
<el-form-item label="真实姓名" required>
<el-input v-model="form.name" class="form-input" placeholder="与身份证一致"></el-input>
</el-form-item>
<el-form-item label="身份证号" required>
<el-input v-model="form.cert_no" maxlength="18" class="form-input"></el-input>
</el-form-item>
<el-form-item label="证件有效期">
<el-radio-group v-model="form.cert_validity_type">
<el-radio label="1">长期有效</el-radio>
<el-radio label="0">有截止日期</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="证件开始日期" required>
<el-date-picker v-model="form.cert_begin_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" placeholder="选择日期" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="证件截止日期" v-if="form.cert_validity_type === '0'" required>
<el-date-picker v-model="form.cert_end_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" placeholder="长期可填20991231" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="手机号" required>
<el-input v-model="form.mobile_no" maxlength="11" class="form-input"></el-input>
</el-form-item>
<el-form-item label="所在区域" required>
<el-cascader v-model="regionPath" :options="areaOptions" :props="areaProps" filterable clearable class="form-input" @change="onRegionChange"></el-cascader>
</el-form-item>
<el-form-item label="详细地址" required>
<el-input v-model="form.reg_detail" class="form-input" placeholder="详细居住地址"></el-input>
</el-form-item>
<el-form-item label="身份证人像面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('id_card_front_url', res)">
<div class="upload-box">
<img v-if="form.id_card_front_url" :src="form.id_card_front_url">
<span v-else style="color:#909399;font-size:12px;">点击上传</span>
</div>
</el-upload>
</el-form-item>
<el-form-item label="身份证国徽面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('id_card_back_url', res)">
<div class="upload-box">
<img v-if="form.id_card_back_url" :src="form.id_card_back_url">
<span v-else style="color:#909399;font-size:12px;">点击上传</span>
</div>
</el-upload>
</el-form-item>
</template>
<template v-else>
<div class="section-title">商户信息</div>
<el-form-item label="商户名称" required><el-input v-model="form.reg_name" class="form-input"></el-input></el-form-item>
<el-form-item label="统一社会信用代码" required><el-input v-model="form.license_code" maxlength="18" class="form-input"></el-input></el-form-item>
<el-form-item label="执照有效期">
<el-radio-group v-model="form.license_validity_type"><el-radio label="1">长期</el-radio><el-radio label="0">有截止</el-radio></el-radio-group>
</el-form-item>
<el-form-item label="执照开始日期" required>
<el-date-picker v-model="form.license_begin_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="执照截止日期" v-if="form.license_validity_type === '0'" required>
<el-date-picker v-model="form.license_end_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="所在区域" required>
<el-cascader v-model="regionPath" :options="areaOptions" :props="areaProps" filterable clearable class="form-input" @change="onRegionChange"></el-cascader>
</el-form-item>
<el-form-item label="详细地址" required><el-input v-model="form.reg_detail" class="form-input"></el-input></el-form-item>
<el-form-item label="法人姓名" required><el-input v-model="form.legal_name" class="form-input"></el-input></el-form-item>
<el-form-item label="法人手机号" required><el-input v-model="form.legal_mobile" maxlength="11" class="form-input"></el-input></el-form-item>
<el-form-item label="法人身份证号" required><el-input v-model="form.legal_cert_no" maxlength="18" class="form-input"></el-input></el-form-item>
<el-form-item label="法人证件有效期">
<el-radio-group v-model="form.legal_cert_validity_type"><el-radio label="1">长期</el-radio><el-radio label="0">有截止</el-radio></el-radio-group>
</el-form-item>
<el-form-item label="法人证件开始" required>
<el-date-picker v-model="form.legal_cert_begin_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="法人证件截止" v-if="form.legal_cert_validity_type === '0'" required>
<el-date-picker v-model="form.legal_cert_end_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="营业执照" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('license_url', res)">
<div class="upload-box"><img v-if="form.license_url" :src="form.license_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
<el-form-item label="法人身份证人像面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('legal_id_front_url', res)">
<div class="upload-box"><img v-if="form.legal_id_front_url" :src="form.legal_id_front_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
<el-form-item label="法人身份证国徽面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('legal_id_back_url', res)">
<div class="upload-box"><img v-if="form.legal_id_back_url" :src="form.legal_id_back_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
</template>
<div class="section-title">结算账户信息</div>
<el-form-item label="银行卡号" required>
<el-input v-model="form.card_no" class="form-input" placeholder="一类借记卡"></el-input>
</el-form-item>
<el-form-item label="开户银行支行" required>
<el-input v-model="branchKeyword" class="form-input" placeholder="搜索支行名称" @input="onBranchInput"></el-input>
<div class="field-tip" v-if="form.branch_name">已选:{{ form.branch_name }}({{ form.branch_code }})</div>
<div class="branch-panel" v-if="branchResults.length">
<div class="branch-item" v-for="(item, idx) in branchResults" :key="idx" @click="selectBranch(item)">
<div>{{ item.branch_name }}</div>
<div style="color:#909399;font-size:12px;">{{ item.branch_code }}</div>
</div>
</div>
</el-form-item>
<el-form-item label="银行预留手机号" required>
<el-input v-model="bankMobile" maxlength="11" class="form-input"></el-input>
</el-form-item>
<el-form-item label="银行卡正面照">
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('bank_card_url', res)">
<div class="upload-box"><img v-if="form.bank_card_url" :src="form.bank_card_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
<div class="field-tip">建议上传,可加快审核</div>
</el-form-item>
<el-form-item label="开户许可证" v-if="form.account_type === 'ent' && form.bank_account_type === '1'" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('account_permit_url', res)">
<div class="upload-box"><img v-if="form.account_permit_url" :src="form.account_permit_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submitForm">{{ isEdit ? '保存修改' : '保存进件资料' }}</el-button>
<el-button @click="closeForm">取消</el-button>
</el-form-item>
</el-form>
</div>

查看文件

@@ -33,6 +33,7 @@
<el-option label="已驳回" value="rejected"></el-option> <el-option label="已驳回" value="rejected"></el-option>
<el-option label="失败" value="failed"></el-option> <el-option label="失败" value="failed"></el-option>
</el-select> </el-select>
<el-button size="small" type="success" @click="openCreate" style="margin-left:8px;">新建进件</el-button>
<el-button size="small" type="primary" @click="getList" style="margin-left:8px;">刷新列表</el-button> <el-button size="small" type="primary" @click="getList" style="margin-left:8px;">刷新列表</el-button>
</div> </div>
<el-table :data="listData" v-loading="loading" border empty-text="暂无进件记录"> <el-table :data="listData" v-loading="loading" border empty-text="暂无进件记录">
@@ -55,9 +56,10 @@
<el-table-column prop="updatetime" label="更新时间" width="160"> <el-table-column prop="updatetime" label="更新时间" width="160">
<template slot-scope="scope">{{ formatTime(scope.row.updatetime) }}</template> <template slot-scope="scope">{{ formatTime(scope.row.updatetime) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="240" fixed="right"> <el-table-column label="操作" width="300" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" @click="openDetail(scope.row.id)">查看资料</el-button> <el-button size="mini" type="text" @click="openDetail(scope.row.id)">查看资料</el-button>
<el-button size="mini" type="text" @click="openEdit(scope.row.id)" v-if="canEditRow(scope.row)">编辑</el-button>
<el-button size="mini" type="primary" @click="submitRow(scope.row.id)" v-if="scope.row.onboard_status === 'draft'">提交汇付</el-button> <el-button size="mini" type="primary" @click="submitRow(scope.row.id)" v-if="scope.row.onboard_status === 'draft'">提交汇付</el-button>
<el-button size="mini" @click="pollRow(scope.row.id)" v-if="scope.row.onboard_status !== 'draft' && scope.row.onboard_status !== 'approved'">刷新</el-button> <el-button size="mini" @click="pollRow(scope.row.id)" v-if="scope.row.onboard_status !== 'draft' && scope.row.onboard_status !== 'approved'">刷新</el-button>
<el-button size="mini" type="warning" @click="retryRow(scope.row.id)" v-if="scope.row.onboard_status === 'rejected' || scope.row.onboard_status === 'failed'">重提</el-button> <el-button size="mini" type="warning" @click="retryRow(scope.row.id)" v-if="scope.row.onboard_status === 'rejected' || scope.row.onboard_status === 'failed'">重提</el-button>
@@ -133,6 +135,7 @@
</div> </div>
<span slot="footer"> <span slot="footer">
<el-button @click="detailVisible = false">关闭</el-button> <el-button @click="detailVisible = false">关闭</el-button>
<el-button v-if="detailData && canEditRow(detailData.record)" @click="editFromDetail">编辑资料</el-button>
<el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button> <el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button>
<el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button> <el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button>
<el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button> <el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button>

查看文件

@@ -25,6 +25,10 @@ return [
'weigh' => 4, 'weigh' => 4,
'sublist' => [ 'sublist' => [
['type' => 'file', 'name' => 'shopro_huifu/onboard/index', 'title' => '查看', 'ismenu' => 0], ['type' => 'file', 'name' => 'shopro_huifu/onboard/index', 'title' => '查看', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/add', 'title' => '新建进件', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/edit', 'title' => '编辑进件', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/userRoles', 'title' => '用户角色', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/searchBranch', 'title' => '搜索支行', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/detail', 'title' => '查看资料', 'ismenu' => 0], ['type' => 'file', 'name' => 'shopro_huifu/onboard/detail', 'title' => '查看资料', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/poll', 'title' => '刷新状态', 'ismenu' => 0], ['type' => 'file', 'name' => 'shopro_huifu/onboard/poll', 'title' => '刷新状态', 'ismenu' => 0],
['type' => 'file', 'name' => 'shopro_huifu/onboard/submit', 'title' => '提交汇付', 'ismenu' => 0], ['type' => 'file', 'name' => 'shopro_huifu/onboard/submit', 'title' => '提交汇付', 'ismenu' => 0],

查看文件

@@ -0,0 +1,47 @@
<?php
namespace addons\shopro_huifu\library\onboard;
use addons\shopro_huifu\model\Onboard;
use think\Db;
class HuifuOnboardAdminService
{
public function saveDraft($userId, $roleType, $roleRefId, array $formData)
{
$userId = intval($userId);
$roleRefId = intval($roleRefId);
if ($userId <= 0) {
throw new \InvalidArgumentException('请选择用户');
}
if (!in_array($roleType, ['promoter', 'regional_agent'], true)) {
throw new \InvalidArgumentException('请选择进件角色');
}
$this->assertRoleBinding($userId, $roleType, $roleRefId);
$service = new HuifuOnboardService();
return $service->saveDraft($userId, $roleType, $roleRefId, $formData);
}
protected function assertRoleBinding($userId, $roleType, $roleRefId)
{
if ($roleType === 'promoter') {
if ($roleRefId !== $userId) {
throw new \InvalidArgumentException('分销商角色参数无效');
}
$agent = Db::name('shopro_commission_agent')->where('user_id', $userId)->find();
if (!$agent) {
throw new \InvalidArgumentException('该用户不是分销商,无法创建分销商进件');
}
return;
}
$agent = Db::name('shopro_regional_agent')
->where('id', $roleRefId)
->where('user_id', $userId)
->find();
if (!$agent) {
throw new \InvalidArgumentException('区域公司与用户不匹配');
}
}
}

查看文件

@@ -206,4 +206,140 @@ class HuifuOnboardAdminView
} }
return substr($value, 0, 4) . '-' . substr($value, 4, 2) . '-' . substr($value, 6, 2); return substr($value, 0, 4) . '-' . substr($value, 4, 2) . '-' . substr($value, 6, 2);
} }
/**
* 后台新建/编辑进件:列出用户可绑定的角色
*/
public static function listRolesForUser($userId)
{
$userId = intval($userId);
if ($userId <= 0) {
return [];
}
$roles = [];
$commissionAgent = Db::name('shopro_commission_agent')->where('user_id', $userId)->find();
if ($commissionAgent) {
$row = \addons\shopro_huifu\model\Onboard::findByRole($userId, 'promoter', $userId);
$roles[] = self::buildRoleOption(
'promoter',
$userId,
'分销商',
$commissionAgent['status'] ?? '',
$row
);
}
$regionalAgents = Db::name('shopro_regional_agent')->where('user_id', $userId)->order('id desc')->select();
foreach ($regionalAgents as $agent) {
$row = \addons\shopro_huifu\model\Onboard::findByRole($userId, 'regional_agent', $agent['id']);
$label = '区域公司';
if (!empty($agent['area_name'])) {
$label .= '(' . $agent['area_name'] . ')';
}
$roles[] = self::buildRoleOption(
'regional_agent',
$agent['id'],
$label,
$agent['status'] ?? '',
$row
);
}
return $roles;
}
protected static function buildRoleOption($roleType, $roleRefId, $label, $agentStatus, $onboardRow)
{
$onboardStatus = $onboardRow ? ($onboardRow['onboard_status'] ?? '') : '';
$blocked = $onboardRow && in_array($onboardStatus, [
\addons\shopro_huifu\model\Onboard::STATUS_SUBMITTING,
\addons\shopro_huifu\model\Onboard::STATUS_BASIC_OK,
\addons\shopro_huifu\model\Onboard::STATUS_BUSI_PENDING,
\addons\shopro_huifu\model\Onboard::STATUS_APPROVED,
], true);
return [
'role_type' => $roleType,
'role_ref_id' => $roleRefId,
'role_label' => $label,
'agent_status' => $agentStatus,
'onboard_id' => $onboardRow ? intval($onboardRow['id']) : 0,
'onboard_status' => $onboardStatus ?: null,
'can_save' => !$blocked,
'disabled_reason' => $blocked ? '该角色进件处理中或已通过,不可新建/覆盖' : '',
];
}
public static function defaultFormData()
{
return [
'account_type' => 'indv',
'bank_account_type' => '0',
'name' => '',
'cert_no' => '',
'cert_validity_type' => '1',
'cert_begin_date' => '',
'cert_end_date' => '',
'mobile_no' => '',
'reg_detail' => '',
'shopro_province_id' => '',
'shopro_city_id' => '',
'shopro_district_id' => '',
'prov_id' => '',
'area_id' => '',
'district_id' => '',
'card_no' => '',
'card_name' => '',
'branch_code' => '',
'branch_name' => '',
'mp' => '',
'id_card_front_url' => '',
'id_card_back_url' => '',
'bank_card_url' => '',
'reg_name' => '',
'license_code' => '',
'license_validity_type' => '1',
'license_begin_date' => '',
'license_end_date' => '',
'legal_name' => '',
'legal_mobile' => '',
'legal_cert_no' => '',
'legal_cert_validity_type' => '1',
'legal_cert_begin_date' => '',
'legal_cert_end_date' => '',
'license_url' => '',
'legal_id_front_url' => '',
'legal_id_back_url' => '',
'account_permit_url' => '',
];
}
public static function formDataFromRow($row)
{
$data = is_array($row) ? $row : $row->toArray();
$payload = self::parsePayload($data['payload'] ?? '');
$form = array_merge(self::defaultFormData(), $payload);
foreach (['id_card_front_url', 'id_card_back_url', 'bank_card_url', 'license_url', 'legal_id_front_url', 'legal_id_back_url', 'account_permit_url'] as $field) {
if (!empty($form[$field])) {
$form[$field] = self::fullUrl($form[$field]);
}
}
$user = self::fetchUser($data['user_id'] ?? 0);
$roles = self::listRolesForUser($data['user_id'] ?? 0);
$roleLabel = '';
foreach ($roles as $role) {
if ($role['role_type'] === ($data['role_type'] ?? '') && intval($role['role_ref_id']) === intval($data['role_ref_id'] ?? 0)) {
$roleLabel = $role['role_label'];
break;
}
}
return [
'onboard_id' => intval($data['id'] ?? 0),
'user_id' => intval($data['user_id'] ?? 0),
'user_label' => trim(($user['nickname'] ?? '') . ' (' . ($user['mobile'] ?? $data['user_id']) . ')'),
'role_type' => $data['role_type'] ?? '',
'role_ref_id' => intval($data['role_ref_id'] ?? 0),
'role_label' => $roleLabel,
'onboard_status' => $data['onboard_status'] ?? '',
'form' => $form,
];
}
} }

查看文件

@@ -1,5 +1,8 @@
define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) { define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
var areaCache = null;
var branchTimer = null;
function withVue(callback) { function withVue(callback) {
if (typeof Vue !== 'undefined') { if (typeof Vue !== 'undefined') {
callback(Vue); callback(Vue);
@@ -22,6 +25,262 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
}); });
} }
function defaultForm() {
return {
account_type: 'indv',
bank_account_type: '0',
name: '',
cert_no: '',
cert_validity_type: '1',
cert_begin_date: '',
cert_end_date: '',
mobile_no: '',
reg_detail: '',
shopro_province_id: '',
shopro_city_id: '',
shopro_district_id: '',
prov_id: '',
area_id: '',
district_id: '',
card_no: '',
card_name: '',
branch_code: '',
branch_name: '',
mp: '',
id_card_front_url: '',
id_card_back_url: '',
bank_card_url: '',
reg_name: '',
license_code: '',
license_validity_type: '1',
license_begin_date: '',
license_end_date: '',
legal_name: '',
legal_mobile: '',
legal_cert_no: '',
legal_cert_validity_type: '1',
legal_cert_begin_date: '',
legal_cert_end_date: '',
license_url: '',
legal_id_front_url: '',
legal_id_back_url: '',
account_permit_url: ''
};
}
function createFormMixin() {
return {
data: function () {
return {
isEdit: false,
editId: 0,
userId: '',
userLabel: '',
roleKey: '',
roleType: '',
roleRefId: 0,
roleLabel: '',
userOptions: [],
userLoading: false,
roleOptions: [],
form: defaultForm(),
regionPath: [],
areaOptions: [],
areaProps: { value: 'id', label: 'name', children: 'children' },
branchKeyword: '',
branchResults: [],
uploadUrl: Fast.api.fixurl('ajax/upload'),
saving: false
};
},
computed: {
bankMobile: {
get: function () {
return this.form.account_type === 'ent' ? this.form.legal_mobile : this.form.mobile_no;
},
set: function (val) {
if (this.form.account_type === 'ent') {
this.form.legal_mobile = val;
} else {
this.form.mobile_no = val;
}
this.form.mp = val;
}
}
},
mounted: function () {
this.loadAreaTree();
this.initFromConfig();
},
methods: {
initFromConfig: function () {
var init = (typeof Config !== 'undefined' && Config.onboardForm) ? Config.onboardForm : null;
if (!init) return;
this.isEdit = true;
this.editId = init.onboard_id || 0;
this.userId = init.user_id;
this.userLabel = init.user_label || '';
this.roleType = init.role_type;
this.roleRefId = init.role_ref_id;
this.roleKey = init.role_type + ':' + init.role_ref_id;
this.roleLabel = init.role_label || '';
this.form = Object.assign(defaultForm(), init.form || {});
this.branchKeyword = this.form.branch_name || '';
this.restoreRegionPath();
},
loadAreaTree: function () {
var vm = this;
var done = function (data) {
areaCache = data || [];
vm.areaOptions = areaCache;
vm.restoreRegionPath();
};
if (areaCache) {
done(areaCache);
return;
}
Fast.api.ajax({ url: 'shopro/area/getCascader', type: 'GET' }, function (ret, res) {
done(res.data || []);
return false;
});
},
restoreRegionPath: function () {
var ids = [];
if (this.form.shopro_province_id) ids.push(parseInt(this.form.shopro_province_id, 10));
if (this.form.shopro_city_id) ids.push(parseInt(this.form.shopro_city_id, 10));
if (this.form.shopro_district_id) ids.push(parseInt(this.form.shopro_district_id, 10));
this.regionPath = ids.filter(function (n) { return !!n; });
},
onRegionChange: function (val) {
val = val || [];
this.form.shopro_province_id = val[0] ? String(val[0]) : '';
this.form.shopro_city_id = val[1] ? String(val[1]) : '';
this.form.shopro_district_id = val[2] ? String(val[2]) : '';
this.form.prov_id = this.form.shopro_province_id;
this.form.area_id = this.form.shopro_city_id;
this.form.district_id = this.form.shopro_district_id;
},
searchUser: function (keyword) {
if (!keyword) return;
var vm = this;
vm.userLoading = true;
Fast.api.ajax({
url: 'shopro/user/user/index',
type: 'GET',
data: { offset: 0, limit: 20, searchWhere: keyword }
}, function (ret, res) {
vm.userOptions = (res.data && res.data.rows) || [];
vm.userLoading = false;
return false;
}, function () {
vm.userLoading = false;
});
},
onUserChange: function () {
var vm = this;
vm.roleKey = '';
vm.roleType = '';
vm.roleRefId = 0;
vm.roleOptions = [];
if (!vm.userId) return;
Fast.api.ajax({
url: 'shopro_huifu/onboard/userRoles',
type: 'GET',
data: { user_id: vm.userId }
}, function (ret, res) {
var data = res.data || {};
vm.roleOptions = data.roles || [];
if (data.user) {
vm.userLabel = (data.user.nickname || '') + ' (' + (data.user.mobile || data.user.id) + ')';
}
return false;
});
},
onRoleChange: function () {
var parts = String(this.roleKey || '').split(':');
this.roleType = parts[0] || '';
this.roleRefId = parseInt(parts[1] || '0', 10);
var role = this.roleOptions.find(function (item) {
return item.role_type === this.roleType && item.role_ref_id === this.roleRefId;
}.bind(this));
this.roleLabel = role ? role.role_label : '';
},
onBranchInput: function () {
var vm = this;
clearTimeout(branchTimer);
branchTimer = setTimeout(function () {
if (!vm.branchKeyword) {
vm.branchResults = [];
return;
}
Fast.api.ajax({
url: 'shopro_huifu/onboard/searchBranch',
type: 'GET',
data: {
keyword: vm.branchKeyword,
area_id: vm.form.area_id || vm.form.shopro_city_id
}
}, function (ret, res) {
vm.branchResults = res.data || [];
return false;
});
}, 300);
},
selectBranch: function (item) {
this.form.branch_code = item.branch_code;
this.form.branch_name = item.branch_name;
this.branchKeyword = item.branch_name;
this.branchResults = [];
if (item.prov_id) this.form.prov_id = item.prov_id;
if (item.area_id) this.form.area_id = item.area_id;
},
onUploadSuccess: function (field, res) {
if (res && res.code === 1 && res.data && res.data.url) {
this.form[field] = res.data.fullurl || res.data.url;
return;
}
this.$message.error((res && res.msg) || '上传失败');
},
closeForm: function () {
Fast.api.close();
},
submitForm: function () {
var vm = this;
if (!vm.isEdit) {
if (!vm.userId) {
Toastr.error('请选择用户');
return;
}
if (!vm.roleType || !vm.roleRefId) {
Toastr.error('请选择进件角色');
return;
}
}
vm.saving = true;
var url = vm.isEdit
? ('shopro_huifu/onboard/edit/ids/' + vm.editId)
: 'shopro_huifu/onboard/add';
Fast.api.ajax({
url: url,
type: 'POST',
data: {
user_id: vm.userId,
role_type: vm.roleType,
role_ref_id: vm.roleRefId,
form: vm.form
}
}, function () {
vm.saving = false;
Fast.api.close();
return false;
}, function () {
vm.saving = false;
});
}
}
};
}
var Controller = { var Controller = {
index: function () { index: function () {
withVue(function (Vue) { withVue(function (Vue) {
@@ -49,6 +308,30 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
this.getList(); this.getList();
}, },
methods: { methods: {
openCreate: function () {
var vm = this;
Backend.api.open('shopro_huifu/onboard/add', '新建汇付进件', {
area: ['900px', '90%'],
callback: function () {
vm.getList();
}
});
},
openEdit: function (id) {
var vm = this;
Backend.api.open('shopro_huifu/onboard/edit/ids/' + id, '编辑汇付进件', {
area: ['900px', '90%'],
callback: function () {
vm.getList();
if (vm.detailVisible && vm.detailId === id) {
vm.openDetail(id);
}
}
});
},
canEditRow: function (row) {
return ['draft', 'failed', 'rejected'].indexOf(row.onboard_status) >= 0;
},
getList: function () { getList: function () {
var vm = this; var vm = this;
vm.loading = true; vm.loading = true;
@@ -131,6 +414,11 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
this.retryRow(this.detailId); this.retryRow(this.detailId);
} }
}, },
editFromDetail: function () {
if (this.detailId) {
this.openEdit(this.detailId);
}
},
statusText: function (status) { statusText: function (status) {
var map = { var map = {
draft: '待后台审核', draft: '待后台审核',
@@ -155,6 +443,17 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
} }
}); });
}); });
},
add: function () {
withVue(function (Vue) {
new Vue({
el: '#huifuOnboardAdd',
mixins: [createFormMixin()]
});
});
},
edit: function () {
Controller.add();
} }
}; };
return Controller; return Controller;

查看文件

@@ -2,7 +2,10 @@
namespace app\admin\controller\shopro_huifu; namespace app\admin\controller\shopro_huifu;
use addons\shopro_huifu\library\onboard\HuifuOnboardAdminService;
use addons\shopro_huifu\library\onboard\HuifuOnboardAdminView; use addons\shopro_huifu\library\onboard\HuifuOnboardAdminView;
use addons\shopro_huifu\library\onboard\HuifuOnboardBankBranch;
use addons\shopro_huifu\library\onboard\HuifuOnboardError;
use addons\shopro_huifu\library\onboard\HuifuOnboardQuery; use addons\shopro_huifu\library\onboard\HuifuOnboardQuery;
use addons\shopro_huifu\library\onboard\HuifuOnboardService; use addons\shopro_huifu\library\onboard\HuifuOnboardService;
use addons\shopro_huifu\model\Onboard as OnboardModel; use addons\shopro_huifu\model\Onboard as OnboardModel;
@@ -10,6 +13,7 @@ use app\admin\controller\shopro\Base;
class Onboard extends Base class Onboard extends Base
{ {
protected $noNeedRight = ['userRoles', 'searchBranch'];
public function index() public function index()
{ {
if ($this->request->isAjax()) { if ($this->request->isAjax()) {
@@ -77,6 +81,78 @@ class Onboard extends Base
} }
} }
public function add()
{
if ($this->request->isPost()) {
return $this->persistDraft();
}
return $this->view->fetch();
}
public function edit($ids = null)
{
$row = OnboardModel::get($ids);
if (!$row) {
$this->error('记录不存在');
}
if ($this->request->isPost()) {
return $this->persistDraft($row);
}
$this->assignconfig('onboardForm', HuifuOnboardAdminView::formDataFromRow($row));
return $this->view->fetch('shopro_huifu/onboard/add');
}
public function userRoles()
{
$userId = intval($this->request->get('user_id', 0));
if ($userId <= 0) {
$this->error('请选择用户');
}
$user = HuifuOnboardAdminView::enrichListRow(['user_id' => $userId, 'payload' => '']);
$this->success('ok', null, [
'user' => [
'id' => $userId,
'nickname' => $user['user_nickname'] ?? '',
'mobile' => $user['user_mobile'] ?? '',
],
'roles' => HuifuOnboardAdminView::listRolesForUser($userId),
]);
}
public function searchBranch()
{
$keyword = trim((string) $this->request->get('keyword', ''));
$areaId = trim((string) $this->request->get('area_id', ''));
$this->success('ok', null, HuifuOnboardBankBranch::search($keyword, $areaId, 20));
}
protected function persistDraft($row = null)
{
$userId = intval($this->request->post('user_id', 0));
$roleType = trim((string) $this->request->post('role_type', ''));
$roleRefId = intval($this->request->post('role_ref_id', 0));
$form = $this->request->post('form/a', []);
if ($row) {
$userId = intval($row['user_id']);
$roleType = $row['role_type'];
$roleRefId = intval($row['role_ref_id']);
}
if (!$form || !is_array($form)) {
$this->error('请填写进件资料');
}
try {
$service = new HuifuOnboardAdminService();
$saved = $service->saveDraft($userId, $roleType, $roleRefId, $form);
$this->success('进件资料已保存,状态为待后台审核', null, [
'id' => $saved ? $saved['id'] : 0,
]);
} catch (\InvalidArgumentException $e) {
$this->error($e->getMessage());
} catch (\Throwable $e) {
$this->error(HuifuOnboardError::translate($e->getMessage()));
}
}
public function retry($ids = null) public function retry($ids = null)
{ {
$row = OnboardModel::get($ids); $row = OnboardModel::get($ids);

查看文件

@@ -0,0 +1,180 @@
<link rel="stylesheet" href="__CDN__/assets/addons/shopro/libs/element/element.css">
<style>
[v-cloak] { display: none; }
#huifuOnboardAdd { padding: 16px 20px 24px; color: #444; max-height: 78vh; overflow-y: auto; }
#huifuOnboardAdd .form-input { width: 100%; }
#huifuOnboardAdd .section-title { font-size: 14px; font-weight: 600; margin: 18px 0 12px; padding-left: 8px; border-left: 3px solid #409EFF; }
#huifuOnboardAdd .field-tip { color: #909399; font-size: 12px; line-height: 1.5; margin-top: 4px; }
#huifuOnboardAdd .upload-box { width: 140px; height: 90px; border: 1px dashed #dcdfe6; border-radius: 4px; display: flex; align-items: center; justify-content: center; overflow: hidden; background: #fafafa; }
#huifuOnboardAdd .upload-box img { width: 100%; height: 100%; object-fit: cover; }
#huifuOnboardAdd .branch-item { padding: 8px 0; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
#huifuOnboardAdd .branch-item:hover { background: #f5f7fa; }
#huifuOnboardAdd .branch-panel { max-height: 180px; overflow-y: auto; border: 1px solid #ebeef5; border-radius: 4px; padding: 0 10px; margin-top: 6px; }
</style>
<script src="__CDN__/assets/addons/shopro/libs/vue.js"></script>
<script src="__CDN__/assets/addons/shopro/libs/element/element.js"></script>
<div id="huifuOnboardAdd" v-cloak>
<el-form label-width="120px" size="small">
<div class="section-title">用户与角色</div>
<el-form-item label="选择用户" v-if="!isEdit">
<el-select v-model="userId" filterable remote reserve-keyword placeholder="输入昵称/手机号/ID搜索" :remote-method="searchUser" :loading="userLoading" class="form-input" @change="onUserChange">
<el-option v-for="item in userOptions" :key="item.id" :label="item.nickname + ' (' + (item.mobile || item.id) + ')'" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="用户" v-else>
<span>{{ userLabel }}</span>
</el-form-item>
<el-form-item label="进件角色" v-if="!isEdit">
<el-select v-model="roleKey" placeholder="请先选择用户" class="form-input" :disabled="!roleOptions.length" @change="onRoleChange">
<el-option v-for="item in roleOptions" :key="item.role_type + '-' + item.role_ref_id" :label="item.role_label + (item.onboard_status ? ' [' + item.onboard_status + ']' : '')" :value="item.role_type + ':' + item.role_ref_id" :disabled="!item.can_save">
<span>{{ item.role_label }}</span>
<span style="color:#909399;font-size:12px;margin-left:8px;">{{ item.disabled_reason || (item.onboard_status ? '已有记录:' + item.onboard_status : '新建') }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="进件角色" v-else>
<span>{{ roleLabel }}</span>
</el-form-item>
<div class="section-title">账户类型</div>
<el-form-item label="结算账户类型">
<el-radio-group v-model="form.account_type">
<el-radio label="indv">个人</el-radio>
<el-radio label="ent">个体户/企业</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="银行卡类型" v-if="form.account_type === 'ent'">
<el-radio-group v-model="form.bank_account_type">
<el-radio label="0">对私</el-radio>
<el-radio label="1">对公</el-radio>
</el-radio-group>
</el-form-item>
<template v-if="form.account_type === 'indv'">
<div class="section-title">身份实名信息</div>
<el-form-item label="真实姓名" required>
<el-input v-model="form.name" class="form-input" placeholder="与身份证一致"></el-input>
</el-form-item>
<el-form-item label="身份证号" required>
<el-input v-model="form.cert_no" maxlength="18" class="form-input"></el-input>
</el-form-item>
<el-form-item label="证件有效期">
<el-radio-group v-model="form.cert_validity_type">
<el-radio label="1">长期有效</el-radio>
<el-radio label="0">有截止日期</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="证件开始日期" required>
<el-date-picker v-model="form.cert_begin_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" placeholder="选择日期" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="证件截止日期" v-if="form.cert_validity_type === '0'" required>
<el-date-picker v-model="form.cert_end_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" placeholder="长期可填20991231" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="手机号" required>
<el-input v-model="form.mobile_no" maxlength="11" class="form-input"></el-input>
</el-form-item>
<el-form-item label="所在区域" required>
<el-cascader v-model="regionPath" :options="areaOptions" :props="areaProps" filterable clearable class="form-input" @change="onRegionChange"></el-cascader>
</el-form-item>
<el-form-item label="详细地址" required>
<el-input v-model="form.reg_detail" class="form-input" placeholder="详细居住地址"></el-input>
</el-form-item>
<el-form-item label="身份证人像面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('id_card_front_url', res)">
<div class="upload-box">
<img v-if="form.id_card_front_url" :src="form.id_card_front_url">
<span v-else style="color:#909399;font-size:12px;">点击上传</span>
</div>
</el-upload>
</el-form-item>
<el-form-item label="身份证国徽面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('id_card_back_url', res)">
<div class="upload-box">
<img v-if="form.id_card_back_url" :src="form.id_card_back_url">
<span v-else style="color:#909399;font-size:12px;">点击上传</span>
</div>
</el-upload>
</el-form-item>
</template>
<template v-else>
<div class="section-title">商户信息</div>
<el-form-item label="商户名称" required><el-input v-model="form.reg_name" class="form-input"></el-input></el-form-item>
<el-form-item label="统一社会信用代码" required><el-input v-model="form.license_code" maxlength="18" class="form-input"></el-input></el-form-item>
<el-form-item label="执照有效期">
<el-radio-group v-model="form.license_validity_type"><el-radio label="1">长期</el-radio><el-radio label="0">有截止</el-radio></el-radio-group>
</el-form-item>
<el-form-item label="执照开始日期" required>
<el-date-picker v-model="form.license_begin_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="执照截止日期" v-if="form.license_validity_type === '0'" required>
<el-date-picker v-model="form.license_end_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="所在区域" required>
<el-cascader v-model="regionPath" :options="areaOptions" :props="areaProps" filterable clearable class="form-input" @change="onRegionChange"></el-cascader>
</el-form-item>
<el-form-item label="详细地址" required><el-input v-model="form.reg_detail" class="form-input"></el-input></el-form-item>
<el-form-item label="法人姓名" required><el-input v-model="form.legal_name" class="form-input"></el-input></el-form-item>
<el-form-item label="法人手机号" required><el-input v-model="form.legal_mobile" maxlength="11" class="form-input"></el-input></el-form-item>
<el-form-item label="法人身份证号" required><el-input v-model="form.legal_cert_no" maxlength="18" class="form-input"></el-input></el-form-item>
<el-form-item label="法人证件有效期">
<el-radio-group v-model="form.legal_cert_validity_type"><el-radio label="1">长期</el-radio><el-radio label="0">有截止</el-radio></el-radio-group>
</el-form-item>
<el-form-item label="法人证件开始" required>
<el-date-picker v-model="form.legal_cert_begin_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="法人证件截止" v-if="form.legal_cert_validity_type === '0'" required>
<el-date-picker v-model="form.legal_cert_end_date" type="date" value-format="yyyyMMdd" format="yyyy-MM-dd" class="form-input"></el-date-picker>
</el-form-item>
<el-form-item label="营业执照" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('license_url', res)">
<div class="upload-box"><img v-if="form.license_url" :src="form.license_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
<el-form-item label="法人身份证人像面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('legal_id_front_url', res)">
<div class="upload-box"><img v-if="form.legal_id_front_url" :src="form.legal_id_front_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
<el-form-item label="法人身份证国徽面" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('legal_id_back_url', res)">
<div class="upload-box"><img v-if="form.legal_id_back_url" :src="form.legal_id_back_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
</template>
<div class="section-title">结算账户信息</div>
<el-form-item label="银行卡号" required>
<el-input v-model="form.card_no" class="form-input" placeholder="一类借记卡"></el-input>
</el-form-item>
<el-form-item label="开户银行支行" required>
<el-input v-model="branchKeyword" class="form-input" placeholder="搜索支行名称" @input="onBranchInput"></el-input>
<div class="field-tip" v-if="form.branch_name">已选:{{ form.branch_name }}({{ form.branch_code }})</div>
<div class="branch-panel" v-if="branchResults.length">
<div class="branch-item" v-for="(item, idx) in branchResults" :key="idx" @click="selectBranch(item)">
<div>{{ item.branch_name }}</div>
<div style="color:#909399;font-size:12px;">{{ item.branch_code }}</div>
</div>
</div>
</el-form-item>
<el-form-item label="银行预留手机号" required>
<el-input v-model="bankMobile" maxlength="11" class="form-input"></el-input>
</el-form-item>
<el-form-item label="银行卡正面照">
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('bank_card_url', res)">
<div class="upload-box"><img v-if="form.bank_card_url" :src="form.bank_card_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
<div class="field-tip">建议上传,可加快审核</div>
</el-form-item>
<el-form-item label="开户许可证" v-if="form.account_type === 'ent' && form.bank_account_type === '1'" required>
<el-upload :action="uploadUrl" :show-file-list="false" accept="image/*" :on-success="(res) => onUploadSuccess('account_permit_url', res)">
<div class="upload-box"><img v-if="form.account_permit_url" :src="form.account_permit_url"><span v-else style="color:#909399;font-size:12px;">点击上传</span></div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submitForm">{{ isEdit ? '保存修改' : '保存进件资料' }}</el-button>
<el-button @click="closeForm">取消</el-button>
</el-form-item>
</el-form>
</div>

查看文件

@@ -33,6 +33,7 @@
<el-option label="已驳回" value="rejected"></el-option> <el-option label="已驳回" value="rejected"></el-option>
<el-option label="失败" value="failed"></el-option> <el-option label="失败" value="failed"></el-option>
</el-select> </el-select>
<el-button size="small" type="success" @click="openCreate" style="margin-left:8px;">新建进件</el-button>
<el-button size="small" type="primary" @click="getList" style="margin-left:8px;">刷新列表</el-button> <el-button size="small" type="primary" @click="getList" style="margin-left:8px;">刷新列表</el-button>
</div> </div>
<el-table :data="listData" v-loading="loading" border empty-text="暂无进件记录"> <el-table :data="listData" v-loading="loading" border empty-text="暂无进件记录">
@@ -55,9 +56,10 @@
<el-table-column prop="updatetime" label="更新时间" width="160"> <el-table-column prop="updatetime" label="更新时间" width="160">
<template slot-scope="scope">{{ formatTime(scope.row.updatetime) }}</template> <template slot-scope="scope">{{ formatTime(scope.row.updatetime) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="240" fixed="right"> <el-table-column label="操作" width="300" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" @click="openDetail(scope.row.id)">查看资料</el-button> <el-button size="mini" type="text" @click="openDetail(scope.row.id)">查看资料</el-button>
<el-button size="mini" type="text" @click="openEdit(scope.row.id)" v-if="canEditRow(scope.row)">编辑</el-button>
<el-button size="mini" type="primary" @click="submitRow(scope.row.id)" v-if="scope.row.onboard_status === 'draft'">提交汇付</el-button> <el-button size="mini" type="primary" @click="submitRow(scope.row.id)" v-if="scope.row.onboard_status === 'draft'">提交汇付</el-button>
<el-button size="mini" @click="pollRow(scope.row.id)" v-if="scope.row.onboard_status !== 'draft' && scope.row.onboard_status !== 'approved'">刷新</el-button> <el-button size="mini" @click="pollRow(scope.row.id)" v-if="scope.row.onboard_status !== 'draft' && scope.row.onboard_status !== 'approved'">刷新</el-button>
<el-button size="mini" type="warning" @click="retryRow(scope.row.id)" v-if="scope.row.onboard_status === 'rejected' || scope.row.onboard_status === 'failed'">重提</el-button> <el-button size="mini" type="warning" @click="retryRow(scope.row.id)" v-if="scope.row.onboard_status === 'rejected' || scope.row.onboard_status === 'failed'">重提</el-button>
@@ -133,6 +135,7 @@
</div> </div>
<span slot="footer"> <span slot="footer">
<el-button @click="detailVisible = false">关闭</el-button> <el-button @click="detailVisible = false">关闭</el-button>
<el-button v-if="detailData && canEditRow(detailData.record)" @click="editFromDetail">编辑资料</el-button>
<el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button> <el-button type="primary" v-if="detailData && detailData.can_submit_huifu" @click="submitFromDetail">提交汇付</el-button>
<el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button> <el-button v-if="detailData && detailData.can_poll" @click="pollFromDetail">刷新状态</el-button>
<el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button> <el-button type="warning" v-if="detailData && detailData.can_retry_huifu" @click="retryFromDetail">重新提交</el-button>

查看文件

@@ -1,5 +1,8 @@
define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) { define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
var areaCache = null;
var branchTimer = null;
function withVue(callback) { function withVue(callback) {
if (typeof Vue !== 'undefined') { if (typeof Vue !== 'undefined') {
callback(Vue); callback(Vue);
@@ -22,6 +25,262 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
}); });
} }
function defaultForm() {
return {
account_type: 'indv',
bank_account_type: '0',
name: '',
cert_no: '',
cert_validity_type: '1',
cert_begin_date: '',
cert_end_date: '',
mobile_no: '',
reg_detail: '',
shopro_province_id: '',
shopro_city_id: '',
shopro_district_id: '',
prov_id: '',
area_id: '',
district_id: '',
card_no: '',
card_name: '',
branch_code: '',
branch_name: '',
mp: '',
id_card_front_url: '',
id_card_back_url: '',
bank_card_url: '',
reg_name: '',
license_code: '',
license_validity_type: '1',
license_begin_date: '',
license_end_date: '',
legal_name: '',
legal_mobile: '',
legal_cert_no: '',
legal_cert_validity_type: '1',
legal_cert_begin_date: '',
legal_cert_end_date: '',
license_url: '',
legal_id_front_url: '',
legal_id_back_url: '',
account_permit_url: ''
};
}
function createFormMixin() {
return {
data: function () {
return {
isEdit: false,
editId: 0,
userId: '',
userLabel: '',
roleKey: '',
roleType: '',
roleRefId: 0,
roleLabel: '',
userOptions: [],
userLoading: false,
roleOptions: [],
form: defaultForm(),
regionPath: [],
areaOptions: [],
areaProps: { value: 'id', label: 'name', children: 'children' },
branchKeyword: '',
branchResults: [],
uploadUrl: Fast.api.fixurl('ajax/upload'),
saving: false
};
},
computed: {
bankMobile: {
get: function () {
return this.form.account_type === 'ent' ? this.form.legal_mobile : this.form.mobile_no;
},
set: function (val) {
if (this.form.account_type === 'ent') {
this.form.legal_mobile = val;
} else {
this.form.mobile_no = val;
}
this.form.mp = val;
}
}
},
mounted: function () {
this.loadAreaTree();
this.initFromConfig();
},
methods: {
initFromConfig: function () {
var init = (typeof Config !== 'undefined' && Config.onboardForm) ? Config.onboardForm : null;
if (!init) return;
this.isEdit = true;
this.editId = init.onboard_id || 0;
this.userId = init.user_id;
this.userLabel = init.user_label || '';
this.roleType = init.role_type;
this.roleRefId = init.role_ref_id;
this.roleKey = init.role_type + ':' + init.role_ref_id;
this.roleLabel = init.role_label || '';
this.form = Object.assign(defaultForm(), init.form || {});
this.branchKeyword = this.form.branch_name || '';
this.restoreRegionPath();
},
loadAreaTree: function () {
var vm = this;
var done = function (data) {
areaCache = data || [];
vm.areaOptions = areaCache;
vm.restoreRegionPath();
};
if (areaCache) {
done(areaCache);
return;
}
Fast.api.ajax({ url: 'shopro/area/getCascader', type: 'GET' }, function (ret, res) {
done(res.data || []);
return false;
});
},
restoreRegionPath: function () {
var ids = [];
if (this.form.shopro_province_id) ids.push(parseInt(this.form.shopro_province_id, 10));
if (this.form.shopro_city_id) ids.push(parseInt(this.form.shopro_city_id, 10));
if (this.form.shopro_district_id) ids.push(parseInt(this.form.shopro_district_id, 10));
this.regionPath = ids.filter(function (n) { return !!n; });
},
onRegionChange: function (val) {
val = val || [];
this.form.shopro_province_id = val[0] ? String(val[0]) : '';
this.form.shopro_city_id = val[1] ? String(val[1]) : '';
this.form.shopro_district_id = val[2] ? String(val[2]) : '';
this.form.prov_id = this.form.shopro_province_id;
this.form.area_id = this.form.shopro_city_id;
this.form.district_id = this.form.shopro_district_id;
},
searchUser: function (keyword) {
if (!keyword) return;
var vm = this;
vm.userLoading = true;
Fast.api.ajax({
url: 'shopro/user/user/index',
type: 'GET',
data: { offset: 0, limit: 20, searchWhere: keyword }
}, function (ret, res) {
vm.userOptions = (res.data && res.data.rows) || [];
vm.userLoading = false;
return false;
}, function () {
vm.userLoading = false;
});
},
onUserChange: function () {
var vm = this;
vm.roleKey = '';
vm.roleType = '';
vm.roleRefId = 0;
vm.roleOptions = [];
if (!vm.userId) return;
Fast.api.ajax({
url: 'shopro_huifu/onboard/userRoles',
type: 'GET',
data: { user_id: vm.userId }
}, function (ret, res) {
var data = res.data || {};
vm.roleOptions = data.roles || [];
if (data.user) {
vm.userLabel = (data.user.nickname || '') + ' (' + (data.user.mobile || data.user.id) + ')';
}
return false;
});
},
onRoleChange: function () {
var parts = String(this.roleKey || '').split(':');
this.roleType = parts[0] || '';
this.roleRefId = parseInt(parts[1] || '0', 10);
var role = this.roleOptions.find(function (item) {
return item.role_type === this.roleType && item.role_ref_id === this.roleRefId;
}.bind(this));
this.roleLabel = role ? role.role_label : '';
},
onBranchInput: function () {
var vm = this;
clearTimeout(branchTimer);
branchTimer = setTimeout(function () {
if (!vm.branchKeyword) {
vm.branchResults = [];
return;
}
Fast.api.ajax({
url: 'shopro_huifu/onboard/searchBranch',
type: 'GET',
data: {
keyword: vm.branchKeyword,
area_id: vm.form.area_id || vm.form.shopro_city_id
}
}, function (ret, res) {
vm.branchResults = res.data || [];
return false;
});
}, 300);
},
selectBranch: function (item) {
this.form.branch_code = item.branch_code;
this.form.branch_name = item.branch_name;
this.branchKeyword = item.branch_name;
this.branchResults = [];
if (item.prov_id) this.form.prov_id = item.prov_id;
if (item.area_id) this.form.area_id = item.area_id;
},
onUploadSuccess: function (field, res) {
if (res && res.code === 1 && res.data && res.data.url) {
this.form[field] = res.data.fullurl || res.data.url;
return;
}
this.$message.error((res && res.msg) || '上传失败');
},
closeForm: function () {
Fast.api.close();
},
submitForm: function () {
var vm = this;
if (!vm.isEdit) {
if (!vm.userId) {
Toastr.error('请选择用户');
return;
}
if (!vm.roleType || !vm.roleRefId) {
Toastr.error('请选择进件角色');
return;
}
}
vm.saving = true;
var url = vm.isEdit
? ('shopro_huifu/onboard/edit/ids/' + vm.editId)
: 'shopro_huifu/onboard/add';
Fast.api.ajax({
url: url,
type: 'POST',
data: {
user_id: vm.userId,
role_type: vm.roleType,
role_ref_id: vm.roleRefId,
form: vm.form
}
}, function () {
vm.saving = false;
Fast.api.close();
return false;
}, function () {
vm.saving = false;
});
}
}
};
}
var Controller = { var Controller = {
index: function () { index: function () {
withVue(function (Vue) { withVue(function (Vue) {
@@ -49,6 +308,30 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
this.getList(); this.getList();
}, },
methods: { methods: {
openCreate: function () {
var vm = this;
Backend.api.open('shopro_huifu/onboard/add', '新建汇付进件', {
area: ['900px', '90%'],
callback: function () {
vm.getList();
}
});
},
openEdit: function (id) {
var vm = this;
Backend.api.open('shopro_huifu/onboard/edit/ids/' + id, '编辑汇付进件', {
area: ['900px', '90%'],
callback: function () {
vm.getList();
if (vm.detailVisible && vm.detailId === id) {
vm.openDetail(id);
}
}
});
},
canEditRow: function (row) {
return ['draft', 'failed', 'rejected'].indexOf(row.onboard_status) >= 0;
},
getList: function () { getList: function () {
var vm = this; var vm = this;
vm.loading = true; vm.loading = true;
@@ -131,6 +414,11 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
this.retryRow(this.detailId); this.retryRow(this.detailId);
} }
}, },
editFromDetail: function () {
if (this.detailId) {
this.openEdit(this.detailId);
}
},
statusText: function (status) { statusText: function (status) {
var map = { var map = {
draft: '待后台审核', draft: '待后台审核',
@@ -144,7 +432,7 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
return map[status] || status || '-'; return map[status] || status || '-';
}, },
roleText: function (role) { roleText: function (role) {
return role === 'regional_agent' ? '区域代理' : (role === 'promoter' ? '推广员' : role); return role === 'regional_agent' ? '区域公司' : (role === 'promoter' ? '推广员' : role);
}, },
formatTime: function (ts) { formatTime: function (ts) {
if (!ts) return '-'; if (!ts) return '-';
@@ -155,6 +443,17 @@ define(['jquery', 'bootstrap', 'backend'], function ($, undefined, Backend) {
} }
}); });
}); });
},
add: function () {
withVue(function (Vue) {
new Vue({
el: '#huifuOnboardAdd',
mixins: [createFormMixin()]
});
});
},
edit: function () {
Controller.add();
} }
}; };
return Controller; return Controller;