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) {
|
Schema::create(config('catch.module.table_name'), function (Blueprint $table) {
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
|
|
||||||
|
$table->string('title')->comment('模块标题');
|
||||||
|
|
||||||
$table->string('name')->comment('模块名称');
|
$table->string('name')->comment('模块名称');
|
||||||
|
|
||||||
$table->string('path', 20)->comment('模块目录');
|
$table->string('path', 20)->comment('模块目录');
|
||||||
|
@ -44,8 +44,8 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
|||||||
public function all(array $search): Collection
|
public function all(array $search): Collection
|
||||||
{
|
{
|
||||||
return $this->model::query()
|
return $this->model::query()
|
||||||
->when($search['name'] ?? false, function ($query) use ($search) {
|
->when($search['title'] ?? false, function ($query) use ($search) {
|
||||||
$query->where('name', 'like', '%'.$search['name'].'%');
|
$query->where('title', 'like', '%'.$search['title'].'%');
|
||||||
})->get();
|
})->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
|||||||
$this->hasSameModule($module);
|
$this->hasSameModule($module);
|
||||||
|
|
||||||
return $this->model->save([
|
return $this->model->save([
|
||||||
'name' => $module['name'],
|
'title' => $module['title'],
|
||||||
'path' => $module['path'],
|
'path' => $module['path'],
|
||||||
'description' => $module['desc'],
|
'description' => $module['desc'],
|
||||||
'keywords' => $module['keywords'],
|
'keywords' => $module['keywords'],
|
||||||
@ -91,7 +91,8 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
|||||||
return $this->model->where('name', $name)
|
return $this->model->where('name', $name)
|
||||||
|
|
||||||
->update([
|
->update([
|
||||||
'name' => $module['name'],
|
'title' => $module['title'],
|
||||||
|
'name' => $module['path'],
|
||||||
'path' => $module['path'],
|
'path' => $module['path'],
|
||||||
'description' => $module['desc'],
|
'description' => $module['desc'],
|
||||||
'keywords' => $module['keywords'],
|
'keywords' => $module['keywords'],
|
||||||
@ -119,7 +120,7 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
|||||||
{
|
{
|
||||||
$module = $this->show($name);
|
$module = $this->show($name);
|
||||||
|
|
||||||
$module->status = (int) $module->status;
|
$module->enable = (int) $module->enable;
|
||||||
|
|
||||||
return $module->save();
|
return $module->save();
|
||||||
}
|
}
|
||||||
@ -132,7 +133,7 @@ class DatabaseDriver implements ModuleRepositoryInterface
|
|||||||
public function getEnabled(): Collection
|
public function getEnabled(): Collection
|
||||||
{
|
{
|
||||||
// TODO: Implement getEnabled() method.
|
// 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
|
public function enabled(string $moduleName): bool
|
||||||
{
|
{
|
||||||
// TODO: Implement enabled() method.
|
// 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()) {
|
if ($this->model->where('name', $module['name'])->first()) {
|
||||||
throw new FailedException(sprintf('Module [%s] has been created', $module['name']));
|
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();
|
$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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $modules->filter(function ($module) use ($name) {
|
return $modules->filter(function ($module) use ($title) {
|
||||||
return Str::of($module['name'])->contains($name);
|
return Str::of($module['title'])->contains($title);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +82,8 @@ class FileDriver implements ModuleRepositoryInterface
|
|||||||
$module['version'] = '1.0.0';
|
$module['version'] = '1.0.0';
|
||||||
$module['enable'] = true;
|
$module['enable'] = true;
|
||||||
|
|
||||||
|
$this->removeDirs($module);
|
||||||
|
|
||||||
File::put($this->moduleJson, $modules->push($module)->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
File::put($this->moduleJson, $modules->push($module)->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -115,12 +117,13 @@ class FileDriver implements ModuleRepositoryInterface
|
|||||||
{
|
{
|
||||||
File::put($this->moduleJson, $this->all()->map(function ($m) use ($module, $name) {
|
File::put($this->moduleJson, $this->all()->map(function ($m) use ($module, $name) {
|
||||||
if (Str::of($name)->exactly($m['name'])) {
|
if (Str::of($name)->exactly($m['name'])) {
|
||||||
$m['path'] = $module['path'];
|
|
||||||
$m['name'] = $module['name'];
|
$m['name'] = $module['name'];
|
||||||
|
$m['title'] = $module['title'];
|
||||||
$m['description'] = $module['description'] ?? '';
|
$m['description'] = $module['description'] ?? '';
|
||||||
$m['keywords'] = $module['keywords'] ?? '';
|
$m['keywords'] = $module['keywords'] ?? '';
|
||||||
$m['enable'] = $module['enable'];
|
$m['enable'] = $module['enable'];
|
||||||
}
|
}
|
||||||
|
$this->removeDirs($m);
|
||||||
return $m;
|
return $m;
|
||||||
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||||
|
|
||||||
@ -180,7 +183,7 @@ class FileDriver implements ModuleRepositoryInterface
|
|||||||
public function enabled(string $moduleName): bool
|
public function enabled(string $moduleName): bool
|
||||||
{
|
{
|
||||||
// TODO: Implement enabled() method.
|
// 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'])) {
|
if ($modules->pluck('name')->contains($module['name'])) {
|
||||||
throw new FailedException(sprintf('Module [%s] has been created', $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
|
public function create(array $module): bool
|
||||||
{
|
{
|
||||||
|
$module['name'] = lcfirst($module['path']);
|
||||||
|
|
||||||
Event::dispatch(new Creating($module));
|
Event::dispatch(new Creating($module));
|
||||||
|
|
||||||
$this->moduleRepository->create($module);
|
$this->moduleRepository->create($module);
|
||||||
@ -93,6 +95,8 @@ class ModuleRepository
|
|||||||
*/
|
*/
|
||||||
public function update(string $name, array $module): bool
|
public function update(string $name, array $module): bool
|
||||||
{
|
{
|
||||||
|
$module['name'] = lcfirst($module['path']);
|
||||||
|
|
||||||
Event::dispatch(new Updating($name, $module));
|
Event::dispatch(new Updating($name, $module));
|
||||||
|
|
||||||
$this->moduleRepository->update($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 :model="formData" label-width="120px" ref="form" v-loading="loading" class="pr-4">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:label="$t('module.form.name.title')"
|
:label="$t('module.form.name.title')"
|
||||||
prop="name"
|
prop="title"
|
||||||
:rules="[
|
:rules="[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<el-input v-model="formData.name" />
|
<el-input v-model="formData.title" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:label="$t('module.form.path.title')"
|
:label="$t('module.form.path.title')"
|
||||||
@ -48,18 +48,18 @@
|
|||||||
import { useCreate } from '/admin/composables/curd/useCreate'
|
import { useCreate } from '/admin/composables/curd/useCreate'
|
||||||
import { useShow } from '/admin/composables/curd/useShow'
|
import { useShow } from '/admin/composables/curd/useShow'
|
||||||
|
|
||||||
import { computed, onMounted, watch } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
primary: String | Number,
|
primary: String | Number,
|
||||||
api: String,
|
api: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { formData, form, loading, submitForm, isClose } = useCreate(
|
const { formData, form, loading, submitForm, close } = useCreate(
|
||||||
props.api,
|
props.api,
|
||||||
props.primary,
|
props.primary,
|
||||||
Object.assign({
|
Object.assign({
|
||||||
name: '',
|
title: '',
|
||||||
path: '',
|
path: '',
|
||||||
keywords: '',
|
keywords: '',
|
||||||
description: '',
|
description: '',
|
||||||
@ -73,13 +73,12 @@ const { formData, form, loading, submitForm, isClose } = useCreate(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
watch(isClose, function (value) {
|
|
||||||
if (value) {
|
|
||||||
emit('close')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (props.primary) {
|
if (props.primary) {
|
||||||
useShow(props.api, props.primary, formData)
|
useShow(props.api, props.primary, formData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
close(() => emit('close'))
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
<Search :search="search" :reset="reset">
|
<Search :search="search" :reset="reset">
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<el-form-item label="模块名称">
|
<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>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</Search>
|
</Search>
|
||||||
<div class="pl-2 pr-2 bg-white dark:bg-regal-dark rounded-lg mt-4 pb-6">
|
<div class="pl-2 pr-2 bg-white dark:bg-regal-dark rounded-lg mt-4 pb-6">
|
||||||
<Operate :show="open" />
|
<Operate :show="open" />
|
||||||
<el-table :data="tableData" class="mt-3" v-loading="loading">
|
<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="path" label="模块目录" width="180" />
|
||||||
<el-table-column prop="version" label="模块版本">
|
<el-table-column prop="version" label="模块版本">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@ -19,7 +19,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="enable" label="模块状态">
|
<el-table-column prop="enable" label="模块状态">
|
||||||
<template #default="scope">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="300">
|
<el-table-column label="操作" width="300">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
<Dialog v-model="visible" :title="title" destroy-on-close>
|
<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>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -54,6 +54,6 @@ const tableData = computed(() => data.value?.data)
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search()
|
search()
|
||||||
|
|
||||||
deleted()
|
deleted(reset)
|
||||||
})
|
})
|
||||||
</script>
|
</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);
|
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();
|
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\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class () extends Migration {
|
return new class extends Migration
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
@ -12,6 +13,10 @@ return new class () extends Migration {
|
|||||||
*/
|
*/
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
|
if (Schema::hasTable('roles')) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('roles', function (Blueprint $table) {
|
Schema::create('roles', function (Blueprint $table) {
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->string('role_name', 30)->comment('角色名称');
|
$table->string('role_name', 30)->comment('角色名称');
|
||||||
|
@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class () extends Migration {
|
return new class extends Migration
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
@ -12,6 +13,10 @@ return new class () extends Migration {
|
|||||||
*/
|
*/
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
|
if (Schema::hasTable('jobs')) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('jobs', function (Blueprint $table) {
|
Schema::create('jobs', function (Blueprint $table) {
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->string('job_name', 50)->comment('岗位名称');
|
$table->string('job_name', 50)->comment('岗位名称');
|
||||||
|
@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class () extends Migration {
|
return new class extends Migration
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
@ -12,6 +13,10 @@ return new class () extends Migration {
|
|||||||
*/
|
*/
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
|
if (Schema::hasTable('departments')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('departments', function (Blueprint $table) {
|
Schema::create('departments', function (Blueprint $table) {
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->integer('parent_id')->default(0)->comment('父级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()
|
public function up()
|
||||||
{
|
{
|
||||||
|
if (Schema::hasTable('permissions')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('permissions', function (Blueprint $table) {
|
Schema::create('permissions', function (Blueprint $table) {
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->integer('parent_id')->default(0)->comment('父级菜单');
|
$table->integer('parent_id')->default(0)->comment('父级菜单');
|
||||||
@ -19,7 +23,7 @@ return new class () extends Migration {
|
|||||||
$table->string('route', 50)->comment('前端路由');
|
$table->string('route', 50)->comment('前端路由');
|
||||||
$table->string('icon', 50)->comment('菜单 icon');
|
$table->string('icon', 50)->comment('菜单 icon');
|
||||||
$table->string('module', 50)->comment('所属模块');
|
$table->string('module', 50)->comment('所属模块');
|
||||||
$table->string('permission_mark', 100)->comment('权限标识,使用 @ 分割');
|
$table->string('permission_mark', 100)->default('')->comment('权限标识,使用 @ 分割');
|
||||||
$table->string('component')->comment('组件');
|
$table->string('component')->comment('组件');
|
||||||
$table->string('redirect')->nullable()->comment('跳转地址');
|
$table->string('redirect')->nullable()->comment('跳转地址');
|
||||||
$table->tinyInteger('keepalive')->default('1')->comment('1 缓存 2 不缓存');
|
$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::put('departments/enable/{id}', [DepartmentsController::class, 'enable']);
|
||||||
|
|
||||||
Route::apiResource('permissions', PermissionsController::class);
|
Route::apiResource('permissions', PermissionsController::class);
|
||||||
|
Route::put('permissions/enable/{id}', [PermissionsController::class, 'enable']);
|
||||||
//next
|
//next
|
||||||
});
|
});
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
import Create from './create.vue'
|
import Create from './form/create.vue'
|
||||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
import Create from './create.vue'
|
import Create from './form/create.vue'
|
||||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||||
|
@ -17,19 +17,53 @@
|
|||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="菜单名称" prop="permission_name">
|
<el-form-item label="菜单名称" prop="permission_name" :rules="[{ required: true, message: '菜单名称必须填写' }]">
|
||||||
<el-input v-model="formData.permission_name" name="permission_name" clearable />
|
<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>
|
||||||
<el-form-item label="所属模块" prop="module">
|
<el-form-item label="所属模块" prop="module" :rules="[{ required: true, message: '所属模块必须填写' }]" v-if="!isAction">
|
||||||
<Select v-model="formData.module" clearable api="modules" class="w-full" filterable />
|
<Select v-model="formData.module" api="modules" @clear="clearModule" />
|
||||||
</el-form-item>
|
</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-input v-model="formData.route" name="route" clearable />
|
||||||
</el-form-item>
|
</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-input v-model="formData.redirect" name="redirect" clearable />
|
||||||
</el-form-item>
|
</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-group v-model="formData.keepalive">
|
||||||
<el-radio
|
<el-radio
|
||||||
v-for="item in [
|
v-for="item in [
|
||||||
@ -44,48 +78,23 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</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>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<el-button type="primary" @click="submitForm(form)">{{ $t('system.confirm') }}</el-button>
|
<el-button type="primary" @click="submitForm(form)">{{ $t('system.confirm') }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<Dialog v-model="visible" title="选择 Icon" width="1000px" destroy-on-close>
|
||||||
|
<Icons v-model="formData.icon" @close="closeSelectIcon" />
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useCreate } from '/admin/composables/curd/useCreate'
|
import { useCreate } from '/admin/composables/curd/useCreate'
|
||||||
import { useShow } from '/admin/composables/curd/useShow'
|
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'
|
import http from '/admin/support/http'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -94,12 +103,23 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { formData, form, loading, submitForm, close, beforeCreate, beforeUpdate } = useCreate(props.api, props.primary)
|
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.sort = 1
|
||||||
formData.value.keepalive = 1
|
formData.value.keepalive = 1
|
||||||
formData.value.type = 1
|
formData.value.type = 1
|
||||||
formData.value.hidden = 1
|
formData.value.hidden = 1
|
||||||
|
// 默认目录
|
||||||
|
const isTop = ref<boolean>(true)
|
||||||
|
const isMenu = ref<boolean>(false)
|
||||||
|
const isAction = ref<boolean>(false)
|
||||||
|
|
||||||
|
// 回显示表单
|
||||||
if (props.primary) {
|
if (props.primary) {
|
||||||
useShow(props.api, props.primary, formData)
|
useShow(props.api, props.primary, formData)
|
||||||
}
|
}
|
||||||
@ -110,14 +130,68 @@ onMounted(() => {
|
|||||||
http.get(props.api).then(r => {
|
http.get(props.api).then(r => {
|
||||||
permissions.value = r.data.data
|
permissions.value = r.data.data
|
||||||
})
|
})
|
||||||
|
// close dialog
|
||||||
close(() => emit('close'))
|
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 = () => {
|
beforeCreate.value = () => {
|
||||||
formData.value.parent_id = getParent(formData.value.parent_id)
|
formData.value.parent_id = getParent(formData.value.parent_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新前的钩子
|
||||||
beforeUpdate.value = () => {
|
beforeUpdate.value = () => {
|
||||||
formData.value.parent_id = getParent(formData.value.parent_id)
|
formData.value.parent_id = getParent(formData.value.parent_id)
|
||||||
}
|
}
|
@ -13,7 +13,11 @@
|
|||||||
<el-table-column prop="permission_name" label="菜单名称" />
|
<el-table-column prop="permission_name" label="菜单名称" />
|
||||||
<el-table-column prop="route" label="菜单路由" />
|
<el-table-column prop="route" label="菜单路由" />
|
||||||
<el-table-column prop="permission_mark" 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 prop="created_at" label="创建时间" />
|
||||||
<el-table-column label="操作" width="200">
|
<el-table-column label="操作" width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@ -31,8 +35,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, watch } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
import Create from './create.vue'
|
import Create from './form/create.vue'
|
||||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
import { useOpen } from '/admin/composables/curd/useOpen'
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
import Create from './create.vue'
|
import Create from './form/create.vue'
|
||||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||||
import { useOpen } from '/admin/composables/curd/useOpen'
|
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)
|
public function online(Request $request)
|
||||||
{
|
{
|
||||||
/* @var Users $user */
|
/* @var Users $user */
|
||||||
$user = $this->getLoginUser();
|
$user = $this->getLoginUser()->withPermissions();
|
||||||
|
|
||||||
if ($request->isMethod('post')) {
|
if ($request->isMethod('post')) {
|
||||||
return $user->updateBy($user->id, $request->all());
|
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 Catch\Base\CatchModel as Model;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Modules\User\Models\Traits\UserRelations;
|
||||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||||
use Illuminate\Auth\Authenticatable;
|
use Illuminate\Auth\Authenticatable;
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ use Illuminate\Auth\Authenticatable;
|
|||||||
*/
|
*/
|
||||||
class Users extends Model implements AuthenticatableContract, JWTSubject
|
class Users extends Model implements AuthenticatableContract, JWTSubject
|
||||||
{
|
{
|
||||||
use Authenticatable;
|
use Authenticatable, UserRelations;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'id', 'username', 'email', 'avatar', 'password', 'remember_token', 'creator_id', 'status', 'login_ip', 'login_at', 'created_at', 'updated_at', 'deleted_at'
|
'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>
|
<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-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 v-for="item in group.options" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-option-group>
|
</el-option-group>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import http from '/admin/support/http'
|
import http from '/admin/support/http'
|
||||||
import { ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
options: {
|
options: {
|
||||||
@ -26,6 +26,10 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
query: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
@ -38,11 +42,21 @@ interface GroupOption {
|
|||||||
options: Array<Option>
|
options: Array<Option>
|
||||||
}
|
}
|
||||||
|
|
||||||
const elOptions = props.group ? ref<Array<GroupOption>>() : ref<Array<Option>>()
|
const getOptions = () => {
|
||||||
if (props.api) {
|
http.get('options/' + props.api, props.query).then(r => {
|
||||||
http.get('options/' + props.api).then(r => {
|
|
||||||
elOptions.value = r.data.data
|
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 {
|
} else {
|
||||||
elOptions.value = props.options
|
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>
|
<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>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useEnabled } from '/admin/composables/curd/useEnabled'
|
import { useEnabled } from '/admin/composables/curd/useEnabled'
|
||||||
import { Status } from '/admin/enum/app'
|
import { Status } from '/admin/enum/app'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: Boolean | Number | String,
|
modelValue: Boolean | Number | String,
|
||||||
@ -16,8 +17,19 @@ const emits = defineEmits(['update:modelValue', 'refresh'])
|
|||||||
|
|
||||||
const { enabled, success, loading, afterEnabled } = useEnabled()
|
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(() => {
|
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 = () => {
|
afterEnabled.value = () => {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { t } from '/admin/support/helper'
|
import { t } from '/admin/support/helper'
|
||||||
|
|
||||||
|
export function useOpen() {
|
||||||
const visible = ref<boolean>(false)
|
const visible = ref<boolean>(false)
|
||||||
const id = ref(null)
|
const id = ref(null)
|
||||||
const title = ref<string>('')
|
const title = ref<string>('')
|
||||||
export function useOpen() {
|
|
||||||
const open = (primary: any) => {
|
const open = (primary: any) => {
|
||||||
console.log(primary)
|
|
||||||
title.value = primary ? t('system.edit') : t('system.add')
|
title.value = primary ? t('system.edit') : t('system.add')
|
||||||
id.value = primary
|
id.value = primary
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
@ -2,6 +2,7 @@ import http from '/admin/support/http'
|
|||||||
import { Ref, ref } from 'vue'
|
import { Ref, ref } from 'vue'
|
||||||
import { isFunction } from '../../support/helper'
|
import { isFunction } from '../../support/helper'
|
||||||
|
|
||||||
|
export function useShow(path: string, id: string | number, fillData: null | Ref = null) {
|
||||||
const loading = ref<boolean>(true)
|
const loading = ref<boolean>(true)
|
||||||
|
|
||||||
const data = ref<object>()
|
const data = ref<object>()
|
||||||
@ -9,7 +10,6 @@ const data = ref<object>()
|
|||||||
// 后置钩子
|
// 后置钩子
|
||||||
const afterShow = ref()
|
const afterShow = ref()
|
||||||
|
|
||||||
export function useShow(path: string, id: string | number, fillData: null | Ref = null) {
|
|
||||||
http.get(path + '/' + id).then(r => {
|
http.get(path + '/' + id).then(r => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
data.value = r.data.data
|
data.value = r.data.data
|
||||||
|
@ -37,7 +37,7 @@ export const enum WhiteListPage {
|
|||||||
* menu 类型
|
* menu 类型
|
||||||
*/
|
*/
|
||||||
export const enum MenuType {
|
export const enum MenuType {
|
||||||
PAGE_TYPE = 1,
|
TOP_TYPE = 1,
|
||||||
|
PAGE_TYPE = 2,
|
||||||
Button_Type,
|
Button_Type = 3,
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<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 { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
||||||
import MenuItem from './item.vue'
|
import MenuItem from './item.vue'
|
||||||
import menus from './menus.vue'
|
import menus from './menus.vue'
|
||||||
@ -106,8 +106,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
// 后端的 permissions 返回 undefined,则认为该后端无权限系统
|
// 后端的 permissions 返回 undefined,则认为该后端无权限系统
|
||||||
const permissions = userStore.getPermissions === undefined ? [] : userStore.getPermissions
|
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 () => {
|
||||||
return h(
|
return h(
|
||||||
menus,
|
menus,
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { RouteRecordRaw } from 'vue-router'
|
import { RouteRecordRaw } from 'vue-router'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
export function getModuleRoutes() {
|
||||||
const modules = import.meta.glob('@/module/**/views/router.ts', { eager: true })
|
const modules = import.meta.glob('@/module/**/views/router.ts', { eager: true })
|
||||||
let moduleRoutes: RouteRecordRaw[] = []
|
let moduleRoutes: RouteRecordRaw[] = []
|
||||||
|
|
||||||
Object.keys(modules).forEach(routePath => {
|
Object.keys(modules).forEach(routePath => {
|
||||||
moduleRoutes = moduleRoutes.concat(modules[routePath].default)
|
moduleRoutes = moduleRoutes.concat(modules[routePath].default)
|
||||||
})
|
})
|
||||||
export default moduleRoutes
|
|
||||||
|
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 { Router, RouteRecordRaw } from 'vue-router'
|
||||||
import { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
import { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
||||||
import { Menu } from '/admin/types/Menu'
|
import { Menu } from '/admin/types/Menu'
|
||||||
|
import { toRaw } from 'vue'
|
||||||
|
|
||||||
const guard = (router: Router) => {
|
const guard = (router: Router) => {
|
||||||
// white list
|
// white list
|
||||||
@ -36,7 +37,7 @@ const guard = (router: Router) => {
|
|||||||
// 挂载路由(实际是从后端获取用户的权限)
|
// 挂载路由(实际是从后端获取用户的权限)
|
||||||
const permissionStore = usePermissionsStore()
|
const permissionStore = usePermissionsStore()
|
||||||
// 动态路由挂载
|
// 动态路由挂载
|
||||||
const asyncRoutes = permissionStore.getAsyncMenusFrom(userStore.getPermissions)
|
const asyncRoutes = permissionStore.getAsyncMenusFrom(toRaw(userStore.getPermissions))
|
||||||
asyncRoutes.forEach((route: Menu) => {
|
asyncRoutes.forEach((route: Menu) => {
|
||||||
router.addRoute(route as unknown as RouteRecordRaw)
|
router.addRoute(route as unknown as RouteRecordRaw)
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
// module routers
|
// module routers
|
||||||
import moduleRoutes from './constantRoutes'
|
import { getModuleRoutes, getModuleViewComponents } from './constantRoutes'
|
||||||
|
|
||||||
|
const moduleRoutes = getModuleRoutes()
|
||||||
|
getModuleViewComponents()
|
||||||
export const constantRoutes: RouteRecordRaw[] = [
|
export const constantRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
|
@ -128,7 +128,7 @@ export const useUserStore = defineStore('UserStore', {
|
|||||||
getUserInfo() {
|
getUserInfo() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
http
|
http
|
||||||
.get('/user/info')
|
.get('/user/online')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { id, nickname, email, avatar, permissions, roles, rememberToken, status } = response.data.data
|
const { id, nickname, email, avatar, permissions, roles, rememberToken, status } = response.data.data
|
||||||
// set user info
|
// set user info
|
||||||
|
@ -4,6 +4,8 @@ import { MenuType } from '/admin/enum/app'
|
|||||||
import { Menu } from '/admin/types/Menu'
|
import { Menu } from '/admin/types/Menu'
|
||||||
import { constantRoutes } from '/admin/router'
|
import { constantRoutes } from '/admin/router'
|
||||||
import { RouteRecordRaw } from 'vue-router'
|
import { RouteRecordRaw } from 'vue-router'
|
||||||
|
import { toRaw } from 'vue'
|
||||||
|
import { getModuleViewComponents } from '/admin/router/constantRoutes'
|
||||||
|
|
||||||
interface Permissions {
|
interface Permissions {
|
||||||
menus: Menu[]
|
menus: Menu[]
|
||||||
@ -68,15 +70,15 @@ export const usePermissionsStore = defineStore('PermissionsStore', {
|
|||||||
const menus: Permission[] = []
|
const menus: Permission[] = []
|
||||||
|
|
||||||
permissions.forEach(permission => {
|
permissions.forEach(permission => {
|
||||||
if (permission.type === MenuType.PAGE_TYPE) {
|
if (permission.type === MenuType.PAGE_TYPE || permission.type === MenuType.TOP_TYPE) {
|
||||||
menus.push(permission)
|
menus.push(permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set map
|
// 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
|
return this.asyncMenus
|
||||||
},
|
},
|
||||||
@ -94,7 +96,7 @@ export const usePermissionsStore = defineStore('PermissionsStore', {
|
|||||||
}
|
}
|
||||||
const asyncMenus = this.getAsyncMenusFrom(permissions, force)
|
const asyncMenus = this.getAsyncMenusFrom(permissions, force)
|
||||||
|
|
||||||
this.setMenus(asyncMenus)
|
this.setMenus(toRaw(asyncMenus))
|
||||||
|
|
||||||
return this.menus
|
return this.menus
|
||||||
},
|
},
|
||||||
@ -118,23 +120,31 @@ export const usePermissionsStore = defineStore('PermissionsStore', {
|
|||||||
* @param permissions
|
* @param permissions
|
||||||
* @param parentId
|
* @param parentId
|
||||||
* @param path
|
* @param path
|
||||||
|
* @param viewComponents
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getAsnycMenus(permissions: Permission[], parentId: number = 0, path: string = ''): Menu[] {
|
getAsnycMenus(permissions: Permission[], parentId: number = 0, path: string = '', viewComponents: any): Menu[] {
|
||||||
const menus: Menu[] = []
|
const menus: Menu[] = []
|
||||||
|
console.log(viewComponents)
|
||||||
permissions.forEach(permission => {
|
permissions.forEach(permission => {
|
||||||
if (permission.parent_id === parentId) {
|
if (permission.parent_id === parentId) {
|
||||||
// menu
|
// menu
|
||||||
|
let importComponent
|
||||||
|
if (permission.type === MenuType.TOP_TYPE) {
|
||||||
|
importComponent = () => import(permission.component)
|
||||||
|
} else {
|
||||||
|
importComponent = viewComponents['/modules' + permission.component]
|
||||||
|
}
|
||||||
const menu: Menu = Object.assign({
|
const menu: Menu = Object.assign({
|
||||||
path: this.resoulveRoutePath(permission.route, path),
|
path: this.resoulveRoutePath(permission.route, path),
|
||||||
name: permission.module + '_' + permission.permission_mark,
|
name: permission.module + '_' + permission.permission_mark,
|
||||||
|
component: importComponent,
|
||||||
redirect: permission.redirect,
|
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
|
// 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) {
|
if (children.length > 0) {
|
||||||
menu.children = children
|
menu.children = children
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ export interface Permission {
|
|||||||
|
|
||||||
parent_id: number
|
parent_id: number
|
||||||
|
|
||||||
title: string
|
permission_name: string
|
||||||
|
|
||||||
type: number
|
type: number
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user