14 Commits
v3.1 ... v3.1.2

Author SHA1 Message Date
JaguarJack
c48607c123 feat: 优化 icons 选择器 2023-05-11 14:21:47 +08:00
JaguarJack
035ba22f52 fix: 重制搜索条件 2023-05-10 16:55:38 +08:00
JaguarJack
403501b214 update package.json 2023-05-10 16:53:52 +08:00
JaguarJack
c56a01df56 chore 2023-05-10 16:53:38 +08:00
JaguarJack
ac5e0957b9 fix: 上传文件路径转换 2023-05-10 16:53:22 +08:00
JaguarJack
948082f4ce fix: 权限组件路径符转换 2023-05-10 16:52:57 +08:00
JaguarJack
35622b164c chore: 优化角色交互 2023-05-10 16:52:17 +08:00
JaguarJack
cbb3c156a6 fix: 用户权限认证 2023-05-10 16:51:25 +08:00
JaguarJack
2d61786ec6 fixed: 转换 windows 路径 2023-05-08 15:38:06 +08:00
JaguarJack
95fae0dc28 chore 2023-05-04 23:17:56 +08:00
JaguarJack
f91e434a83 chore 2023-05-04 15:37:33 +08:00
JaguarJack
3f4bbb70d3 update 2023-04-30 22:33:22 +08:00
JaguarJack
3fc1e07de9 update 2023-04-30 22:29:20 +08:00
JaguarJack
23348b3a6b chore: 调整样式 2023-04-28 10:58:52 +08:00
14 changed files with 124 additions and 66 deletions

View File

