feat:动态权限
This commit is contained in:
parent
8c537e6656
commit
c4270a2fc8
@ -16,6 +16,8 @@ return new class () extends Migration {
|
||||
Schema::create(config('catch.module.table_name'), function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
|
||||
$table->string('title')->comment('模块标题');
|
||||
|
||||
$table->string('name')->comment('模块名称');
|
||||
|
||||
$table->string('path', 20)->comment('模块目录');
|
||||
|
@ -44,8 +44,8 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
||||
public function all(array $search): Collection
|
||||
{
|
||||
return $this->model::query()
|
||||
->when($search['name'] ?? false, function ($query) use ($search) {
|
||||
$query->where('name', 'like', '%'.$search['name'].'%');
|
||||
->when($search['title'] ?? false, function ($query) use ($search) {
|
||||
$query->where('title', 'like', '%'.$search['title'].'%');
|
||||
})->get();
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
||||
$this->hasSameModule($module);
|
||||
|
||||
return $this->model->save([
|
||||
'name' => $module['name'],
|
||||
'title' => $module['title'],
|
||||
'path' => $module['path'],
|
||||
'description' => $module['desc'],
|
||||
'keywords' => $module['keywords'],
|
||||
@ -91,7 +91,8 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
||||
return $this->model->where('name', $name)
|
||||
|
||||
->update([
|
||||
'name' => $module['name'],
|
||||
'title' => $module['title'],
|
||||
'name' => $module['path'],
|
||||
'path' => $module['path'],
|
||||
'description' => $module['desc'],
|
||||
'keywords' => $module['keywords'],
|
||||
@ -119,7 +120,7 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
||||
{
|
||||
$module = $this->show($name);
|
||||
|
||||
$module->status = (int) $module->status;
|
||||
$module->enable = (int) $module->enable;
|
||||
|
||||
return $module->save();
|
||||
}
|
||||
@ -132,7 +133,7 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
||||
public function getEnabled(): Collection
|
||||
{
|
||||
// TODO: Implement getEnabled() method.
|
||||
return $this->model->where('status', Status::Enable->value())->get();
|
||||
return $this->model->where('enable', Status::Enable->value())->get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,7 +145,7 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
||||
public function enabled(string $moduleName): bool
|
||||
{
|
||||
// TODO: Implement enabled() method.
|
||||
return $this->getEnabled()->pluck('path')->contains($moduleName);
|
||||
return $this->getEnabled()->pluck('name')->contains($moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,10 +158,6 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
||||
if ($this->model->where('name', $module['name'])->first()) {
|
||||
throw new FailedException(sprintf('Module [%s] has been created', $module['name']));
|
||||
}
|
||||
|
||||
if ($this->model->where('path', $module['path'])->first()) {
|
||||
throw new FailedException(sprintf('Module Alias [%s] has been exised', $module['alias']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,14 +55,14 @@ class FileDriver implements ModuleRepositoryInterface
|
||||
|
||||
$modules = Collection::make(\json_decode(File::get($this->moduleJson), true))->values();
|
||||
|
||||
$name = $search['name'] ?? '';
|
||||
$title = $search['title'] ?? '';
|
||||
|
||||
if (! $name) {
|
||||
if (! $title) {
|
||||
return $modules;
|
||||
}
|
||||
|
||||
return $modules->filter(function ($module) use ($name) {
|
||||
return Str::of($module['name'])->contains($name);
|
||||
return $modules->filter(function ($module) use ($title) {
|
||||
return Str::of($module['title'])->contains($title);
|
||||
});
|
||||
}
|
||||
|
||||
@ -82,6 +82,8 @@ class FileDriver implements ModuleRepositoryInterface
|
||||
$module['version'] = '1.0.0';
|
||||
$module['enable'] = true;
|
||||
|
||||
$this->removeDirs($module);
|
||||
|
||||
File::put($this->moduleJson, $modules->push($module)->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return true;
|
||||
@ -115,12 +117,13 @@ class FileDriver implements ModuleRepositoryInterface
|
||||
{
|
||||
File::put($this->moduleJson, $this->all()->map(function ($m) use ($module, $name) {
|
||||
if (Str::of($name)->exactly($m['name'])) {
|
||||
$m['path'] = $module['path'];
|
||||
$m['name'] = $module['name'];
|
||||
$m['title'] = $module['title'];
|
||||
$m['description'] = $module['description'] ?? '';
|
||||
$m['keywords'] = $module['keywords'] ?? '';
|
||||
$m['enable'] = $module['enable'];
|
||||
}
|
||||
$this->removeDirs($m);
|
||||
return $m;
|
||||
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
@ -180,7 +183,7 @@ class FileDriver implements ModuleRepositoryInterface
|
||||
public function enabled(string $moduleName): bool
|
||||
{
|
||||
// TODO: Implement enabled() method.
|
||||
return $this->getEnabled()->pluck('path')->contains($moduleName);
|
||||
return $this->getEnabled()->pluck('name')->contains($moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,10 +198,18 @@ class FileDriver implements ModuleRepositoryInterface
|
||||
if ($modules->pluck('name')->contains($module['name'])) {
|
||||
throw new FailedException(sprintf('Module [%s] has been created', $module['name']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($modules->pluck('path')->contains($module['path'])) {
|
||||
throw new FailedException(sprintf('Module path [%s] has been existed', $module['path']));
|
||||
}
|
||||
/**
|
||||
* remove dirs
|
||||
*
|
||||
* @param array $modules
|
||||
*/
|
||||
protected function removeDirs(array &$modules)
|
||||
{
|
||||
if ($modules['dirs'] ?? false) {
|
||||
unset($modules['dirs']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,8 @@ class ModuleRepository
|
||||
*/
|
||||
public function create(array $module): bool
|
||||
{
|
||||
$module['name'] = lcfirst($module['path']);
|
||||
|
||||
Event::dispatch(new Creating($module));
|
||||
|
||||
$this->moduleRepository->create($module);
|
||||
@ -93,6 +95,8 @@ class ModuleRepository
|
||||
*/
|
||||
public function update(string $name, array $module): bool
|
||||
{
|
||||
$module['name'] = lcfirst($module['path']);
|
||||
|
||||
Event::dispatch(new Updating($name, $module));
|
||||
|
||||
$this->moduleRepository->update($name, $module);
|
||||
|
@ -2,7 +2,7 @@
|
||||
<el-form :model="formData" label-width="120px" ref="form" v-loading="loading" class="pr-4">
|
||||
<el-form-item
|
||||
:label="$t('module.form.name.title')"
|
||||
prop="name"
|
||||
prop="title"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
@ -10,7 +10,7 @@
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-input v-model="formData.name" />
|
||||
<el-input v-model="formData.title" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('module.form.path.title')"
|
||||
@ -48,18 +48,18 @@
|
||||
import { useCreate } from '/admin/composables/curd/useCreate'
|
||||
import { useShow } from '/admin/composables/curd/useShow'
|
||||
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
primary: String | Number,
|
||||
api: String,
|
||||
})
|
||||
|
||||
const { formData, form, loading, submitForm, isClose } = useCreate(
|
||||
const { formData, form, loading, submitForm, close } = useCreate(
|
||||
props.api,
|
||||
props.primary,
|
||||
Object.assign({
|
||||
name: '',
|
||||
title: '',
|
||||
path: '',
|
||||
keywords: '',
|
||||
description: '',
|
||||
@ -73,13 +73,12 @@ const { formData, form, loading, submitForm, isClose } = useCreate(
|
||||
)
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
watch(isClose, function (value) {
|
||||
if (value) {
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
if (props.primary) {
|
||||
useShow(props.api, props.primary, formData)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
close(() => emit('close'))
|
||||
})
|
||||
</script>
|
||||
|
@ -3,14 +3,14 @@
|
||||
<Search :search="search" :reset="reset">
|
||||
<template v-slot:body>
|
||||
<el-form-item label="模块名称">
|
||||
<el-input v-model="query.name" name="name" clearable />
|
||||
<el-input v-model="query.title" name="title" clearable />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</Search>
|
||||
<div class="pl-2 pr-2 bg-white dark:bg-regal-dark rounded-lg mt-4 pb-6">
|
||||
<Operate :show="open" />
|
||||
<el-table :data="tableData" class="mt-3" v-loading="loading">
|
||||
<el-table-column prop="name" label="模块名称" width="180" />
|
||||
<el-table-column prop="title" label="模块名称" width="180" />
|
||||
<el-table-column prop="path" label="模块目录" width="180" />
|
||||
<el-table-column prop="version" label="模块版本">
|
||||
<template #default="scope">
|
||||
@ -19,7 +19,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="enable" label="模块状态">
|
||||
<template #default="scope">
|
||||
<Status v-model="scope.row.status" :id="scope.row.id" :api="api" />
|
||||
<Status v-model="scope.row.enable" :id="scope.row.name" :api="api" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300">
|
||||
@ -31,7 +31,7 @@
|
||||
</el-table>
|
||||
</div>
|
||||
<Dialog v-model="visible" :title="title" destroy-on-close>
|
||||
<Create @close="close" :primary="id" :api="api" />
|
||||
<Create @close="close(reset)" :primary="id" :api="api" />
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
@ -54,6 +54,6 @@ const tableData = computed(() => data.value?.data)
|
||||
onMounted(() => {
|
||||
search()
|
||||
|
||||
deleted()
|
||||
deleted(reset)
|
||||
})
|
||||
</script>
|
||||
|
40
modules/Options/Repository/Components.php
Normal file
40
modules/Options/Repository/Components.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace Modules\Options\Repository;
|
||||
|
||||
use Catch\CatchAdmin;
|
||||
use Catch\Support\Composer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Components implements OptionInterface
|
||||
{
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected array $components = [
|
||||
[
|
||||
'label' => 'layout',
|
||||
'value' => '/admin/layout/index.vue'
|
||||
]
|
||||
];
|
||||
|
||||
public function get(): array
|
||||
{
|
||||
if ($module = request()->get('module')) {
|
||||
$components = File::glob(CatchAdmin::getModuleViewsPath($module) . '*/*.vue');
|
||||
|
||||
foreach ($components as $component) {
|
||||
$this->components[] = [
|
||||
'label' => Str::of($component)->explode(DIRECTORY_SEPARATOR)->pop(2)->pop(),
|
||||
|
||||
'value' => Str::of($component)->replace(CatchAdmin::moduleRootPath(), '')->prepend('/')
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->components;
|
||||
}
|
||||
|
||||
}
|
32
modules/Options/Repository/Controllers.php
Normal file
32
modules/Options/Repository/Controllers.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Modules\Options\Repository;
|
||||
|
||||
use Catch\CatchAdmin;
|
||||
use Catch\Support\Composer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Controllers implements OptionInterface
|
||||
{
|
||||
public function get(): array
|
||||
{
|
||||
$controllers = [];
|
||||
|
||||
if ($module = request()->get('module')) {
|
||||
$controllerFiles = File::glob(CatchAdmin::getModuleControllerPath($module) . '*.php');
|
||||
|
||||
foreach ($controllerFiles as $controllerFile) {
|
||||
$controllers[] = [
|
||||
'label' => Str::of(File::name($controllerFile))->remove('Controller'),
|
||||
|
||||
'value' => Str::of(File::name($controllerFile))->remove('Controller'),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $controllers;
|
||||
}
|
||||
|
||||
}
|
@ -43,4 +43,15 @@ class PermissionsController extends Controller
|
||||
{
|
||||
return $this->model->deleteBy($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* enable
|
||||
*
|
||||
* @param $id
|
||||
* @return bool
|
||||
*/
|
||||
public function enable($id)
|
||||
{
|
||||
return $this->model->disOrEnable($id, 'hidden');
|
||||
}
|
||||
}
|
||||
|
@ -55,4 +55,14 @@ class PermissionsModel extends Model
|
||||
{
|
||||
return self::query()->select($this->fieldsInList)->quickSearch()->get()->toTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* is hidden
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isHidden(): bool
|
||||
{
|
||||
return $this->hidden === 2;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
@ -12,6 +13,10 @@ return new class () extends Migration {
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (Schema::hasTable('roles')) {
|
||||
return ;
|
||||
}
|
||||
|
||||
Schema::create('roles', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('role_name', 30)->comment('角色名称');
|
||||
|
@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
@ -12,6 +13,10 @@ return new class () extends Migration {
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (Schema::hasTable('jobs')) {
|
||||
return ;
|
||||
}
|
||||
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('job_name', 50)->comment('岗位名称');
|
||||
|
@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
@ -12,6 +13,10 @@ return new class () extends Migration {
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (Schema::hasTable('departments')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::create('departments', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('parent_id')->default(0)->comment('父级ID');
|
||||
|
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('permissions', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('permission_name', 30)->comment('菜单名称');
|
||||
$table->integer('parent_id')->default('1')->comment('父级菜单');
|
||||
$table->string('route', 50)->comment('路由');
|
||||
$table->string('icon', 50)->comment('菜单 icon');
|
||||
$table->string('module', 50)->comment('所属模块');
|
||||
$table->string('permission_mark')->comment('权限标识');
|
||||
$table->string('component')->comment('组件');
|
||||
$table->string('redirect')->comment('组件跳转');
|
||||
$table->tinyInteger('keepalive')->default('1')->comment('1 缓存 2 不缓存');
|
||||
$table->tinyInteger('type')->default('1')->comment('1 菜单 2 按钮');
|
||||
$table->tinyInteger('hidden')->default('1')->comment('1 显示 2 隐藏');
|
||||
$table->integer('sort')->default('1')->comment('排序');
|
||||
$table->creatorId();
|
||||
$table->createdAt();
|
||||
$table->updatedAt();
|
||||
$table->deletedAt();
|
||||
|
||||
$table->engine = 'InnoDB';
|
||||
$table->comment('权限模块');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('permissions');
|
||||
}
|
||||
};
|
@ -12,6 +12,10 @@ return new class () extends Migration {
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (Schema::hasTable('permissions')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::create('permissions', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('parent_id')->default(0)->comment('父级菜单');
|
||||
@ -19,7 +23,7 @@ return new class () extends Migration {
|
||||
$table->string('route', 50)->comment('前端路由');
|
||||
$table->string('icon', 50)->comment('菜单 icon');
|
||||
$table->string('module', 50)->comment('所属模块');
|
||||
$table->string('permission_mark', 100)->comment('权限标识,使用 @ 分割');
|
||||
$table->string('permission_mark', 100)->default('')->comment('权限标识,使用 @ 分割');
|
||||
$table->string('component')->comment('组件');
|
||||
$table->string('redirect')->nullable()->comment('跳转地址');
|
||||
$table->tinyInteger('keepalive')->default('1')->comment('1 缓存 2 不缓存');
|
||||
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('user_has_roles', function (Blueprint $table) {
|
||||
$table->integer('user_id')->comment('users primary key');
|
||||
|
||||
$table->integer('role_id')->comment('roles primary key');
|
||||
|
||||
$table->comment('user relate roles');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('role_has_permissions', function (Blueprint $table) {
|
||||
$table->integer('role_id')->comment('roles primary key');
|
||||
|
||||
$table->integer('permission_id')->comment('permissions primary key');
|
||||
|
||||
$table->comment('role relate permissions');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('user_has_jobs', function (Blueprint $table) {
|
||||
$table->integer('user_id')->comment('users primary key');
|
||||
|
||||
$table->integer('job_id')->comment('jobs primary key');
|
||||
|
||||
$table->comment('user relate jobs');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('role_has_departments', function (Blueprint $table) {
|
||||
$table->integer('role_id')->comment('roles primary key');
|
||||
|
||||
$table->integer('department_id')->comment('departments primary key');
|
||||
|
||||
$table->comment('role relate departments');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -16,5 +16,6 @@ Route::prefix('permissions')->group(function () {
|
||||
Route::put('departments/enable/{id}', [DepartmentsController::class, 'enable']);
|
||||
|
||||
Route::apiResource('permissions', PermissionsController::class);
|
||||
Route::put('permissions/enable/{id}', [PermissionsController::class, 'enable']);
|
||||
//next
|
||||
});
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import Create from './create.vue'
|
||||
import Create from './form/create.vue'
|
||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import Create from './create.vue'
|
||||
import Create from './form/create.vue'
|
||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||
|
@ -17,19 +17,53 @@
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单名称" prop="permission_name">
|
||||
<el-input v-model="formData.permission_name" name="permission_name" clearable />
|
||||
<el-form-item label="菜单名称" prop="permission_name" :rules="[{ required: true, message: '菜单名称必须填写' }]">
|
||||
<Select v-model="formData.permission_name" name="permission_name" :options="actionMenuNames" v-if="isAction" />
|
||||
<el-input v-model="formData.permission_name" name="permission_name" clearable v-else />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属模块" prop="module">
|
||||
<Select v-model="formData.module" clearable api="modules" class="w-full" filterable />
|
||||
<el-form-item label="所属模块" prop="module" :rules="[{ required: true, message: '所属模块必须填写' }]" v-if="!isAction">
|
||||
<Select v-model="formData.module" api="modules" @clear="clearModule" />
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单路由" prop="route">
|
||||
<el-form-item label="路由Path" prop="route" :rules="[{ required: true, message: '路由Path必须填写' }]" v-if="!isAction">
|
||||
<el-input v-model="formData.route" name="route" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="Redirect" prop="redirect">
|
||||
<el-form-item label="Redirect" prop="redirect" v-if="!isAction">
|
||||
<el-input v-model="formData.redirect" name="redirect" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="Keepalive" prop="keepalive">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" name="sort" :min="1" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div>
|
||||
<el-form-item label="父级菜单" prop="parent_id">
|
||||
<el-cascader :options="permissions" name="parent_id" v-model="formData.parent_id" clearable :props="{ value: 'id', label: 'permission_name', checkStrictly: true }" class="w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限标识" prop="permission_mark" :rules="[{ required: true, message: '权限标识必须填写' }]" v-if="!isTop">
|
||||
<Select v-model="formData.permission_mark" name="permission_mark" :options="actionMenuNames" allow-create v-if="isAction" />
|
||||
<Select v-model="formData.permission_mark" placeholder="请选择" api="controllers" :query="{ module: formData.module }" v-else />
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单Icon" prop="icon" v-if="!isAction">
|
||||
<el-input v-model="formData.icon" name="icon" clearable @focus="open" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组件" prop="component" :rules="[{ required: true, message: '所属组件必须填写' }]" v-if="!isAction">
|
||||
<Select v-model="formData.component" placeholder="请选择" allow-create api="components" :query="{ module: formData.module }" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Hidden" prop="hidden" v-if="!isAction">
|
||||
<el-radio-group v-model="formData.hidden">
|
||||
<el-radio
|
||||
v-for="item in [
|
||||
{ label: '显示', value: 1 },
|
||||
{ label: '隐藏', value: 2 },
|
||||
]"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
name="hidden"
|
||||
>{{ item.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="Keepalive" prop="keepalive" v-if="!isAction">
|
||||
<el-radio-group v-model="formData.keepalive">
|
||||
<el-radio
|
||||
v-for="item in [
|
||||
@ -44,48 +78,23 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div>
|
||||
<el-form-item label="父级菜单" prop="parent_id">
|
||||
<el-cascader :options="permissions" name="parent_id" v-model="formData.parent_id" clearable :props="{ value: 'id', label: 'permission_name', checkStrictly: true }" class="w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限标识" prop="permission_mark">
|
||||
<el-input v-model="formData.permission_mark" name="permission_mark" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单Icon" prop="icon">
|
||||
<el-input v-model="formData.icon" name="icon" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组件" prop="component">
|
||||
<el-select v-model="formData.component" placeholder="请选择" clearable class="w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" name="sort" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Hidden" prop="hidden">
|
||||
<el-radio-group v-model="formData.hidden">
|
||||
<el-radio
|
||||
v-for="item in [
|
||||
{ label: '显示', value: 1 },
|
||||
{ label: '隐藏', value: 2 },
|
||||
]"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
name="hidden"
|
||||
>{{ item.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<el-button type="primary" @click="submitForm(form)">{{ $t('system.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<Dialog v-model="visible" title="选择 Icon" width="1000px" destroy-on-close>
|
||||
<Icons v-model="formData.icon" @close="closeSelectIcon" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useCreate } from '/admin/composables/curd/useCreate'
|
||||
import { useShow } from '/admin/composables/curd/useShow'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import http from '/admin/support/http'
|
||||
|
||||
const props = defineProps({
|
||||
@ -94,12 +103,23 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const { formData, form, loading, submitForm, close, beforeCreate, beforeUpdate } = useCreate(props.api, props.primary)
|
||||
const { open, visible } = useOpen()
|
||||
|
||||
// 关闭选择
|
||||
const closeSelectIcon = () => {
|
||||
visible.value = false
|
||||
}
|
||||
// 初始化
|
||||
formData.value.sort = 1
|
||||
formData.value.keepalive = 1
|
||||
formData.value.type = 1
|
||||
formData.value.hidden = 1
|
||||
// 默认目录
|
||||
const isTop = ref<boolean>(true)
|
||||
const isMenu = ref<boolean>(false)
|
||||
const isAction = ref<boolean>(false)
|
||||
|
||||
// 回显示表单
|
||||
if (props.primary) {
|
||||
useShow(props.api, props.primary, formData)
|
||||
}
|
||||
@ -110,14 +130,68 @@ onMounted(() => {
|
||||
http.get(props.api).then(r => {
|
||||
permissions.value = r.data.data
|
||||
})
|
||||
|
||||
// close dialog
|
||||
close(() => emit('close'))
|
||||
|
||||
// 监听 form data
|
||||
watch(
|
||||
formData,
|
||||
(value, oldValue) => {
|
||||
const type: number = formData.value.type
|
||||
|
||||
if (type === 1) {
|
||||
isTop.value = true
|
||||
isMenu.value = isAction.value = false
|
||||
} else if (type === 2) {
|
||||
isMenu.value = true
|
||||
isTop.value = isAction.value = false
|
||||
} else {
|
||||
isAction.value = true
|
||||
isTop.value = isMenu.value = false
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
})
|
||||
|
||||
// 菜单是菜单类型的时,清除模块,那么权限标识&组件也需要清除
|
||||
const clearModule = () => {
|
||||
if (formData.value.type === 1 || formData.value.type === 2) {
|
||||
formData.value.component = null
|
||||
}
|
||||
if (formData.value.type === 2) {
|
||||
formData.value.permission_mark = null
|
||||
}
|
||||
}
|
||||
|
||||
// 当菜单是按钮类型时, 定义两个初始值
|
||||
const actionMenuNames = [
|
||||
{ label: '列表', value: '列表' },
|
||||
{ label: '新增', value: '新增' },
|
||||
{ label: '读取', value: '读取' },
|
||||
{ label: '更新', value: '更新' },
|
||||
{ label: '删除', value: '删除' },
|
||||
{ label: '禁用/启用', value: '禁用/启用' },
|
||||
{ label: '导入', value: '导入' },
|
||||
{ label: '导出', value: '导出' },
|
||||
]
|
||||
|
||||
const actionMenuMark = [
|
||||
{ label: 'index', value: 'index' },
|
||||
{ label: 'store', value: 'store' },
|
||||
{ label: 'show', value: 'show' },
|
||||
{ label: 'update', value: 'update' },
|
||||
{ label: 'destroy', value: 'destroy' },
|
||||
{ label: 'enable', value: 'enable' },
|
||||
{ label: 'import', value: 'import' },
|
||||
{ label: 'export', value: 'export' },
|
||||
]
|
||||
// 创建前的钩子
|
||||
beforeCreate.value = () => {
|
||||
formData.value.parent_id = getParent(formData.value.parent_id)
|
||||
}
|
||||
|
||||
// 更新前的钩子
|
||||
beforeUpdate.value = () => {
|
||||
formData.value.parent_id = getParent(formData.value.parent_id)
|
||||
}
|
@ -13,7 +13,11 @@
|
||||
<el-table-column prop="permission_name" label="菜单名称" />
|
||||
<el-table-column prop="route" label="菜单路由" />
|
||||
<el-table-column prop="permission_mark" label="权限标识" />
|
||||
<el-table-column prop="hidden" label="状态" />
|
||||
<el-table-column prop="hidden" label="状态">
|
||||
<template #default="scope">
|
||||
<Status v-model="scope.row.hidden" :id="scope.row.id" :api="api" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" />
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="scope">
|
||||
@ -31,8 +35,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import Create from './create.vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import Create from './form/create.vue'
|
||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import Create from './create.vue'
|
||||
import Create from './form/create.vue'
|
||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||
|
@ -35,4 +35,4 @@ const router: RouteRecordRaw[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export default router
|
||||
export default []
|
||||
|
@ -89,7 +89,7 @@ class UserController extends Controller
|
||||
public function online(Request $request)
|
||||
{
|
||||
/* @var Users $user */
|
||||
$user = $this->getLoginUser();
|
||||
$user = $this->getLoginUser()->withPermissions();
|
||||
|
||||
if ($request->isMethod('post')) {
|
||||
return $user->updateBy($user->id, $request->all());
|
||||
|
98
modules/User/Models/Traits/UserRelations.php
Normal file
98
modules/User/Models/Traits/UserRelations.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace Modules\User\Models\Traits;
|
||||
|
||||
|
||||
use Catch\CatchAdmin;
|
||||
use Catch\Support\Module\ModuleRepository;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
trait UserRelations
|
||||
{
|
||||
|
||||
/**
|
||||
* init traits
|
||||
*/
|
||||
public function initializeUserRelations(): void
|
||||
{
|
||||
if (app(ModuleRepository::class)->enabled('permissions')) {
|
||||
$this->with = ['roles', 'jobs'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* roles
|
||||
*
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany($this->getRolesModel(), 'user_has_roles', 'user_id', 'role_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* jobs
|
||||
*
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function jobs(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany($this->getJobsModel(), 'user_has_jobs', 'user_id', 'job_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* permissions
|
||||
*/
|
||||
public function withPermissions(): self
|
||||
{
|
||||
/* @var \Modules\Permissions\Models\PermissionsModel $permissionsModel */
|
||||
$permissionsModel = app($this->getPermissionsModel());
|
||||
|
||||
if ($this->isSuperAdmin()) {
|
||||
$permissions = $permissionsModel->get();
|
||||
} else {
|
||||
$roles = app($this->getRolesModel())->with(['permissions'])->get();
|
||||
|
||||
$permissions = [];
|
||||
}
|
||||
|
||||
$this->setAttribute('permissions', $permissions->each(fn($permission) => $permission->setAttribute('hidden', $permission->isHidden())));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get RolesModel
|
||||
*
|
||||
* @see \Modules\Permissions\Models\RolesModel
|
||||
* @return string
|
||||
*/
|
||||
protected function getRolesModel(): string
|
||||
{
|
||||
return '\\' . CatchAdmin::getModuleModelNamespace('permissions') . 'RolesModel';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get JobsModel
|
||||
*
|
||||
* @see \Modules\Permissions\Models\JobsModel
|
||||
* @return string
|
||||
*/
|
||||
protected function getJobsModel(): string
|
||||
{
|
||||
return '\\'. CatchAdmin::getModuleModelNamespace('permissions') . 'JobsModel';
|
||||
}
|
||||
|
||||
/**
|
||||
* get PermissionsModel
|
||||
*
|
||||
* @see \Modules\Permissions\Models\PermissionsModel
|
||||
* @return string
|
||||
*/
|
||||
protected function getPermissionsModel(): string
|
||||
{
|
||||
return '\\'. CatchAdmin::getModuleModelNamespace('permissions') . 'PermissionsModel';
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ namespace Modules\User\Models;
|
||||
use Catch\Base\CatchModel as Model;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Modules\User\Models\Traits\UserRelations;
|
||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
|
||||
@ -24,7 +25,7 @@ use Illuminate\Auth\Authenticatable;
|
||||
*/
|
||||
class Users extends Model implements AuthenticatableContract, JWTSubject
|
||||
{
|
||||
use Authenticatable;
|
||||
use Authenticatable, UserRelations;
|
||||
|
||||
protected $fillable = [
|
||||
'id', 'username', 'email', 'avatar', 'password', 'remember_token', 'creator_id', 'status', 'login_ip', 'login_at', 'created_at', 'updated_at', 'deleted_at'
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-select v-bind="$attrs">
|
||||
<el-select v-bind="$attrs" class="w-full" clearable filterable>
|
||||
<el-option-group v-for="group in elOptions" :key="group.label" :label="group.label" v-if="group">
|
||||
<el-option v-for="item in group.options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-option-group>
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import http from '/admin/support/http'
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
@ -26,6 +26,10 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
query: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
interface Option {
|
||||
@ -38,11 +42,21 @@ interface GroupOption {
|
||||
options: Array<Option>
|
||||
}
|
||||
|
||||
const elOptions = props.group ? ref<Array<GroupOption>>() : ref<Array<Option>>()
|
||||
if (props.api) {
|
||||
http.get('options/' + props.api).then(r => {
|
||||
const getOptions = () => {
|
||||
http.get('options/' + props.api, props.query).then(r => {
|
||||
elOptions.value = r.data.data
|
||||
})
|
||||
}
|
||||
|
||||
const elOptions = props.group ? ref<Array<GroupOption>>() : ref<Array<Option>>()
|
||||
if (props.api) {
|
||||
if (!props.query) {
|
||||
getOptions()
|
||||
} else {
|
||||
watch(props, function () {
|
||||
getOptions()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
elOptions.value = props.options
|
||||
}
|
||||
|
332
resources/admin/components/admin/icons/index.vue
Normal file
332
resources/admin/components/admin/icons/index.vue
Normal file
@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div :class="`grid ${grid} gap-y-6`">
|
||||
<div v-for="icon in icons" :key="icon" class="flex justify-center hover:cursor-pointer" @click="selectIcon(icon)">
|
||||
<div v-if="modelValue === icon">
|
||||
<div class="flex justify-center w-full text-violet-700"><Icon :name="icon" /></div>
|
||||
<div class="text-sm text-violet-700">{{ icon }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="flex justify-center w-full"><Icon :name="icon" /></div>
|
||||
<div class="text-sm">{{ icon }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
grid: {
|
||||
type: String,
|
||||
default: 'grid-cols-5',
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
const selectIcon = (icon: string) => {
|
||||
emits('update:modelValue', icon)
|
||||
emits('close')
|
||||
}
|
||||
// icons
|
||||
const icons = [
|
||||
'academic-cap',
|
||||
'adjustments-horizontal',
|
||||
'adjustments-vertical',
|
||||
'archive-box-arrow-down',
|
||||
'archive-box-x-mark',
|
||||
'archive-box',
|
||||
'arrow-down-circle',
|
||||
'arrow-down-left',
|
||||
'arrow-down-on-square-stack',
|
||||
'arrow-down-on-square',
|
||||
'arrow-down-right',
|
||||
'arrow-down-tray',
|
||||
'arrow-down',
|
||||
'arrow-left-circle',
|
||||
'arrow-left-on-rectangle',
|
||||
'arrow-left',
|
||||
'arrow-long-down',
|
||||
'arrow-long-left',
|
||||
'arrow-long-right',
|
||||
'arrow-long-up',
|
||||
'arrow-path-rounded-square',
|
||||
'arrow-path',
|
||||
'arrow-right-circle',
|
||||
'arrow-right-on-rectangle',
|
||||
'arrow-right',
|
||||
'arrow-small-down',
|
||||
'arrow-small-left',
|
||||
'arrow-small-right',
|
||||
'arrow-small-up',
|
||||
'arrow-top-right-on-square',
|
||||
'arrow-trending-down',
|
||||
'arrow-trending-up',
|
||||
'arrow-up-circle',
|
||||
'arrow-up-left',
|
||||
'arrow-up-on-square-stack',
|
||||
'arrow-up-on-square',
|
||||
'arrow-up-right',
|
||||
'arrow-up-tray',
|
||||
'arrow-up',
|
||||
'arrow-uturn-down',
|
||||
'arrow-uturn-left',
|
||||
'arrow-uturn-right',
|
||||
'arrow-uturn-up',
|
||||
'arrows-pointing-in',
|
||||
'arrows-pointing-out',
|
||||
'arrows-right-left',
|
||||
'arrows-up-down',
|
||||
'at-symbol',
|
||||
'backspace',
|
||||
'backward',
|
||||
'banknotes',
|
||||
'bars-2',
|
||||
'bars-3-bottom-left',
|
||||
'bars-3-bottom-right',
|
||||
'bars-3-center-left',
|
||||
'bars-3',
|
||||
'bars-4',
|
||||
'bars-arrow-down',
|
||||
'bars-arrow-up',
|
||||
'battery-0',
|
||||
'battery-100',
|
||||
'battery-50',
|
||||
'beaker',
|
||||
'bell-alert',
|
||||
'bell-slash',
|
||||
'bell-snooze',
|
||||
'bell',
|
||||
'bolt-slash',
|
||||
'bolt',
|
||||
'book-open',
|
||||
'bookmark-slash',
|
||||
'bookmark-square',
|
||||
'bookmark',
|
||||
'briefcase',
|
||||
'bug-ant',
|
||||
'building-library',
|
||||
'building-office-2',
|
||||
'building-office',
|
||||
'building-storefront',
|
||||
'cake',
|
||||
'calculator',
|
||||
'calendar-days',
|
||||
'calendar',
|
||||
'camera',
|
||||
'chart-bar-square',
|
||||
'chart-bar',
|
||||
'chart-pie',
|
||||
'chat-bubble-bottom-center-text',
|
||||
'chat-bubble-bottom-center',
|
||||
'chat-bubble-left-ellipsis',
|
||||
'chat-bubble-left-right',
|
||||
'chat-bubble-left',
|
||||
'chat-bubble-oval-left-ellipsis',
|
||||
'chat-bubble-oval-left',
|
||||
'check-badge',
|
||||
'check-circle',
|
||||
'check',
|
||||
'chevron-double-down',
|
||||
'chevron-double-left',
|
||||
'chevron-double-right',
|
||||
'chevron-double-up',
|
||||
'chevron-down',
|
||||
'chevron-left',
|
||||
'chevron-right',
|
||||
'chevron-up-down',
|
||||
'chevron-up',
|
||||
'circle-stack',
|
||||
'clipboard-document-check',
|
||||
'clipboard-document-list',
|
||||
'clipboard-document',
|
||||
'clipboard',
|
||||
'clock',
|
||||
'cloud-arrow-down',
|
||||
'cloud-arrow-up',
|
||||
'cloud',
|
||||
'code-bracket-square',
|
||||
'code-bracket',
|
||||
'cog-6-tooth',
|
||||
'cog-8-tooth',
|
||||
'cog',
|
||||
'command-line',
|
||||
'computer-desktop',
|
||||
'cpu-chip',
|
||||
'credit-card',
|
||||
'cube-transparent',
|
||||
'cube',
|
||||
'currency-bangladeshi',
|
||||
'currency-dollar',
|
||||
'currency-euro',
|
||||
'currency-pound',
|
||||
'currency-rupee',
|
||||
'currency-yen',
|
||||
'cursor-arrow-rays',
|
||||
'cursor-arrow-ripple',
|
||||
'device-phone-mobile',
|
||||
'device-tablet',
|
||||
'document-arrow-down',
|
||||
'document-arrow-up',
|
||||
'document-chart-bar',
|
||||
'document-check',
|
||||
'document-duplicate',
|
||||
'document-magnifying-glass',
|
||||
'document-minus',
|
||||
'document-plus',
|
||||
'document-text',
|
||||
'document',
|
||||
'ellipsis-horizontal-circle',
|
||||
'ellipsis-horizontal',
|
||||
'ellipsis-vertical',
|
||||
'envelope-open',
|
||||
'envelope',
|
||||
'exclamation-circle',
|
||||
'exclamation-triangle',
|
||||
'eye-dropper',
|
||||
'eye-slash',
|
||||
'eye',
|
||||
'face-frown',
|
||||
'face-smile',
|
||||
'film',
|
||||
'finger-print',
|
||||
'fire',
|
||||
'flag',
|
||||
'folder-arrow-down',
|
||||
'folder-minus',
|
||||
'folder-open',
|
||||
'folder-plus',
|
||||
'folder',
|
||||
'forward',
|
||||
'funnel',
|
||||
'gif',
|
||||
'gift-top',
|
||||
'gift',
|
||||
'globe-alt',
|
||||
'globe-americas',
|
||||
'globe-asia-australia',
|
||||
'globe-europe-africa',
|
||||
'hand-raised',
|
||||
'hand-thumb-down',
|
||||
'hand-thumb-up',
|
||||
'hashtag',
|
||||
'heart',
|
||||
'home-modern',
|
||||
'home',
|
||||
'identification',
|
||||
'inbox-arrow-down',
|
||||
'inbox-stack',
|
||||
'inbox',
|
||||
'information-circle',
|
||||
'key',
|
||||
'language',
|
||||
'lifebuoy',
|
||||
'light-bulb',
|
||||
'link',
|
||||
'list-bullet',
|
||||
'lock-closed',
|
||||
'lock-open',
|
||||
'magnifying-glass-circle',
|
||||
'magnifying-glass-minus',
|
||||
'magnifying-glass-plus',
|
||||
'magnifying-glass',
|
||||
'map-pin',
|
||||
'map',
|
||||
'megaphone',
|
||||
'microphone',
|
||||
'minus-circle',
|
||||
'minus-small',
|
||||
'minus',
|
||||
'moon',
|
||||
'musical-note',
|
||||
'newspaper',
|
||||
'no-symbol',
|
||||
'paint-brush',
|
||||
'paper-airplane',
|
||||
'paper-clip',
|
||||
'pause-circle',
|
||||
'pause',
|
||||
'pencil-square',
|
||||
'pencil',
|
||||
'phone-arrow-down-left',
|
||||
'phone-arrow-up-right',
|
||||
'phone-x-mark',
|
||||
'phone',
|
||||
'photo',
|
||||
'play-circle',
|
||||
'play-pause',
|
||||
'play',
|
||||
'plus-circle',
|
||||
'plus-small',
|
||||
'plus',
|
||||
'power',
|
||||
'presentation-chart-bar',
|
||||
'presentation-chart-line',
|
||||
'printer',
|
||||
'puzzle-piece',
|
||||
'qr-code',
|
||||
'question-mark-circle',
|
||||
'queue-list',
|
||||
'radio',
|
||||
'receipt-percent',
|
||||
'receipt-refund',
|
||||
'rectangle-group',
|
||||
'rectangle-stack',
|
||||
'rocket-launch',
|
||||
'rss',
|
||||
'scale',
|
||||
'scissors',
|
||||
'server-stack',
|
||||
'server',
|
||||
'share',
|
||||
'shield-check',
|
||||
'shield-exclamation',
|
||||
'shopping-bag',
|
||||
'shopping-cart',
|
||||
'signal-slash',
|
||||
'signal',
|
||||
'sparkles',
|
||||
'speaker-wave',
|
||||
'speaker-x-mark',
|
||||
'square-2-stack',
|
||||
'square-3-stack-3d',
|
||||
'squares-2x2',
|
||||
'squares-plus',
|
||||
'star',
|
||||
'stop-circle',
|
||||
'stop',
|
||||
'sun',
|
||||
'swatch',
|
||||
'table-cells',
|
||||
'tag',
|
||||
'ticket',
|
||||
'trash',
|
||||
'trophy',
|
||||
'truck',
|
||||
'tv',
|
||||
'user-circle',
|
||||
'user-group',
|
||||
'user-minus',
|
||||
'user-plus',
|
||||
'user',
|
||||
'users',
|
||||
'variable',
|
||||
'video-camera-slash',
|
||||
'video-camera',
|
||||
'view-columns',
|
||||
'viewfinder-circle',
|
||||
'wallet',
|
||||
'wifi',
|
||||
'window',
|
||||
'wrench-screwdriver',
|
||||
'wrench',
|
||||
'x-circle',
|
||||
'x-mark',
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<el-switch @change="enabled(api, id)" :active-value="Status.ENABLE" :inactive-value="Status.DISABLE" :model-value="modelValue" :loading="loading" />
|
||||
<el-switch @change="enabled(api, id)" :active-value="activeValue" :inactive-value="inactiveValue" :model-value="modelValue" :loading="loading" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useEnabled } from '/admin/composables/curd/useEnabled'
|
||||
import { Status } from '/admin/enum/app'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: Boolean | Number | String,
|
||||
@ -16,8 +17,19 @@ const emits = defineEmits(['update:modelValue', 'refresh'])
|
||||
|
||||
const { enabled, success, loading, afterEnabled } = useEnabled()
|
||||
|
||||
const activeValue = ref<boolean | number | string>()
|
||||
const inactiveValue = ref<boolean | number | string>()
|
||||
|
||||
if (typeof props.modelValue === 'boolean') {
|
||||
activeValue.value = true
|
||||
inactiveValue.value = false
|
||||
} else {
|
||||
activeValue.value = Status.ENABLE
|
||||
inactiveValue.value = Status.DISABLE
|
||||
}
|
||||
|
||||
success(() => {
|
||||
emits('update:modelValue', props.modelValue === Status.ENABLE ? Status.DISABLE : Status.ENABLE)
|
||||
emits('update:modelValue', props.modelValue === activeValue.value ? inactiveValue.value : activeValue.value)
|
||||
})
|
||||
|
||||
afterEnabled.value = () => {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ref } from 'vue'
|
||||
import { t } from '/admin/support/helper'
|
||||
|
||||
const visible = ref<boolean>(false)
|
||||
const id = ref(null)
|
||||
const title = ref<string>('')
|
||||
export function useOpen() {
|
||||
const visible = ref<boolean>(false)
|
||||
const id = ref(null)
|
||||
const title = ref<string>('')
|
||||
|
||||
const open = (primary: any) => {
|
||||
console.log(primary)
|
||||
title.value = primary ? t('system.edit') : t('system.add')
|
||||
id.value = primary
|
||||
visible.value = true
|
||||
|
@ -2,14 +2,14 @@ import http from '/admin/support/http'
|
||||
import { Ref, ref } from 'vue'
|
||||
import { isFunction } from '../../support/helper'
|
||||
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
const data = ref<object>()
|
||||
|
||||
// 后置钩子
|
||||
const afterShow = ref()
|
||||
|
||||
export function useShow(path: string, id: string | number, fillData: null | Ref = null) {
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
const data = ref<object>()
|
||||
|
||||
// 后置钩子
|
||||
const afterShow = ref()
|
||||
|
||||
http.get(path + '/' + id).then(r => {
|
||||
loading.value = false
|
||||
data.value = r.data.data
|
||||
|
@ -37,7 +37,7 @@ export const enum WhiteListPage {
|
||||
* menu 类型
|
||||
*/
|
||||
export const enum MenuType {
|
||||
PAGE_TYPE = 1,
|
||||
|
||||
Button_Type,
|
||||
TOP_TYPE = 1,
|
||||
PAGE_TYPE = 2,
|
||||
Button_Type = 3,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { h, defineComponent, VNode } from 'vue'
|
||||
import { h, defineComponent, VNode, toRaw } from 'vue'
|
||||
import { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
||||
import MenuItem from './item.vue'
|
||||
import menus from './menus.vue'
|
||||
@ -106,8 +106,10 @@ export default defineComponent({
|
||||
|
||||
// 后端的 permissions 返回 undefined,则认为该后端无权限系统
|
||||
const permissions = userStore.getPermissions === undefined ? [] : userStore.getPermissions
|
||||
const vnodes = getVNodes(filterMenus(permissionsStore.getMenusFrom(permissions)), props.subMenuClass)
|
||||
|
||||
console.log(permissionsStore.getMenusFrom(permissions))
|
||||
console.log(filterMenus(permissionsStore.getMenusFrom(permissions)))
|
||||
const vnodes = getVNodes(filterMenus(permissionsStore.getMenusFrom(permissions)), props.subMenuClass)
|
||||
return () => {
|
||||
return h(
|
||||
menus,
|
||||
|
@ -1,9 +1,16 @@
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
// @ts-ignore
|
||||
const modules = import.meta.glob('@/module/**/views/router.ts', { eager: true })
|
||||
let moduleRoutes: RouteRecordRaw[] = []
|
||||
export function getModuleRoutes() {
|
||||
const modules = import.meta.glob('@/module/**/views/router.ts', { eager: true })
|
||||
let moduleRoutes: RouteRecordRaw[] = []
|
||||
|
||||
Object.keys(modules).forEach(routePath => {
|
||||
moduleRoutes = moduleRoutes.concat(modules[routePath].default)
|
||||
})
|
||||
export default moduleRoutes
|
||||
Object.keys(modules).forEach(routePath => {
|
||||
moduleRoutes = moduleRoutes.concat(modules[routePath].default)
|
||||
})
|
||||
|
||||
return moduleRoutes
|
||||
}
|
||||
|
||||
export function getModuleViewComponents() {
|
||||
return import.meta.glob(['@/module/**/views/**/*.vue', '@/module/!User/views/**/*.vue', '@/module/!Develop/views/**/*.vue', '@/module/!Options/views/**/*.vue'])
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { WhiteListPage } from '/admin/enum/app'
|
||||
import { Router, RouteRecordRaw } from 'vue-router'
|
||||
import { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
||||
import { Menu } from '/admin/types/Menu'
|
||||
import { toRaw } from 'vue'
|
||||
|
||||
const guard = (router: Router) => {
|
||||
// white list
|
||||
@ -36,7 +37,7 @@ const guard = (router: Router) => {
|
||||
// 挂载路由(实际是从后端获取用户的权限)
|
||||
const permissionStore = usePermissionsStore()
|
||||
// 动态路由挂载
|
||||
const asyncRoutes = permissionStore.getAsyncMenusFrom(userStore.getPermissions)
|
||||
const asyncRoutes = permissionStore.getAsyncMenusFrom(toRaw(userStore.getPermissions))
|
||||
asyncRoutes.forEach((route: Menu) => {
|
||||
router.addRoute(route as unknown as RouteRecordRaw)
|
||||
})
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||
import type { App } from 'vue'
|
||||
// module routers
|
||||
import moduleRoutes from './constantRoutes'
|
||||
import { getModuleRoutes, getModuleViewComponents } from './constantRoutes'
|
||||
|
||||
const moduleRoutes = getModuleRoutes()
|
||||
getModuleViewComponents()
|
||||
export const constantRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
|
@ -128,7 +128,7 @@ export const useUserStore = defineStore('UserStore', {
|
||||
getUserInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
http
|
||||
.get('/user/info')
|
||||
.get('/user/online')
|
||||
.then(response => {
|
||||
const { id, nickname, email, avatar, permissions, roles, rememberToken, status } = response.data.data
|
||||
// set user info
|
||||
|
@ -4,6 +4,8 @@ import { MenuType } from '/admin/enum/app'
|
||||
import { Menu } from '/admin/types/Menu'
|
||||
import { constantRoutes } from '/admin/router'
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
import { toRaw } from 'vue'
|
||||
import { getModuleViewComponents } from '/admin/router/constantRoutes'
|
||||
|
||||
interface Permissions {
|
||||
menus: Menu[]
|
||||
@ -68,15 +70,15 @@ export const usePermissionsStore = defineStore('PermissionsStore', {
|
||||
const menus: Permission[] = []
|
||||
|
||||
permissions.forEach(permission => {
|
||||
if (permission.type === MenuType.PAGE_TYPE) {
|
||||
if (permission.type === MenuType.PAGE_TYPE || permission.type === MenuType.TOP_TYPE) {
|
||||
menus.push(permission)
|
||||
}
|
||||
|
||||
// set map
|
||||
this.menuPathMap.set(permission.route, permission.title)
|
||||
this.menuPathMap.set(permission.route, permission.permission_name)
|
||||
})
|
||||
|
||||
this.setAsyncMenus(this.getAsnycMenus(menus))
|
||||
this.setAsyncMenus(this.getAsnycMenus(menus, 0, '', getModuleViewComponents()))
|
||||
|
||||
return this.asyncMenus
|
||||
},
|
||||
@ -94,7 +96,7 @@ export const usePermissionsStore = defineStore('PermissionsStore', {
|
||||
}
|
||||
const asyncMenus = this.getAsyncMenusFrom(permissions, force)
|
||||
|
||||
this.setMenus(asyncMenus)
|
||||
this.setMenus(toRaw(asyncMenus))
|
||||
|
||||
return this.menus
|
||||
},
|
||||
@ -118,23 +120,31 @@ export const usePermissionsStore = defineStore('PermissionsStore', {
|
||||
* @param permissions
|
||||
* @param parentId
|
||||
* @param path
|
||||
* @param viewComponents
|
||||
* @returns
|
||||
*/
|
||||
getAsnycMenus(permissions: Permission[], parentId: number = 0, path: string = ''): Menu[] {
|
||||
getAsnycMenus(permissions: Permission[], parentId: number = 0, path: string = '', viewComponents: any): Menu[] {
|
||||
const menus: Menu[] = []
|
||||
|
||||
console.log(viewComponents)
|
||||
permissions.forEach(permission => {
|
||||
if (permission.parent_id === parentId) {
|
||||
// menu
|
||||
let importComponent
|
||||
if (permission.type === MenuType.TOP_TYPE) {
|
||||
importComponent = () => import(permission.component)
|
||||
} else {
|
||||
importComponent = viewComponents['/modules' + permission.component]
|
||||
}
|
||||
const menu: Menu = Object.assign({
|
||||
path: this.resoulveRoutePath(permission.route, path),
|
||||
name: permission.module + '_' + permission.permission_mark,
|
||||
component: importComponent,
|
||||
redirect: permission.redirect,
|
||||
meta: Object.assign({ title: permission.title, icon: permission.icon, hidden: permission.hidden, is_inner: permission.is_inner }),
|
||||
meta: Object.assign({ title: permission.permission_name, icon: permission.icon, hidden: permission.hidden, is_inner: permission.is_inner }),
|
||||
})
|
||||
|
||||
// child menu
|
||||
const children = this.getAsnycMenus(permissions, permission.id, menu.path)
|
||||
const children = this.getAsnycMenus(permissions, permission.id, menu.path, viewComponents)
|
||||
if (children.length > 0) {
|
||||
menu.children = children
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ export interface Permission {
|
||||
|
||||
parent_id: number
|
||||
|
||||
title: string
|
||||
permission_name: string
|
||||
|
||||
type: number
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user