model = new \app\admin\model\FcCategory; $parentList = [0 => '顶级分类(一级大类)']; $topRows = Db::name('fc_category') ->where('pid', 0) ->order('sort asc, id asc') ->select(); foreach ($topRows as $r) { $parentList[(int)$r['id']] = (string)$r['name'] . ((int)$r['enabled'] ? '' : ' (已停用)'); } $this->view->assign('parentList', $parentList); } /** * 分类列表(按一级分类 + 二级分类层级分组展示,支持附带父分类名称) * * @return string|\think\response\Json */ public function index() { $this->request->filter(['strip_tags', 'trim']); if ($this->request->isAjax()) { if ($this->request->request('keyField')) { return $this->selectpage(); } list($where, $sort, $order, $offset, $limit) = $this->buildparams(); // 分类总量有限:全量取回后按「父在前、子随父」的树形顺序在内存中重排, // 再切片分页,避免数据库层先 limit 导致翻页后父子分组被打散。 $rows = $this->model->where($where) ->order('pid asc, sort asc, id asc') ->select(); $total = count($rows); $rows = collection($rows)->toArray(); $parentMap = Db::name('fc_category')->where('pid', 0)->column('name', 'id'); // 树形顺序排列(一级分类紧跟其下属二级分类) $byParent = []; $tops = []; $others = []; foreach ($rows as &$r) { $pid = isset($r['pid']) ? (int)$r['pid'] : 0; $r['parent_name'] = $pid > 0 && isset($parentMap[$pid]) ? (string)$parentMap[$pid] : '顶级分类'; if (empty($r['image']) && !empty($r['icon_key'])) { $r['image'] = $this->resolveImageFromKey($r['icon_key']); } if ($pid === 0) { $tops[] = $r; } else { $byParent[$pid][] = $r; $others[] = $r; } } unset($r); $ordered = []; $usedIds = []; foreach ($tops as $top) { $ordered[] = $top; $usedIds[$top['id']] = true; $tid = (int)$top['id']; if (!empty($byParent[$tid])) { foreach ($byParent[$tid] as $child) { $ordered[] = $child; $usedIds[$child['id']] = true; } } } foreach ($others as $o) { if (!isset($usedIds[$o['id']])) { $ordered[] = $o; } } return json(['total' => $total, 'rows' => array_slice($ordered, $offset, $limit)]); } return $this->view->fetch(); } /** * 新增分类(自动同步 image 与 icon_key) * * @return string */ public function add() { if ($this->request->isPost()) { $params = $this->request->post('row/a', []); $params = $this->normalizeCategoryParams($params); $this->request->post(['row' => $params]); } return parent::add(); } /** * 编辑分类(自动补全图片路径并同步 image 与 icon_key) * * @param mixed $ids * @return string */ public function edit($ids = null) { $row = $this->model->get($ids); if (!$row) { $this->error(__('No Results were found')); } $adminIds = $this->getDataLimitAdminIds(); if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) { $this->error(__('You have no permission')); } if ($this->request->isPost()) { $params = $this->request->post('row/a', []); $params = $this->normalizeCategoryParams($params, (int)$ids); $this->request->post(['row' => $params]); return parent::edit($ids); } if (empty($row['image']) && !empty($row['icon_key'])) { $row['image'] = $this->resolveImageFromKey($row['icon_key']); } $this->view->assign("row", $row); return $this->view->fetch(); } /** * 规范化分类参数:同步实拍图 image 与 icon_key,防止将自身设为上级分类 * * @param array $params * @param int $currentId * @return array */ protected function normalizeCategoryParams(array $params, $currentId = 0) { $pid = isset($params['pid']) ? max(0, (int)$params['pid']) : 0; if ($currentId > 0 && $pid === $currentId) { $pid = 0; } $params['pid'] = $pid; // 分类类型:farm=农产品(发布页显示产地规格字段),general=通用信息(招聘求职等) // 二级分类强制继承上级类型;编辑一级分类时级联同步其下属二级分类 $kind = isset($params['kind']) ? trim((string)$params['kind']) : ''; if (!in_array($kind, ['farm', 'general'], true)) { $kind = 'general'; } if ($pid > 0) { $parentKind = Db::name('fc_category')->where('id', $pid)->value('kind'); if ($parentKind === 'farm' || $parentKind === 'general') { $kind = $parentKind; } } elseif ($currentId > 0) { Db::name('fc_category')->where('pid', $currentId)->update(['kind' => $kind, 'updatetime' => time()]); } $params['kind'] = $kind; $iconKey = isset($params['icon_key']) ? trim((string)$params['icon_key']) : ''; $image = isset($params['image']) ? trim((string)$params['image']) : ''; if ($image !== '' && $iconKey === '') { $params['icon_key'] = $image; } elseif ($iconKey !== '' && $image === '') { $params['image'] = $this->resolveImageFromKey($iconKey); } elseif ($iconKey !== '' && preg_match('/^(\/uploads\/|\/static\/|https?:\/\/)/i', $iconKey)) { $params['image'] = $iconKey; } return $params; } /** * 根据 icon_key 推导分类图片路径 * * @param string $iconKey * @return string */ protected function resolveImageFromKey($iconKey) { $iconKey = trim((string)$iconKey); if ($iconKey === '') { return '/static/icons/cat-service.png'; } if (preg_match('/^(\/uploads\/|\/static\/|https?:\/\/)/i', $iconKey)) { return $iconKey; } $file = strpos($iconKey, '.') !== false ? $iconKey : ($iconKey . '.png'); if (is_file(ROOT_PATH . 'public/static/categories/' . $file)) { return '/static/categories/' . $file; } return '/static/icons/' . $file; } /** * SelectPage搜索(供信息/草稿表单选择同城分类) * * @internal */ public function selectpage() { return parent::selectpage(); } }