@@ -40,6 +40,11 @@
- 账户: `catch@admin.com` - 账户: `catch@admin.com`
- 密码: `catchadmin` - 密码: `catchadmin`
## 视频教程(😂记得一键三连哦)
- [catchadmin 安装](https://www.bilibili.com/video/BV1eY411v71J/)
- [catchadmin 开发之模块创建](https://www.bilibili.com/video/BV1jP41127aW/)
- [catchadmin 之快速开发](https://www.bilibili.com/video/BV1Qh4y1J7eB/)
## 赞助 ## 赞助
如果项目对你有帮助,或者在工作上帮你节省了开发时间。在力所能及的情况下,可以支持下`Catchadmin`项目, 非常感谢🙏 如果项目对你有帮助,或者在工作上帮你节省了开发时间。在力所能及的情况下,可以支持下`Catchadmin`项目, 非常感谢🙏

View File

@@ -18,7 +18,7 @@
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.0", "laravel/framework": "^10.0",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"catchadmin/core": "^0.1.10" "catchadmin/core": "^0.1.12"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",

View File

@@ -4,6 +4,7 @@ namespace Modules\Common\Support\Upload\Uses;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class LocalUpload extends Upload class LocalUpload extends Upload
{ {
@@ -25,7 +26,9 @@ class LocalUpload extends Upload
*/ */
protected function addUrl($path): mixed protected function addUrl($path): mixed
{ {
$path['path'] = config('app.url') . '/'. $path['path']; $path['path'] = config('app.url') . '/'.
Str::of($path['path'])->replace('\\', '/')->toString();
return $path; return $path;
} }

View File

@@ -13,7 +13,6 @@ class {controller} extends Controller
){} ){}
/** /**
* @param Request $request
* @return mixed * @return mixed
*/ */
public function index(): mixed public function index(): mixed

View File

@@ -6,6 +6,7 @@ namespace Modules\Permissions\Http\Controllers;
use Catch\Base\CatchController as Controller; use Catch\Base\CatchController as Controller;
use Catch\Exceptions\FailedException; use Catch\Exceptions\FailedException;
use Illuminate\Http\Request;
use Modules\Permissions\Enums\DataRange; use Modules\Permissions\Enums\DataRange;
use Modules\Permissions\Models\Roles; use Modules\Permissions\Models\Roles;
use Modules\Permissions\Http\Requests\RoleRequest; use Modules\Permissions\Http\Requests\RoleRequest;
@@ -39,13 +40,15 @@ class RolesController extends Controller
*/ */
public function store(RoleRequest $request) public function store(RoleRequest $request)
{ {
$dataRange = $request->get('data_range'); $data = $request->all();
if ($dataRange && ! DataRange::Personal_Choose->assert($request->get('data_range'))) { if ($request->get('data_range') && ! DataRange::Personal_Choose->assert($data['data_range'])) {
$request['departments'] = []; $data['departments'] = [];
} else {
$data['data_range'] = 0;
} }
return $this->model->storeBy($request->all()); return $this->model->storeBy($data);
} }
/** /**
@@ -53,11 +56,15 @@ class RolesController extends Controller
* @param $id * @param $id
* @return \Illuminate\Database\Eloquent\Model|null * @return \Illuminate\Database\Eloquent\Model|null
*/ */
public function show($id) public function show($id, Request $request)
{ {
$role = $this->model->firstBy($id); $role = $this->model->firstBy($id);
if ($request->has('from') && $request->get('from') == 'parent_role') {
$role->setAttribute('permissions', $role->permissions()->get()->toTree());
} else {
$role->setAttribute('permissions', $role->permissions()->get()->pluck('id')); $role->setAttribute('permissions', $role->permissions()->get()->pluck('id'));
}
$role->setAttribute('departments', $role->departments()->pluck('id')); $role->setAttribute('departments', $role->departments()->pluck('id'));
@@ -76,6 +83,8 @@ class RolesController extends Controller
if ($request->get('data_range') && ! DataRange::Personal_Choose->assert($data['data_range'])) { if ($request->get('data_range') && ! DataRange::Personal_Choose->assert($data['data_range'])) {
$data['departments'] = []; $data['departments'] = [];
} else {
$data['data_range'] = 0;
} }
return $this->model->updateBy($id, $data); return $this->model->updateBy($id, $data);

View File

@@ -10,6 +10,7 @@ use Catch\Enums\Status;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Modules\Permissions\Enums\MenuStatus; use Modules\Permissions\Enums\MenuStatus;
use Modules\Permissions\Enums\MenuType; use Modules\Permissions\Enums\MenuType;
@@ -199,6 +200,7 @@ class Permissions extends Model
$data['route'] = '/'.trim($data['route'], '/'); $data['route'] = '/'.trim($data['route'], '/');
} }
$data['component'] = Str::of($data['component'])->replace('\\', '/')->toString();
return parent::storeBy($data); return parent::storeBy($data);
}); });
} }
@@ -244,6 +246,7 @@ class Permissions extends Model
$data['permission_mark'] = $parentMenu->permission_mark.'@'.$data['permission_mark']; $data['permission_mark'] = $parentMenu->permission_mark.'@'.$data['permission_mark'];
} }
$data['component'] = Str::of($data['component'])->replace('\\', '/')->toString();
return parent::updateBy($id, $data); return parent::updateBy($id, $data);
} }
} }

View File

@@ -42,7 +42,14 @@
<Select v-model="formData.permission_mark" allow-create placeholder="请选择" api="controllers" :query="{ module: formData.module }" v-else /> <Select v-model="formData.permission_mark" allow-create placeholder="请选择" api="controllers" :query="{ module: formData.module }" v-else />
</el-form-item> </el-form-item>
<el-form-item label="菜单Icon" prop="icon" v-if="!isAction"> <el-form-item label="菜单Icon" prop="icon" v-if="!isAction">
<el-input v-model="formData.icon" name="icon" clearable @click="open" /> <el-popover placement="right" :width="400" trigger="click">
<template #reference>
<el-input v-model="formData.icon" name="icon" clearable />
</template>
<div>
<Icons v-model="formData.icon" @close="closeSelectIcon" />
</div>
</el-popover>
</el-form-item> </el-form-item>
<el-form-item label="所属组件" prop="component" v-if="!isAction"> <el-form-item label="所属组件" prop="component" v-if="!isAction">
<Select v-model="formData.component" placeholder="请选择" allow-create api="components" :query="{ module: formData.module }" /> <Select v-model="formData.component" placeholder="请选择" allow-create api="components" :query="{ module: formData.module }" />
@@ -92,10 +99,6 @@
<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>

