高考版优化

审核模式优化
This commit is contained in:
Ghost
2026-04-29 18:01:26 +08:00
parent 59419c4ce9
commit a411bf7b21
134 changed files with 19898 additions and 3371 deletions

View File

@@ -0,0 +1,311 @@
<?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);
}
}