feat(agent): 分销商详情改为在后台独立新页面选项卡中打开
这个提交包含在:
@@ -1,6 +1,31 @@
|
||||
{include file="/shopro/common/script" /}
|
||||
|
||||
<style>
|
||||
.agent-detail {
|
||||
background: #fff;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-detail .panel-block {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.agent-detail .detail-header-bar {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--sa-border);
|
||||
}
|
||||
|
||||
.agent-detail .detail-title-text {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin-left: 12px;
|
||||
color: var(--sa-title);
|
||||
}
|
||||
|
||||
.agent-detail .agent-detail-scrollbar>.el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -10,7 +35,8 @@
|
||||
}
|
||||
|
||||
.agent-detail .agent-content {
|
||||
height: 450px;
|
||||
min-height: 450px;
|
||||
height: auto;
|
||||
line-height: 1;
|
||||
padding: 20px;
|
||||
background: var(--sa-table-header-bg);
|
||||
@@ -136,6 +162,23 @@
|
||||
<el-container class="panel-block">
|
||||
<el-main>
|
||||
<el-scrollbar class="agent-detail-scrollbar" height="100%">
|
||||
<div class="detail-header-bar sa-flex sa-row-between">
|
||||
<div class="sa-flex">
|
||||
<el-button icon="Back" size="small" @click="onBack">返回</el-button>
|
||||
<span class="detail-title-text">
|
||||
分销商详情
|
||||
<template v-if="state.data.user">
|
||||
- {{ state.data.user.nickname || state.data.user_id }}
|
||||
</template>
|
||||
<template v-else-if="state.id">
|
||||
#{{ state.id }}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="sa-flex">
|
||||
<el-button icon="RefreshRight" size="small" circle @click="getDetail" title="刷新"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div class="user-content agent-content">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</el-tabs>
|
||||
<div class="sa-title sa-flex sa-row-between">
|
||||
<div class="sa-title-left">
|
||||
<div class="left-name">分销商</div>
|
||||
<div class="left-name">成员档案</div>
|
||||
<sa-filter-condition v-model="state.filter" @filter-delete="onChangeFilter">
|
||||
</sa-filter-condition>
|
||||
</div>
|
||||
@@ -119,7 +119,7 @@
|
||||
{{ scope.row.status_text }}
|
||||
</span>
|
||||
{if $auth->check('shopro/commission/agent/detail')}
|
||||
<el-button type="primary" link @click="onDetail(scope.row.user_id)">查看</el-button>
|
||||
<el-button type="primary" link @click="onDetail(scope.row.user_id, scope.row)">查看</el-button>
|
||||
{/if}
|
||||
{if $auth->check('shopro/commission/agent/edit')}
|
||||
<el-button type="primary" link @click="onOpenIdentity(scope.row)">开通身份</el-button>
|
||||
@@ -176,7 +176,7 @@
|
||||
</el-button>
|
||||
{/if}
|
||||
{if $auth->check('shopro/commission/agent/detail')}
|
||||
<el-button type="primary" link @click="onDetail(scope.row.user_id)">查看</el-button>
|
||||
<el-button type="primary" link @click="onDetail(scope.row.user_id, scope.row)">查看</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</template>
|
||||
@@ -231,7 +231,7 @@
|
||||
{/if}
|
||||
</template>
|
||||
{if $auth->check('shopro/commission/agent/detail')}
|
||||
<el-button type="primary" link @click="onDetail(scope.row.user_id)">查看</el-button>
|
||||
<el-button type="primary" link @click="onDetail(scope.row.user_id, scope.row)">查看</el-button>
|
||||
{/if}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
<div class="right sa-flex">
|
||||
{{ state.detail.voucher_available || '0.00' }}
|
||||
<el-button v-if="state.detail.has_agent" class="ml-2" type="primary" link
|
||||
@click="Fast.api.open('shopro/commission/agent/detail/id/'+state.detail.id+'?id='+state.detail.id, '分销商详情')">
|
||||
@click="onOpenAgentDetail(state.detail.id)">
|
||||
分销商详情
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -232,12 +232,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
}, function (ret, res) { })
|
||||
}
|
||||
|
||||
function onDetail(id) {
|
||||
Fast.api.open(`shopro/commission/agent/detail/id/${id}?id=${id}`, "详情", {
|
||||
callback() {
|
||||
getData()
|
||||
}
|
||||
})
|
||||
function onDetail(id, row) {
|
||||
const url = `shopro/commission/agent/detail/id/${id}?id=${id}`;
|
||||
const title = (row && row.user && row.user.nickname)
|
||||
? `分销商详情 - ${row.user.nickname}`
|
||||
: `分销商详情 #${id}`;
|
||||
if (typeof Backend !== 'undefined' && Backend.api && typeof Backend.api.addtabs === 'function') {
|
||||
Backend.api.addtabs(url, title);
|
||||
} else if (top && top.Backend && top.Backend.api && typeof top.Backend.api.addtabs === 'function') {
|
||||
top.Backend.api.addtabs(url, title);
|
||||
} else if (Fast && Fast.api && typeof Fast.api.addtabs === 'function') {
|
||||
Fast.api.addtabs(url, title);
|
||||
} else {
|
||||
window.location.href = Fast.api.fixurl(url);
|
||||
}
|
||||
}
|
||||
|
||||
const identity = reactive({
|
||||
@@ -402,11 +410,32 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
const detail = {
|
||||
setup() {
|
||||
|
||||
const queryAgentId = (typeof Config !== 'undefined' && (Config.id || Config.agent_id || Config.user_id))
|
||||
? (Config.id || Config.agent_id || Config.user_id)
|
||||
: (new URLSearchParams(location.search).get('id') || (location.pathname.match(/\/id\/(\d+)/i) || [])[1] || Fast.api.query('id'));
|
||||
|
||||
const state = reactive({
|
||||
id: new URLSearchParams(location.search).get('id'),
|
||||
id: queryAgentId,
|
||||
data: {},
|
||||
})
|
||||
|
||||
function onBack() {
|
||||
if (parent && parent.Layer && typeof parent.Layer.getFrameIndex === 'function') {
|
||||
try {
|
||||
var index = parent.Layer.getFrameIndex(window.name);
|
||||
if (index) {
|
||||
parent.Layer.close(index);
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (top && top.Backend && top.Backend.api && typeof top.Backend.api.closetabs === 'function') {
|
||||
top.Backend.api.closetabs();
|
||||
} else {
|
||||
window.history.back();
|
||||
}
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
Fast.api.ajax({
|
||||
url: `shopro/commission/agent/detail/id/${state.id}`,
|
||||
@@ -658,6 +687,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
onChangeTab,
|
||||
pagination,
|
||||
countCommission,
|
||||
onBack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
});
|
||||
}
|
||||
|
||||
function onOpenAgentDetail(id) {
|
||||
const url = `shopro/commission/agent/detail/id/${id}?id=${id}`;
|
||||
const title = `分销商详情 #${id}`;
|
||||
if (typeof Backend !== 'undefined' && Backend.api && typeof Backend.api.addtabs === 'function') {
|
||||
Backend.api.addtabs(url, title);
|
||||
} else if (top && top.Backend && top.Backend.api && typeof top.Backend.api.addtabs === 'function') {
|
||||
top.Backend.api.addtabs(url, title);
|
||||
} else if (Fast && Fast.api && typeof Fast.api.addtabs === 'function') {
|
||||
Fast.api.addtabs(url, title);
|
||||
} else {
|
||||
window.location.href = Fast.api.fixurl(url);
|
||||
}
|
||||
}
|
||||
|
||||
const log = reactive({
|
||||
tabActive: 'money',
|
||||
data: [],
|
||||
@@ -286,6 +300,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
onSave,
|
||||
onChangeParentUser,
|
||||
onRecharge,
|
||||
onOpenAgentDetail,
|
||||
log,
|
||||
getLog,
|
||||
pagination,
|
||||
|
||||
141
tests/partner/AgentDetailTest.php
普通文件
141
tests/partner/AgentDetailTest.php
普通文件
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace tests\partner;
|
||||
|
||||
define('APP_PATH', __DIR__ . '/../../application/');
|
||||
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
|
||||
require_once __DIR__ . '/../../thinkphp/base.php';
|
||||
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'config.php'));
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'database.php'));
|
||||
|
||||
use app\admin\controller\shopro\commission\Agent;
|
||||
use think\Request;
|
||||
use think\Db;
|
||||
|
||||
class AgentDetailTest
|
||||
{
|
||||
private $passed = 0;
|
||||
private $failed = 0;
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "========================================================\n";
|
||||
echo " 分销商详情新页面/选项卡及接口规范专项测试 \n";
|
||||
echo "========================================================\n\n";
|
||||
|
||||
$this->testDetailNonExistUser();
|
||||
$this->testDetailParamIntegrity();
|
||||
$this->testDetailViewFileExists();
|
||||
$this->testJsSyntaxAndAddtabsDefinition();
|
||||
|
||||
echo "\n--------------------------------------------------------\n";
|
||||
echo "测试执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
|
||||
echo "========================================================\n";
|
||||
return $this->failed === 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
private function assert($condition, $testName, $detail = '')
|
||||
{
|
||||
if ($condition) {
|
||||
echo " [PASS] {$testName}\n";
|
||||
$this->passed++;
|
||||
} else {
|
||||
echo " [FAIL] {$testName} - {$detail}\n";
|
||||
$this->failed++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 1:查询不存在的分销商详情返回错误
|
||||
*/
|
||||
public function testDetailNonExistUser()
|
||||
{
|
||||
try {
|
||||
$agent = new Agent();
|
||||
$request = Request::create('shopro/commission/agent/detail', 'GET', ['id' => 999999999], [], [], ['HTTP_X_REQUESTED_WITH' => 'xmlhttprequest']);
|
||||
Request::instance($request);
|
||||
|
||||
try {
|
||||
$agent->detail(999999999);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
$response = $e->getResponse();
|
||||
$data = $response->getData();
|
||||
$this->assert(isset($data['code']) && $data['code'] === 0, '未找到分销商时返回业务错误状态', json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
return;
|
||||
}
|
||||
$this->assert(false, '未找到分销商时应抛出错误响应');
|
||||
} catch (\Throwable $e) {
|
||||
$this->assert(true, '未找到分销商异常正常拦截: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 2:分销商详情视图文件存在且包含顶部返回与新选项卡样式
|
||||
*/
|
||||
public function testDetailViewFileExists()
|
||||
{
|
||||
$viewFile = ROOT_PATH . 'application/admin/view/shopro/commission/agent/detail.html';
|
||||
$this->assert(file_exists($viewFile), '分销商详情视图文件 detail.html 存在');
|
||||
|
||||
$content = file_get_contents($viewFile);
|
||||
$hasHeaderBar = strpos($content, 'detail-header-bar') !== false;
|
||||
$hasBackBtn = strpos($content, 'onBack') !== false;
|
||||
$hasFullHeight = strpos($content, 'height: 100vh') !== false;
|
||||
|
||||
$this->assert($hasHeaderBar, 'detail.html 包含 detail-header-bar 顶部栏');
|
||||
$this->assert($hasBackBtn, 'detail.html 包含 onBack 返回按钮');
|
||||
$this->assert($hasFullHeight, 'detail.html 样式适配选项卡 height: 100vh 满屏展示');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 3:检查 agent.js 中 addtabs 逻辑与 onDetail 定义
|
||||
*/
|
||||
public function testJsSyntaxAndAddtabsDefinition()
|
||||
{
|
||||
$jsFile = ROOT_PATH . 'public/assets/js/backend/shopro/commission/agent.js';
|
||||
$this->assert(file_exists($jsFile), 'agent.js 文件存在');
|
||||
|
||||
$content = file_get_contents($jsFile);
|
||||
$hasAddtabs = strpos($content, 'Backend.api.addtabs(url, title)') !== false;
|
||||
$hasOnBack = strpos($content, 'top.Backend.api.closetabs') !== false;
|
||||
$hasQueryAgentId = strpos($content, 'queryAgentId') !== false;
|
||||
|
||||
$this->assert($hasAddtabs, 'agent.js 包含 Backend.api.addtabs 新页面选项卡打开逻辑');
|
||||
$this->assert($hasOnBack, 'agent.js detail 包含 closetabs/Layer.close 智能返回逻辑');
|
||||
$this->assert($hasQueryAgentId, 'agent.js detail 包含 queryAgentId 多模式参数兼容解析');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 4:分销商详情存在时的字段完整性校验(如果有测试分销商)
|
||||
*/
|
||||
public function testDetailParamIntegrity()
|
||||
{
|
||||
$agentRow = Db::name('shopro_commission_agent')->find();
|
||||
if (!$agentRow) {
|
||||
$this->assert(true, '当前数据库暂无分销商数据,跳过数据字段验证');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$agent = new Agent();
|
||||
$request = Request::create('shopro/commission/agent/detail', 'GET', ['id' => $agentRow['user_id']], [], [], ['HTTP_X_REQUESTED_WITH' => 'xmlhttprequest']);
|
||||
Request::instance($request);
|
||||
|
||||
try {
|
||||
$agent->detail($agentRow['user_id']);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
$response = $e->getResponse();
|
||||
$data = $response->getData();
|
||||
$this->assert($data['code'] === 1 || $data['code'] === 0, '详情响应格式符合规范 (code/msg/data)');
|
||||
$this->assert(isset($data['data']['user_id']), '详情数据包含 user_id');
|
||||
$this->assert(isset($data['data']['active_status_text']), '详情数据包含合伙股东活跃状态扩展字段');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->assert(false, '详情接口测试执行异常: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new AgentDetailTest();
|
||||
exit($test->run());
|
||||
70
tests/verify_agent_detail_tab.js
普通文件
70
tests/verify_agent_detail_tab.js
普通文件
@@ -0,0 +1,70 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const vm = require('vm');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(condition, testName, detail = '') {
|
||||
if (condition) {
|
||||
console.log(` [PASS] ${testName}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.error(` [FAIL] ${testName} - ${detail}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("========================================================");
|
||||
console.log(" 分销商详情新页面/选项卡打开 自动化验证测试");
|
||||
console.log("========================================================\n");
|
||||
|
||||
// 1. 验证 agent.js
|
||||
const agentJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/commission/agent.js');
|
||||
assert(fs.existsSync(agentJsPath), 'agent.js 文件存在');
|
||||
const agentJsContent = fs.readFileSync(agentJsPath, 'utf-8');
|
||||
|
||||
assert(agentJsContent.includes('Backend.api.addtabs(url, title)'), 'agent.js 中 onDetail 包含 Backend.api.addtabs 选项卡打开');
|
||||
assert(agentJsContent.includes('top.Backend.api.addtabs(url, title)'), 'agent.js 中 onDetail 兼容 top.Backend.api.addtabs 顶层打开');
|
||||
assert(agentJsContent.includes('Fast.api.addtabs(url, title)'), 'agent.js 中 onDetail 兼容 Fast.api.addtabs');
|
||||
assert(agentJsContent.includes('queryAgentId'), 'agent.js 中 detail 页面使用 queryAgentId 兼容多种 URL 参数传参');
|
||||
assert(agentJsContent.includes('function onBack()'), 'agent.js 中 detail 页面包含 onBack 方法');
|
||||
assert(agentJsContent.includes('top.Backend.api.closetabs()'), 'agent.js 中 onBack 支持 closetabs 关闭当前选项卡');
|
||||
assert(agentJsContent.includes('onBack,') || agentJsContent.includes('onBack'), 'agent.js detail return 中导出 onBack');
|
||||
|
||||
// 2. 验证 agent/index.html
|
||||
const agentIndexPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/agent/index.html');
|
||||
assert(fs.existsSync(agentIndexPath), 'agent/index.html 文件存在');
|
||||
const agentIndexContent = fs.readFileSync(agentIndexPath, 'utf-8');
|
||||
|
||||
const detailCallCount = (agentIndexContent.match(/onDetail\(scope\.row\.user_id,\s*scope\.row\)/g) || []).length;
|
||||
assert(detailCallCount === 3, `agent/index.html 3处操作列均传递 (user_id, scope.row) [实际匹配: ${detailCallCount}]`);
|
||||
|
||||
// 3. 验证 agent/detail.html
|
||||
const agentDetailPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/agent/detail.html');
|
||||
assert(fs.existsSync(agentDetailPath), 'agent/detail.html 文件存在');
|
||||
const agentDetailContent = fs.readFileSync(agentDetailPath, 'utf-8');
|
||||
|
||||
assert(agentDetailContent.includes('detail-header-bar'), 'agent/detail.html 包含顶部导航栏 detail-header-bar');
|
||||
assert(agentDetailContent.includes('@click="onBack"'), 'agent/detail.html 包含返回按钮');
|
||||
assert(agentDetailContent.includes('height: 100vh'), 'agent/detail.html 设置 100vh 满屏适应选项卡');
|
||||
assert(agentDetailContent.includes('min-height: 450px') && agentDetailContent.includes('height: auto'), 'agent/detail.html 优化 agent-content 高度自适应');
|
||||
|
||||
// 4. 验证 user.js 与 user/detail.html
|
||||
const userJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/user/user.js');
|
||||
const userDetailPath = path.resolve(__dirname, '../application/admin/view/shopro/user/user/detail.html');
|
||||
assert(fs.existsSync(userJsPath), 'user.js 文件存在');
|
||||
assert(fs.existsSync(userDetailPath), 'user/detail.html 文件存在');
|
||||
|
||||
const userJsContent = fs.readFileSync(userJsPath, 'utf-8');
|
||||
const userDetailContent = fs.readFileSync(userDetailPath, 'utf-8');
|
||||
|
||||
assert(userJsContent.includes('function onOpenAgentDetail(id)'), 'user.js 包含 onOpenAgentDetail 方法');
|
||||
assert(userJsContent.includes('Backend.api.addtabs(url, title)'), 'user.js onOpenAgentDetail 使用 addtabs 打开选项卡');
|
||||
assert(userDetailContent.includes('@click="onOpenAgentDetail(state.detail.id)"'), 'user/detail.html 调用 onOpenAgentDetail');
|
||||
|
||||
console.log("\n--------------------------------------------------------");
|
||||
console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
|
||||
console.log("========================================================");
|
||||
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
在新工单中引用
屏蔽一个用户