View File

@@ -105,7 +105,7 @@ const { formData, form, loading, submitForm, close, beforeCreate, beforeUpdate }
if (props.primary) { if (props.primary) {
const { afterShow } = useShow(props.api, props.primary, formData) const { afterShow } = useShow(props.api, props.primary, formData)
// 更新角色值
afterShow.value = formData => { afterShow.value = formData => {
const data = unref(formData) const data = unref(formData)
data.parent_id = data.parent_id ? [data.parent_id] : 0 data.parent_id = data.parent_id ? [data.parent_id] : 0
@@ -115,6 +115,8 @@ if (props.primary) {
} }
formData.value = data formData.value = data
// 这里需要获取角色的上级的权限以限制可用权限范围
getPermissions(data.parent_id)
} }
} }
@@ -127,9 +129,12 @@ const departments = ref()
const showDepartments = ref<boolean>(false) const showDepartments = ref<boolean>(false)
const permissionLoadingText = ref<string>('加载中...') const permissionLoadingText = ref<string>('加载中...')
// 获取权限
const getPermissions = async (value: number = 0) => { const getPermissions = async (value: number = 0) => {
if (value) { if (value) {
http.get('permissions/roles/' + getParent(value)).then(r => { // 获取角色权限
http.get('permissions/roles/' + getParent(value), { from: 'parent_role' }).then(r => {
permissions.value = r.data.data.permissions permissions.value = r.data.data.permissions
setCheckedPermissions() setCheckedPermissions()
}) })
@@ -141,6 +146,7 @@ const getPermissions = async (value: number = 0) => {
} }
} }
// 设置已选权限
const setCheckedPermissions = () => { const setCheckedPermissions = () => {
nextTick(() => { nextTick(() => {
props.hasPermissions.forEach(p => { props.hasPermissions.forEach(p => {
@@ -152,6 +158,8 @@ const setCheckedPermissions = () => {
permissionLoadingText.value = '暂无数据' permissionLoadingText.value = '暂无数据'
} }
} }
// 获取角色信息
const getRoles = () => { const getRoles = () => {
http.get(props.api, { id: props.primary ? props.primary : '' }).then(r => { http.get(props.api, { id: props.primary ? props.primary : '' }).then(r => {
roles.value = r.data.data roles.value = r.data.data
@@ -163,9 +171,15 @@ const getDepartments = () => {
departments.value = r.data.data departments.value = r.data.data
}) })
} }
// 新增默认获取全部权限
if (!props.primary) {
getPermissions()
}
// 页面挂载完成后
onMounted(() => { onMounted(() => {
getRoles() getRoles()
getPermissions()
getDepartments() getDepartments()
close(() => emit('close')) close(() => emit('close'))
watch( watch(

View File

@@ -63,18 +63,14 @@ trait UserRelations
$permissions = $permissionsModel->get(); $permissions = $permissionsModel->get();
} else { } else {
$permissions = Collection::make(); $permissions = Collection::make();
$this->roles()->with('permissions')->get()
app($this->getRolesModel())->with(['permissions'])->get()
->each(function ($role) use (&$permissions) { ->each(function ($role) use (&$permissions) {
$permissions = $permissions->concat($role->permissions); $permissions = $permissions->concat($role->permissions);
}); });
$permissions = $permissions->unique(); $permissions = $permissions->unique();
} }
$this->setAttribute('permissions', $permissions->each(fn ($permission) => $permission->setAttribute('hidden', $permission->isHidden()))); $this->setAttribute('permissions', $permissions->each(fn ($permission) => $permission->setAttribute('hidden', $permission->isHidden())));
return $this; return $this;
} }
@@ -103,14 +99,15 @@ trait UserRelations
if ($permission->isAction()) { if ($permission->isAction()) {
[$controller, $action] = explode('@', $permission->permission_mark); [$controller, $action] = explode('@', $permission->permission_mark);
$actions->add(CatchAdmin::getModuleControllerNamespace($permission->module).$controller.'Controller@'.$action); $actions->add(CatchAdmin::getModuleControllerNamespace($permission->module). ucfirst($controller).'Controller@'.$action);
} }
}); });
// 自定义权限判断
if ($permission) { if ($permission) {
[$module, $controller, $action] = explode('@', $permission); [$module, $controller, $action] = explode('@', $permission);
$permission = CatchAdmin::getModuleControllerNamespace($module).$controller.'Controller@'.$action; $permission = CatchAdmin::getModuleControllerNamespace($module). ucfirst($controller) .'Controller@'.$action;
} }
return $actions->contains($permission ?: Route::currentRouteAction()); return $actions->contains($permission ?: Route::currentRouteAction());

View File

@@ -73,7 +73,8 @@ class User extends Model implements AuthenticatableContract
protected function DepartmentId(): Attribute protected function DepartmentId(): Attribute
{ {
return new Attribute( return new Attribute(
get: fn($value) => $value ? : null get: fn($value) => $value ? : null,
set: fn($value) => $value ? : 0
); );
} }

View File

@@ -8,46 +8,46 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@heroicons/vue": "^2.0.14", "@heroicons/vue": "^2.0.18",
"@tinymce/tinymce-vue": "^5.0.1", "@tinymce/tinymce-vue": "^5.1.0",
"@vueuse/core": "^9.12.0", "@vueuse/core": "^10.1.2",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"element-plus": "^2.2.33", "element-plus": "^2.3.4",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.32", "pinia": "^2.0.36",
"postcss": "^8.4.21", "postcss": "^8.4.23",
"tailwindcss": "^3.2.2", "tailwindcss": "^3.3.2",
"terser": "^5.16.5", "terser": "^5.17.3",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-i18n": "9", "vue-i18n": "9",
"vue-router": "4.1.6", "vue-router": "4.1.6",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/logos": "^1.1.22", "@iconify-json/logos": "^1.1.28",
"@rollup/plugin-alias": "^4.0.3", "@rollup/plugin-alias": "^5.0.0",
"@types/mockjs": "^1.0.7", "@types/mockjs": "^1.0.7",
"@types/node": "^18.14.6", "@types/node": "^20.1.1",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/eslint-plugin": "^5.59.5",
"@typescript-eslint/parser": "^5.54.0", "@typescript-eslint/parser": "^5.59.5",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.2.1",
"@vitejs/plugin-vue-jsx": "^3.0.0", "@vitejs/plugin-vue-jsx": "^3.0.0",
"axios": "^1.3.4", "axios": "^1.4.0",
"eslint": "^8.35.0", "eslint": "^8.40.0",
"eslint-config-standard": "^17.0.0", "eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.0", "eslint-plugin-n": "^15.6.0",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.9.0", "eslint-plugin-vue": "^9.11.1",
"prettier": "2.8.4", "prettier": "2.8.4",
"sass": "^1.58.0", "sass": "^1.62.1",
"typescript": "^4.9.5", "typescript": "^5.0.4",
"unplugin-auto-import": "^0.14.4", "unplugin-auto-import": "^0.14.4",
"unplugin-icons": "^0.15.2", "unplugin-icons": "^0.16.1",
"unplugin-vue-components": "^0.24.0", "unplugin-vue-components": "^0.24.0",
"vite": "^4.1.4", "vite": "^4.3.5",
"vite-plugin-html": "^3.2.0", "vite-plugin-html": "^3.2.0",
"vue-tsc": "^1.2.0" "vue-tsc": "^1.6.4"
} }
} }

View File

@@ -1,20 +1,28 @@
<template> <template>
<div :class="`grid ${grid} gap-y-6`"> <div class="h-84 pl-2 pr-2">
<div :class="`grid ${grid} gap-y-4 gap-x-4` + ' mt-3 h-72'">
<div v-for="icon in icons" :key="icon" class="flex justify-center hover:cursor-pointer" @click="selectIcon(icon)"> <div v-for="icon in icons" :key="icon" class="flex justify-center hover:cursor-pointer" @click="selectIcon(icon)">
<div v-if="modelValue === icon"> <div v-if="modelValue === icon">
<div class="flex justify-center w-full text-violet-700"><Icon :name="icon" /></div> <div class="flex justify-center w-full text-violet-700"><Icon :name="icon" className="w-5 h-5" /></div>
<div class="text-sm text-violet-700">{{ icon }}</div> <div class="text-[1px] text-violet-700">{{ icon }}</div>
</div> </div>
<div v-else> <div v-else>
<div class="flex justify-center w-full"><Icon :name="icon" /></div> <div class="flex justify-center w-full"><Icon :name="icon" className="w-5 h-5" /></div>
<div class="text-sm">{{ icon }}</div> <div class="text-[1px]">{{ icon }}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="flex justify-center mt-6">
<el-pagination layout="prev,next" :page-size="limit" :total="total" prev-text="上一页" next-text="下一页" @next-click="handleNext" @prev-click="handlePrev" />
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
@@ -22,18 +30,38 @@ const props = defineProps({
}, },
grid: { grid: {
type: String, type: String,
default: 'grid-cols-5', default: 'grid-cols-4',
}, },
}) })
const emits = defineEmits(['update:modelValue', 'close']) const emits = defineEmits(['update:modelValue', 'close'])
const limit = ref(16)
const icons = ref([])
const total = ref(0)
function getIcons(page = 1) {
const start = (page - 1) * limit.value
const end = start + limit.value
icons.value = constIcons.slice(start, end)
}
onMounted(() => {
getIcons()
total.value = constIcons.length
})
const handleNext = value => {
getIcons(value)
}
const handlePrev = value => {
getIcons(value)
}
const selectIcon = (icon: string) => { const selectIcon = (icon: string) => {
emits('update:modelValue', icon) emits('update:modelValue', icon)
emits('close') emits('close')
} }
// icons // icons
const icons = [ const constIcons = [
'academic-cap', 'academic-cap',
'adjustments-horizontal', 'adjustments-horizontal',
'adjustments-vertical', 'adjustments-vertical',

View File

@@ -57,11 +57,7 @@ export function useGetList(path: string, isPaginate: boolean = true) {
// reset // reset
function reset() { function reset() {
resetPage() resetPage()
query.value = Object.assign(isPaginate ? { page: page.value, limit: limit.value } : {})
if (isPaginate) {
query.value = Object.assign({ page: page.value, limit: limit.value })
}
getList() getList()
} }

View File

@@ -5,12 +5,12 @@
<!-- Tag view --> <!-- Tag view -->
<!--<div class=""></div>--> <!--<div class=""></div>-->
<!-- Container --> <!-- Container -->
<div class="p-1 sm:p-2 max-w-full h-screen overflow-auto sm:overflow-x-hidden"> <div class="p-1 sm:p-3 max-w-full h-screen overflow-auto sm:overflow-x-hidden">
<div class="min-h-[calc(100vh-8rem)]"> <div class="min-h-[calc(100vh-8rem)]">
<router-view /> <router-view />
</div> </div>
<div class="w-full text-center text-gray-400 h-4 leading-10"> <div class="w-full text-center text-gray-400 h-4 leading-10">
<el-link href="https://catchadmin.com/">CatchAdmin 管理系统 </el-link> @copyright 2018 ~ {{ year }} <el-link href="https://catchadmin.com/" target="_blank">CatchAdmin 管理系统 </el-link> @copyright 2018 ~ {{ year }}
</div> </div>
</div> </div>
</div> </div>