Files
MBTI_wang/api/app/controller/superadmin/Gaokao.php
Ghost a411bf7b21 高考版优化
审核模式优化
2026-04-29 18:01:26 +08:00

312 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\controller\superadmin;
use app\BaseController;
use app\model\SystemConfig as SystemConfigModel;
use think\facade\Db;
use think\facade\Request;
/**
* 超管「高考版管理」Hub概览、用户列表、功能配置与前端 GaokaoHub.vue 对齐)
*/
class Gaokao extends BaseController
{
private const CONFIG_KEY = 'gaokao_hub';
private function authOk(): bool
{
$user = $this->request->user ?? null;
return !!($user && ($user['role'] ?? '') === 'superadmin');
}
/**
* GET /api/v1/superadmin/gaokao/overview
*/
public function overview()
{
if (!$this->authOk()) {
return error('无权限访问', 403);
}
$todayStart = strtotime('today');
$totalUsers = (int) Db::name('gaokao_user_profile')->count();
$completedUsers = (int) Db::name('gaokao_user_profile')
->where(function ($q) {
$q->where('analyzeStatus', 1)->whereOr('latestReportId', '>', 0);
})
->count();
$todayNew = (int) Db::name('gaokao_user_profile')
->where('createdAt', '>=', $todayStart)
->count();
$reportsGenerated = (int) Db::name('test_results')
->where('testType', 'gaokao')
->count();
$avgRow = Db::name('gaokao_user_profile')
->where('estimatedScore', '>', 0)
->fieldRaw('AVG(`estimatedScore`) AS a')
->find();
$avgScore = (int) round((float) ($avgRow['a'] ?? 0));
$conversionRate = $totalUsers > 0
? (string) round($completedUsers / $totalUsers * 100, 1) . '%'
: '0%';
$dist = [
'700plus' => 0,
'650_700' => 0,
'600_650' => 0,
'550_600' => 0,
'500_550' => 0,
'below500' => 0,
];
$scores = Db::name('gaokao_user_profile')
->where('estimatedScore', '>', 0)
->column('estimatedScore');
foreach ($scores as $s) {
$n = (int) $s;
if ($n >= 700) {
$dist['700plus']++;
} elseif ($n >= 650) {
$dist['650_700']++;
} elseif ($n >= 600) {
$dist['600_650']++;
} elseif ($n >= 550) {
$dist['550_600']++;
} elseif ($n >= 500) {
$dist['500_550']++;
} else {
$dist['below500']++;
}
}
return success([
'totalUsers' => $totalUsers,
'completedUsers' => $completedUsers,
'todayNew' => $todayNew,
'reportsGenerated' => $reportsGenerated,
'conversionRate' => $conversionRate,
'avgScore' => $avgScore,
'scoreDistribution'=> $dist,
]);
}
/**
* GET /api/v1/superadmin/gaokao/users
* Query: q, status (completed|in_progress|abandoned), page, pageSize, tenantId可选
*/
public function users()
{
if (!$this->authOk()) {
return error('无权限访问', 403);
}
$page = max(1, (int) Request::param('page', 1));
$pageSize = min(100, max(1, (int) Request::param('pageSize', 20)));
$keyword = trim((string) Request::param('q', Request::param('keyword', '')));
$status = trim((string) Request::param('status', ''));
$tenantId = (int) Request::param('tenantId', 0);
$q = Db::name('gaokao_user_profile')->alias('g')
->join('wechat_users w', 'w.id = g.userId')
->leftJoin('enterprises e', 'e.id = g.tenantId');
if ($keyword !== '') {
$like = '%' . addcslashes($keyword, '%_\\') . '%';
$q->whereRaw('(w.nickname LIKE ? OR w.phone LIKE ? OR g.name LIKE ?)', [$like, $like, $like]);
}
if ($tenantId > 0) {
$q->where('g.tenantId', $tenantId);
}
if ($status === 'completed') {
$q->where(function ($w) {
$w->where('g.analyzeStatus', 1)->whereOr('g.latestReportId', '>', 0);
});
} elseif ($status === 'in_progress') {
$q->whereRaw('(g.`latestReportId` IS NULL OR g.`latestReportId` = 0) AND IFNULL(g.`analyzeStatus`,0) = 0')
->where(function ($w) {
$w->where('g.entryStatus', 1)
->whereOr('g.formStatus', '>', 0)
->whereOr('g.mbtiStatus', '>', 0)
->whereOr('g.pdpStatus', '>', 0)
->whereOr('g.discStatus', '>', 0);
});
} elseif ($status === 'abandoned') {
$q->where('g.analyzeStatus', 2);
}
$total = (int) (clone $q)->count();
$rows = (clone $q)
->field('g.*,w.nickname,w.phone,w.avatar,e.name as tenantName')
->order('g.id', 'desc')
->page($page, $pageSize)
->select()
->toArray();
$list = [];
foreach ($rows as $r) {
$list[] = $this->formatGaokaoUserRow($r);
}
return paginate_response($list, $total, $page, $pageSize);
}
/**
* @param array<string,mixed> $r
* @return array<string,mixed>
*/
private function formatGaokaoUserRow(array $r): array
{
$formJson = $r['formJson'] ?? null;
if (is_string($formJson)) {
$formJson = json_decode($formJson, true) ?: [];
}
if (!is_array($formJson)) {
$formJson = [];
}
$tagsJson = $r['tagsJson'] ?? null;
if (is_string($tagsJson)) {
$tagsJson = json_decode($tagsJson, true) ?: [];
}
if (!is_array($tagsJson)) {
$tagsJson = [];
}
$mbtiType = (string) ($tagsJson['mbtiType'] ?? $tagsJson['mbti'] ?? $formJson['mbtiType'] ?? '');
$majorDirection = (string) ($formJson['majorDirection'] ?? $formJson['majorFocus'] ?? $tagsJson['majorDirection'] ?? '');
$rid = (int) ($r['latestReportId'] ?? 0);
$an = (int) ($r['analyzeStatus'] ?? 0);
if ($rid > 0 || $an === 1) {
$st = 'completed';
} elseif ($an === 2) {
$st = 'abandoned';
} elseif ((int) ($r['entryStatus'] ?? 0) === 1
|| (int) ($r['formStatus'] ?? 0) > 0
|| (int) ($r['mbtiStatus'] ?? 0) > 0
|| (int) ($r['pdpStatus'] ?? 0) > 0
|| (int) ($r['discStatus'] ?? 0) > 0) {
$st = 'in_progress';
} else {
// 尚未进入流程或仅占位档案:前端按「进行中」展示,避免误判为放弃
$st = 'in_progress';
}
$lastAt = isset($r['lastAnalyzeAt']) ? (int) $r['lastAnalyzeAt'] : 0;
return [
'id' => (int) ($r['id'] ?? 0),
'userId' => (int) ($r['userId'] ?? 0),
'nickname' => (string) ($r['nickname'] ?? ''),
'phone' => (string) ($r['phone'] ?? ''),
'gaokaoScore' => isset($r['estimatedScore']) && $r['estimatedScore'] !== null ? (int) $r['estimatedScore'] : null,
'mbtiType' => $mbtiType !== '' ? $mbtiType : null,
'majorDirection' => $majorDirection !== '' ? $majorDirection : null,
'status' => $st,
'reportId' => $rid > 0 ? $rid : null,
'completedAt' => $lastAt > 0 ? date('Y-m-d H:i:s', $lastAt) : null,
'tenantName' => (string) ($r['tenantName'] ?? ''),
];
}
/**
* GET /api/v1/superadmin/gaokao/config
*/
public function getConfig()
{
if (!$this->authOk()) {
return error('无权限访问', 403);
}
return success($this->loadGaokaoHubConfig());
}
/**
* POST /api/v1/superadmin/gaokao/config
*/
public function saveConfig()
{
if (!$this->authOk()) {
return error('无权限访问', 403);
}
$body = Request::post();
if (!is_array($body)) {
$body = [];
}
$merged = array_merge($this->defaultGaokaoHubConfig(), $body);
$merged['reportPrice'] = max(0, (float) ($merged['reportPrice'] ?? 0));
$merged['freeQuota'] = max(0, (int) ($merged['freeQuota'] ?? 0));
$merged['maxMajors'] = max(3, min(20, (int) ($merged['maxMajors'] ?? 10)));
foreach (['gaokaoEnabled', 'faceEnabled', 'mbtiEnabled', 'aiRecommendEnabled', 'paidReportEnabled', 'privacyNoticeEnabled'] as $k) {
$merged[$k] = !empty($merged[$k]);
}
$row = SystemConfigModel::where('key', self::CONFIG_KEY)->where('enterprise_id', 0)->find();
$now = time();
if ($row) {
$row->value = $merged;
$row->updatedAt = $now;
$row->save();
} else {
SystemConfigModel::create([
'key' => self::CONFIG_KEY,
'enterprise_id' => 0,
'value' => $merged,
'description' => '超管高考版 Hub 开关与参数',
'createdAt' => $now,
'updatedAt' => $now,
]);
}
return success($merged, '保存成功');
}
/**
* @return array<string,mixed>
*/
private function defaultGaokaoHubConfig(): array
{
return [
'gaokaoEnabled' => true,
'faceEnabled' => true,
'mbtiEnabled' => true,
'aiRecommendEnabled' => true,
'paidReportEnabled' => false,
'privacyNoticeEnabled' => true,
'reportPrice' => 9.9,
'freeQuota' => 1,
'maxMajors' => 10,
];
}
/**
* @return array<string,mixed>
*/
private function loadGaokaoHubConfig(): array
{
$defaults = $this->defaultGaokaoHubConfig();
$row = SystemConfigModel::where('key', self::CONFIG_KEY)->where('enterprise_id', 0)->find();
if (!$row || $row->value === null || $row->value === '') {
return $defaults;
}
$v = $row->value;
if (!is_array($v)) {
return $defaults;
}
return array_merge($defaults, $v);
}
}