feat(agent): 分销商详情改为在后台独立新页面选项卡中打开

这个提交包含在:
chen
2026-09-17 08:21:36 +08:00
父节点 23fe197bff
当前提交 64a9563779
共有 7 个文件被更改,包括 312 次插入 和 13 次删除

查看文件

@@ -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,

查看文件

@@ -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());

查看文件

@@ -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);