'删除', '0' => '禁用', '1' => '启用']; /** * {@inheritdoc} */ public static function tableName() { return '{{%menu}}'; } /** * {@inheritdoc} */ public function rules() { return [ [['cate_id'], 'required'], [['cate_id', 'is_addon', 'pid', 'level', 'dev', 'sort', 'status', 'created_at', 'updated_at'], 'integer'], [['params', 'addons_name'], 'safe'], [['title', 'icon'], 'string', 'max' => 50], [['app_id'], 'string', 'max' => 20], [['addons_name', 'url'], 'string', 'max' => 100], [['tree'], 'string', 'max' => 300], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'title' => '标题', 'app_id' => '应用id', 'cate_id' => '菜单分类', 'addons_name' => '插件名称', 'is_addon' => '是否插件', 'pid' => '上级id', 'url' => '路由', 'icon' => '样式', 'level' => '级别', 'dev' => '开发者[0:都可见;开发模式可见]', 'sort' => '排序', 'params' => '参数', 'tree' => '树', 'status' => '状态[-1:删除;0:禁用;1启用]', 'created_at' => '添加时间', 'updated_at' => '修改时间', ]; } //关联模型 public function getChild() { return $this->hasMany(self::class, ['pid' => 'id'])->where(['<>', 'status', StatusEnum::DELETE])->orderBy('sort asc,id asc'); } /** * @param Menu $parent */ public function setParent(Menu $parent) { $this->parent = $parent; } /** * @param bool $insert * @return bool */ public function beforeSave($insert) { if ($this->isNewRecord) { if ($this->pid == 0) { $this->tree = TreeHelper::defaultTreeKey(); } else { $parent = $this->parent; $this->cate_id = $parent->cate_id; $this->level = $parent->level + 1; !$this->tree && $this->tree = $parent->tree . TreeHelper::prefixTreeKey($parent->id); } !$this->app_id && $this->app_id = $this->cate->app_id; // !$this->addons_name && $this->addons_name = $this->cate->addons_name; !isset($this->is_addon) && $this->is_addon = $this->cate->is_addon; } else { // 修改父类 if ($this->oldAttributes['pid'] != $this->pid) { $parent = $this->parent; if ($this->pid == 0) { $parent = new self(); $parent = $parent->loadDefaultValues(); $parent->cate_id = $this->cate_id; } $this->cate_id = $parent->cate_id; $level = $parent->level + 1; $tree = $parent->tree . TreeHelper::prefixTreeKey($parent->id ?? 0); // 查找所有子级 $list = Yii::$app->services->menu->findChildByID($this->tree, $this->id); $distanceLevel = $level - $this->level; // 递归修改 $data = ArrayHelper::itemsMerge($list, $this->id); $this->recursionUpdate($data, $parent->cate_id, $distanceLevel, $tree); $this->level = $level; $this->tree = $tree; } } return parent::beforeSave($insert); } /** * 递归更新数据 * * @param $data * @param $distanceLevel * @param $tree */ protected function recursionUpdate($data, $cate_id, $distanceLevel, $tree) { $updateIds = []; $itemLevel = ''; $itemTree = ''; foreach ($data as $item) { $updateIds[] = $item['id']; empty($itemLevel) && $itemLevel = $item['level'] + $distanceLevel; empty($itemTree) && $itemTree = str_replace($this->tree, $tree, $item['tree']); !empty($item['-']) && $this->recursionUpdate($item['-'], $cate_id, $distanceLevel, $tree); unset($item); } !empty($updateIds) && self::updateAll(['cate_id' => $cate_id, 'level' => $itemLevel, 'tree' => $itemTree], ['in', 'id', $updateIds]); } /** * * @Author bing * @DateTime 2021-08-26 10:56:02 * @param integer $cate_id * @param integer $pid * @param string $app_id * @return array|null * @copyright: Copyright (c) 2020 广东七件事集团 */ public static function getMenus(array $cond, array $with = [], bool $is_array = true, string $select = '*') { $query = self::find()->select($select); self::paramPack(['where' => $cond, 'with' => $with, 'order' => 'sort asc, id asc'], $query); $menu = $query->asArray($is_array)->all(); return $menu; } /** * 获取树状菜单 * @Author bing * @DateTime 2021-08-10 18:22:16 * @param array $menu 菜单 * @param integer $parentId * @param string $app_id APP_ID * @return array * @copyright: Copyright (c) 2020 广东七件事集团 */ public static function getTreeMenu(array $menu, int $pid = 0, string $route = '') { $tree = []; foreach ($menu as $k => $v) { $v['value'] = $v['id']; $v['label'] = $v['title']; if ($v['pid'] == $pid) { $tmp = $v; // 当前路由的菜单被选中 if ($route == $v['url']) $tmp['is_active'] = true; $children = self::getTreeMenu($menu, $v['id'], $route); $children && $tmp['children'] = $children; //判断父级菜单是否被选中 if (isset($tmp['children']) && !empty($tmp['children'])) { foreach ($tmp['children'] as $child) { if (isset($child['is_active']) && $child['is_active'] == true) { $tmp['is_active'] = true; } } } $tree[] = $tmp; } } return $tree; } public static function getDropDown($menuCate, $app_id = 'backend', $id = '') { $list = Menu::find() ->where(['>=', 'status', StatusEnum::DISABLED]) ->andWhere(['app_id' => $app_id]) ->andWhere(['is_addon' => $menuCate->is_addon]) ->andFilterWhere(['addons_name' => $menuCate->addons_name]) ->andFilterWhere(['<>', 'id', $id]) ->select(['id', 'title', 'pid', 'level']) ->orderBy('cate_id asc, sort asc') ->asArray() ->all(); $models = ArrayHelper::itemsMerge($list); $data = ArrayHelper::map(ArrayHelper::itemsMergeDropDown($models), 'id', 'title'); return ArrayHelper::merge([0 => '顶级菜单'], $data); } /** * 添加菜单 * @param $params * @return bool */ public static function createMenu($params) { try { if (!empty($params['id'])) { $model = Menu::findOne($params['id']); if (empty($model)) throw new Exception('菜单不存在或已删除'); } else { $model = new self(); } if (!$model->load($params, '')) throw new Exception($model->getErrorMessage()); $pid = !empty($params['pid']) ? $params['pid'] : 0; $model->pid = $pid; $model->level = 1; $model->tree = 'tr_0'; $model->addons_name = ''; if (!empty($model->pid)) { $rbac_item = Menu::findOne($model->pid); $model->level = $rbac_item->level; $model->tree = $rbac_item->tree . ' tr_' . $rbac_item['id']; } if (in_array($params['cate_id'], [AppEnum::PLATFORM, AppEnum::MALL])) $model->is_addon = WhetherEnum::DISABLED; if (in_array($params['cate_id'], [AppEnum::ADDON])) $model->is_addon = 1; $model->setParent($model); $res = $model->save(); if ($res === false) throw new Exception($model->getErrorMessage()); // 触发添加菜单成功事件 if (empty($params['id'])) { $event = new MenuEvent(); $data = [ 'name' => $params['url'], 'title' => $params['title'], 'cate_id' => $params['cate_id'], 'sort' => $params['sort'], 'menu_pid' => $params['pid'], 'app_id' => $params['app_id'], ]; Yii::$app->services->events->dispatch($event, $data, $event->listeners); } return $res; } catch (\Exception $e) { self::$static_error = $e->getMessage(); return false; } } /** * 保存数据 * @param array $params * @return bool|Menu */ public static function setData(array $params) { try { if (!empty($params['id'])) { $model = self::findOne(['id' => $params['id'], 'status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]); if (empty($model)) throw new Exception('服务不存在或已删除'); } else { $model = new self(); $model->loadDefaultValues(); } isset($params['sort']) && $model->sort = $params['sort']; isset($params['status']) && $model->status = $params['status']; if (!$model->load($params, '') || !$model->save()) throw new Exception($model->getErrorMessage()); return $model; } catch (Exception $e) { self::$static_error = $e->getMessage(); return false; } } /** * 获取编辑菜单数据 * @param $id * @param $type * @return array */ public static function getEditData($id, $type) { $where = ['and', ['id' => $id], ['<>', 'status', -1] ]; $menus = Menu::find()->where($where)->asArray()->one(); if (!empty($menus)) { if ($menus['status'] == -1 || $menus['status'] == 0) { $menus['status'] = 0; } else { $menus['status'] = 1; } } if (!empty($id) && $type == 'add') { $parent_title = $menus['title']; } else { $parent_title = '顶级菜单'; if (!empty($menus['pid'])) { $parent_item = Menu::find()->where(['id' => $menus['pid']])->andWhere(['<>', 'status', -1])->asArray()->one(); $parent_title = $parent_item['title']; } } $list = Menu::getDropDown(1, AppEnum::BACKEND); return [ 'menus' => !empty($menus) ? $menus : [], 'parent_title' => $parent_title, 'list' => $list ]; } /** * 删除 * @param $id * @param $status * @return bool */ public static function delMenu($id, $status) { try { $query = Menu::find()->where(['and', ['id' => $id], ['<>', 'status', StatusEnum::DELETE] ]); $menus = $query->one(); $menus->status = $status; $menus->save(); Menu::updateAll(['status' => $status], ['pid' => $id]); return true; } catch (\Exception $e) { return false; } } /** * 关联分类 * * @return \yii\db\ActiveQuery */ public function getCate() { return $this->hasOne(MenuCate::class, ['id' => 'cate_id']); } /** * 迁移生成菜单 * @Author: lun * @DateTime: 2023/7/13 0013 16:36 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $params * @return bool|Menu */ public static function migrateCreateMenu($params) { try { foreach ($params as $menu) { $pidByRoute = 0; if (empty($menu['pid']) && !empty($menu['p_route'])) { $pidByRoute = self::find() ->where(['url' => $menu['p_route'], 'status' => StatusEnum::ENABLED]) ->select('id') ->scalar(); } $menu['pid'] = $pidByRoute ?: $menu['pid']; $model = Menu::findOne(['url' => $menu['route'], 'cate_id' => $menu['cate_id'], 'pid' => $menu['pid'], 'status' => StatusEnum::ENABLED]); if (empty($model)) { $model = new self(); $model->cate_id = $menu['cate_id']; $model->title = $menu['title']; $model->url = $menu['route']; $model->app_id = 'backend'; $model->is_addon = 0; } $model->pid = $menu['pid']; $model->level = 1; $model->icon = $menu['icon'] ?? ''; $model->tree = 'tr_0'; if (!empty($model->pid)) { $rbac_item = Menu::findOne($model->pid); $model->level = $rbac_item->level; $model->tree = $rbac_item->tree . ' tr_' . $rbac_item['id']; // 获取上级权限 $where = []; $where[] = ['name' => $rbac_item->url]; $where[] = ['cate_id' => $rbac_item->cate_id]; $where[] = ['status' => StatusEnum::ENABLED]; $auth_parent = AuthItem::getOne($where); $auth_pid = empty($auth_parent) ? 0 : $auth_parent['id']; } $model->setParent($model); $res = $model->save(); if ($res === false) throw new Exception($model->getErrorMessage()); // 设置权限 $auth = [ 'name' => $menu['route'], 'title' => $menu['title'], 'cate_id' => $menu['cate_id'], 'app_id' => 'backend', 'pid' => $auth_pid ?? 0, ]; $auth_item = AuthItem::createAuthItem($auth); if ($auth_item === false) throw new Exception(AuthItem::getStaticError()); // 设置子权限 if (!empty($menu['authItemChild'])) { foreach ($menu['authItemChild'] as $child) { $auth_child = [ 'name' => $child['name'], 'title' => $child['title'], 'cate_id' => $menu['cate_id'], 'app_id' => 'backend', 'pid' => $auth_item['id'] ?? 0, ]; $res = AuthItem::createAuthItem($auth_child); if ($res === false) throw new Exception(AuthItem::getStaticError()); } } } return true; } catch (\Exception $e) { self::$static_error = $e->getFile().',第'.$e->getLine().'行,'.$e->getMessage(); return false; } } /** * 根据菜单补全权限 * @Author: liuzhantao * @DateTime: 2024/6/22 11:29 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $params * @return bool|Menu */ public static function migrateUpdateMenu() { try { // menu菜单表的字段“url” 对应 qimall_rbac_auth_item权限表的字段“name”,根据两表该字段的差异,找到缺少的权限记录 $missingUrls = Menu::find() ->alias('m') ->leftJoin(AuthItem::tableName() . ' a', 'm.url = a.name') ->where(['a.name' => null]) ->andWhere(['m.status' => 1]) // status = 1,状态:启用 ->andWhere(['m.is_addon' => 0]) // is_addon = 0, 不是插件 ->andWhere([ // 前缀只能是'mall/'或'admin/' 'or', ['like', 'm.url', 'mall/%', false], ['like', 'm.url', 'admin/%', false], ]) ->orderBy(['m.id' => SORT_ASC]) ->asArray() ->all(); foreach ($missingUrls as $k => $v) { $rbac_item = Menu::findOne($v['pid']); // 获取上级权限 $where = []; $where[] = ['name' => $rbac_item['url']]; $where[] = ['cate_id' => $rbac_item['cate_id']]; $auth_parent = AuthItem::getOne($where); $auth_pid = empty($auth_parent) ? 0 : $auth_parent['id']; // 设置权限 $auth = [ 'name' => $v['url'], 'title' => $v['title'], 'cate_id' => $v['cate_id'], 'app_id' => 'backend', 'pid' => $auth_pid ?? 0, ]; AuthItem::createAuthItem($auth); } return true; } catch (\Exception $e) { self::$static_error = $e->getFile() . ',第' . $e->getLine() . '行,' . $e->getMessage(); return false; } } /** * 权限表所有记录的所有字段进行重复对比,删除重复的菜单 * @Author: liuzhantao * @DateTime: 2024/6/27 10:29 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $params * @return bool|Menu */ public static function migrateDeleteMenu() { try { // 找到权限表中记录相同的记录 $query = AuthItem::find() ->select([ 'name', 'title', 'app_id', 'addons_name', 'pid', 'level', 'is_addon', 'sort', 'tree', 'status', 'cate_id', new Expression('COUNT(*) AS duplicate_count') ]) ->groupBy([ 'name', 'title', 'app_id', 'addons_name', 'pid', 'level', 'is_addon', 'sort', 'tree', 'status', 'cate_id' ]) ->having('COUNT(*) > 1'); $results = $query->asArray()->all(); // 循环记录 foreach ($results as $record) { $duplicates = AuthItem::find() ->where([ 'name' => $record['name'], 'title' => $record['title'], 'app_id' => $record['app_id'], 'addons_name' => $record['addons_name'], 'pid' => $record['pid'], 'level' => $record['level'], 'is_addon' => $record['is_addon'], 'sort' => $record['sort'], 'tree' => $record['tree'], 'status' => $record['status'], 'cate_id' => $record['cate_id'], ]) ->all(); // 保留第一条记录,删除其余记录 foreach (array_slice($duplicates, 1) as $duplicate) { $duplicate->delete(); } } } catch (\Exception $e) { self::$static_error = $e->getFile() . ',第' . $e->getLine() . '行,' . $e->getMessage(); return false; } } }