fix(stats/self_input): 修复保存不入库/路由 404,补全部门与自媒体来源下拉

- 模型去掉 defaultSoftDelete=0,避免 ThinkPHP 软删过滤导致新增记录"隐形"
- 菜单 paths 改相对路径并新增「自录数据统计」目录,迁移已有菜单
- Lists/Overview 补部门关联、支持按部门筛选
- 数据权限改由 self_input_stats_view_all_roles 白名单 + DataScope 控制,
  编辑/删除完全交给按钮权限校验(移除"仅本人可改"硬限制)
- 新增 /stats.self_input/mediaSourceOptions 接口,统计页/账户消耗页/录入弹窗
  共用 useMediaSourceOptions + MediaSourceSelect,去重动态加载

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-22 11:47:15 +08:00
co-authored by Cursor
parent 183b6a1acf
commit bba989c0af
19 changed files with 621 additions and 130 deletions
@@ -18,10 +18,13 @@ class SelfInputLogic extends BaseLogic
$pageNo = max(1, (int) ($params['page_no'] ?? 1));
$pageSize = min(100, max(1, (int) ($params['page_size'] ?? 15)));
$mediaSource = trim((string) ($params['media_source'] ?? ''));
$deptId = (int) ($params['dept_id'] ?? 0);
$yejiQuery = self::buildYejiQuery($startDate, $endDate, $adminId, $adminInfo);
$effectiveAdminIds = self::resolveEffectiveAdminIds($adminId, $adminInfo, $deptId);
$yejiQuery = self::buildYejiQuery($startDate, $endDate, $effectiveAdminIds);
if ($mediaSource !== '') {
$yejiQuery->whereLike('media_source', '%' . $mediaSource . '%');
$yejiQuery->where('media_source', $mediaSource);
}
$count = (int) (clone $yejiQuery)->count();
@@ -31,7 +34,7 @@ class SelfInputLogic extends BaseLogic
->select()
->toArray();
$costMap = self::loadAccountCostMap($startDate, $endDate, $adminId, $adminInfo, $mediaSource);
$costMap = self::loadAccountCostMap($startDate, $endDate, $effectiveAdminIds, $mediaSource);
$lists = [];
foreach ($rows as $row) {
@@ -44,10 +47,11 @@ class SelfInputLogic extends BaseLogic
$entity['account_cost'] = round((float) ($costMap[$costKey] ?? 0), 2);
$lists[] = self::finalizeMetrics($entity);
}
$lists = self::attachDeptInfoToRows($lists);
$allYejiRows = self::buildYejiQuery($startDate, $endDate, $adminId, $adminInfo);
$allYejiRows = self::buildYejiQuery($startDate, $endDate, $effectiveAdminIds);
if ($mediaSource !== '') {
$allYejiRows->whereLike('media_source', '%' . $mediaSource . '%');
$allYejiRows->where('media_source', $mediaSource);
}
$allYeji = $allYejiRows->select()->toArray();
@@ -85,39 +89,83 @@ class SelfInputLogic extends BaseLogic
];
}
private static function buildYejiQuery(string $startDate, string $endDate, int $adminId, array $adminInfo)
/**
* 从已录入的业绩/账户消耗中提取去重后的自媒体来源(与列表数据权限一致)。
*
* @return string[]
*/
public static function mediaSourceOptions(int $adminId, array $adminInfo): array
{
$effectiveAdminIds = self::resolveEffectiveAdminIds($adminId, $adminInfo, 0);
if ($effectiveAdminIds === []) {
return [];
}
$map = [];
foreach ([PersonalYeji::class, PersonalAccountCost::class] as $modelClass) {
$query = $modelClass::where('media_source', '<>', '');
if ($effectiveAdminIds !== null) {
$query->whereIn('creator_id', $effectiveAdminIds);
}
$rows = $query->group('media_source')->column('media_source');
foreach ($rows as $raw) {
$norm = self::normalizeMediaSource((string) $raw);
if ($norm !== '') {
$map[$norm] = true;
}
}
}
$list = array_keys($map);
sort($list, SORT_STRING);
return $list;
}
/**
* @return array<int>|null null = 不限;[] = 无可见
*/
private static function resolveEffectiveAdminIds(int $adminId, array $adminInfo, int $deptId): ?array
{
$visibleIds = self::getVisibleCreatorIds($adminId, $adminInfo);
return self::intersectVisibleByDept($visibleIds, $deptId);
}
/**
* @param array<int>|null $effectiveAdminIds
*/
private static function buildYejiQuery(string $startDate, string $endDate, ?array $effectiveAdminIds)
{
$query = PersonalYeji::whereBetween('yeji_date', [$startDate, $endDate]);
$visibleIds = self::getVisibleCreatorIds($adminId, $adminInfo);
if ($visibleIds === []) {
if ($effectiveAdminIds === []) {
$query->whereRaw('0 = 1');
} elseif ($visibleIds !== null) {
$query->whereIn('creator_id', $visibleIds);
} elseif ($effectiveAdminIds !== null) {
$query->whereIn('creator_id', $effectiveAdminIds);
}
return $query;
}
/**
* @param array<int>|null $effectiveAdminIds
* @return array<string, float>
*/
private static function loadAccountCostMap(
string $startDate,
string $endDate,
int $adminId,
array $adminInfo,
?array $effectiveAdminIds,
string $mediaSource
): array {
$query = PersonalAccountCost::whereBetween('cost_date', [$startDate, $endDate]);
$visibleIds = self::getVisibleCreatorIds($adminId, $adminInfo);
if ($visibleIds === []) {
if ($effectiveAdminIds === []) {
return [];
}
if ($visibleIds !== null) {
$query->whereIn('creator_id', $visibleIds);
if ($effectiveAdminIds !== null) {
$query->whereIn('creator_id', $effectiveAdminIds);
}
if ($mediaSource !== '') {
$query->whereLike('media_source', '%' . $mediaSource . '%');
$query->where('media_source', $mediaSource);
}
$rows = $query