now menu can be drag and drop

This commit is contained in:
Geriano
2022-07-26 09:05:28 +07:00
parent c0f95520fa
commit d25f8f49da
6 changed files with 147 additions and 205 deletions

View File

@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\Menu;
use App\Models\Permission;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
@@ -169,171 +170,80 @@ class MenuController extends Controller
}
/**
* @param \App\Models\Menu $menu
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function up(Menu $menu)
public function save(Request $request)
{
$before = Menu::where('parent_id', $menu->parent_id)
->where('position', $menu->position - 1)
->first();
$request->validate([
'menus' => 'required',
]);
if (!$before) {
return redirect()->back()->with('error', __(
'can\'t find menu before `:name`', [
'name' => $menu->name,
]
));
}
$menus = collect($this->positions($request->menus))->flatMap([$this, 'flatMap']);
DB::beginTransaction();
try {
Menu::where('id', $before->id)->update([
'position' => $menu->position,
]);
$menu->update([
'position' => $before->position,
]);
$menus->each(function (Menu $menu) {
Menu::where('id', $menu->id)->update($menu->only([
'parent_id',
'position',
]));
});
DB::commit();
return redirect()->back()->with('success', __(
'menu positions has been saved',
));
} catch (Throwable $e) {
DB::rollBack();
return redirect()->back()->with('error', __($e->getMessage()));
return redirect()->back()->with('error', __(
$e->getMessage(),
));
}
return redirect()->back()->with('success', __(
'position has been updated',
));
}
/**
* @param \App\Models\Menu $menu
* @return \Illuminate\Http\Response
* @return array
*/
public function down(Menu $menu)
public function flatMap(Menu $menu)
{
$after = Menu::where('parent_id', $menu->parent_id)
->where('position', $menu->position + 1)
->first();
if (!$after) {
return redirect()->back()->with('error', __(
'can\'t find menu after `:name`', [
'name' => $menu->name,
]
));
}
DB::beginTransaction();
try {
Menu::where('id', $after->id)->update([
'position' => $menu->position,
]);
$menu->update([
'position' => $after->position,
]);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
return redirect()->back()->with('error', __($e->getMessage()));
}
return redirect()->back()->with('success', __(
'position has been updated',
));
return [
$menu,
...collect($menu->childs)->flatMap([$this, 'flatMap']),
];
}
/**
* @param \App\Models\Menu $menu
* @return \Illuminate\Http\Response
* @param array $menu
* @return array
*/
public function right(Menu $menu)
public function updateWithChild(array $menu)
{
$before = Menu::where('parent_id', $menu->parent_id)
->where('position', $menu->position - 1)
->withCount('childs')
->first();
if (!$before) {
return redirect()->back()->with('error', __(
'can\'t find menu before `:name`', [
'name' => $menu->name,
]
));
if (array_key_exists('childs', $menu) && $menu['childs']) {
return $this->updateWithChilds($menu['childs']);
}
DB::beginTransaction();
try {
Menu::where('parent_id', $menu->parent_id)
->where('position', '>', $menu->position)
->decrement('position');
$menu->update([
'parent_id' => $before->id,
'position' => $before->childs_count + 1,
]);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
return redirect()->back()->with('error', __($e->getMessage()));
}
return redirect()->back()->with('success', __(
'position has been updated',
));
return new Menu($menu);
}
/**
* @param \App\Models\Menu $menu
* @return \Illuminate\Http\Response
* @param \Illuminate\Support\Collection|\App\Models\Menu|array
* @param int $parent
*/
public function left(Menu $menu)
private function positions(Collection|Menu|array $menus, int $parent = null)
{
$parent = $menu->parent;
return array_map(function ($menu) use (&$i, $parent) {
$new = new Menu($menu);
$new->id = $menu['id'];
$new->position = ++$i;
$new->parent_id = $parent;
$new->childs = array_key_exists('childs', $menu) ? $this->positions($menu['childs'], $new->id) : [];
if (!$parent) {
return redirect()->back()->with('error', __(
'can\'t find parent menu `:name`', [
'name' => $menu->name,
]
));
}
DB::beginTransaction();
try {
Menu::where('parent_id', $parent->parent_id)
->where('position', '>', $parent->position)
->increment('position');
Menu::where('parent_id', $menu->parent_id)
->where('position', '>', $menu->position)
->decrement('position');
$menu->update([
'parent_id' => $parent->parent_id,
'position' => $parent->position + 1,
]);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
return redirect()->back()->with('error', __($e->getMessage()));
}
return redirect()->back()->with('success', __(
'position has been updated',
));
return $new;
}, $menus);
}
}

102
package-lock.json generated
View File

@@ -6,7 +6,8 @@
"": {
"dependencies": {
"@vueform/multiselect": "^2.5.1",
"sweetalert2": "^11.4.23"
"sweetalert2": "^11.4.23",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@inertiajs/inertia": "^0.11.0",
@@ -29,7 +30,6 @@
"version": "7.18.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.8.tgz",
"integrity": "sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -161,7 +161,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
"integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/shared": "3.2.37",
@@ -173,7 +172,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
"integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
"dev": true,
"dependencies": {
"@vue/compiler-core": "3.2.37",
"@vue/shared": "3.2.37"
@@ -183,7 +181,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
"integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.37",
@@ -201,7 +198,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
"integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
"dev": true,
"dependencies": {
"@vue/compiler-dom": "3.2.37",
"@vue/shared": "3.2.37"
@@ -211,7 +207,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz",
"integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
"dev": true,
"dependencies": {
"@vue/shared": "3.2.37"
}
@@ -220,7 +215,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
"integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.37",
@@ -233,7 +227,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
"integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
"dev": true,
"dependencies": {
"@vue/reactivity": "3.2.37",
"@vue/shared": "3.2.37"
@@ -243,7 +236,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
"integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
"dev": true,
"dependencies": {
"@vue/runtime-core": "3.2.37",
"@vue/shared": "3.2.37",
@@ -254,7 +246,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
"integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
"dev": true,
"dependencies": {
"@vue/compiler-ssr": "3.2.37",
"@vue/shared": "3.2.37"
@@ -266,8 +257,7 @@
"node_modules/@vue/shared": {
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz",
"integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==",
"dev": true
"integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
},
"node_modules/@vueform/multiselect": {
"version": "2.5.1",
@@ -514,8 +504,7 @@
"node_modules/csstype": {
"version": "2.6.20",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==",
"dev": true
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
},
"node_modules/deepmerge": {
"version": "4.2.2",
@@ -934,8 +923,7 @@
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/fast-glob": {
"version": "3.2.11",
@@ -1204,7 +1192,6 @@
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dev": true,
"dependencies": {
"sourcemap-codec": "^1.4.8"
}
@@ -1250,7 +1237,6 @@
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -1315,8 +1301,7 @@
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -1343,7 +1328,6 @@
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -1613,11 +1597,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1626,7 +1614,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1634,8 +1621,7 @@
"node_modules/sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
@@ -1783,7 +1769,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz",
"integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==",
"dev": true,
"dependencies": {
"@vue/compiler-dom": "3.2.37",
"@vue/compiler-sfc": "3.2.37",
@@ -1792,6 +1777,17 @@
"@vue/shared": "3.2.37"
}
},
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"dependencies": {
"sortablejs": "1.14.0"
},
"peerDependencies": {
"vue": "^3.0.1"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -1815,8 +1811,7 @@
"@babel/parser": {
"version": "7.18.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.8.tgz",
"integrity": "sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==",
"dev": true
"integrity": "sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA=="
},
"@inertiajs/inertia": {
"version": "0.11.0",
@@ -1916,7 +1911,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
"integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
"dev": true,
"requires": {
"@babel/parser": "^7.16.4",
"@vue/shared": "3.2.37",
@@ -1928,7 +1922,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
"integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
"dev": true,
"requires": {
"@vue/compiler-core": "3.2.37",
"@vue/shared": "3.2.37"
@@ -1938,7 +1931,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
"integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
"dev": true,
"requires": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.37",
@@ -1956,7 +1948,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
"integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
"dev": true,
"requires": {
"@vue/compiler-dom": "3.2.37",
"@vue/shared": "3.2.37"
@@ -1966,7 +1957,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz",
"integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
"dev": true,
"requires": {
"@vue/shared": "3.2.37"
}
@@ -1975,7 +1965,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
"integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
"dev": true,
"requires": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.37",
@@ -1988,7 +1977,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
"integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
"dev": true,
"requires": {
"@vue/reactivity": "3.2.37",
"@vue/shared": "3.2.37"
@@ -1998,7 +1986,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
"integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
"dev": true,
"requires": {
"@vue/runtime-core": "3.2.37",
"@vue/shared": "3.2.37",
@@ -2009,7 +1996,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
"integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
"dev": true,
"requires": {
"@vue/compiler-ssr": "3.2.37",
"@vue/shared": "3.2.37"
@@ -2018,8 +2004,7 @@
"@vue/shared": {
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz",
"integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==",
"dev": true
"integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
},
"@vueform/multiselect": {
"version": "2.5.1",
@@ -2179,8 +2164,7 @@
"csstype": {
"version": "2.6.20",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==",
"dev": true
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
},
"deepmerge": {
"version": "4.2.2",
@@ -2400,8 +2384,7 @@
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"fast-glob": {
"version": "3.2.11",
@@ -2597,7 +2580,6 @@
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.8"
}
@@ -2633,8 +2615,7 @@
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
},
"node-releases": {
"version": "2.0.6",
@@ -2681,8 +2662,7 @@
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"picomatch": {
"version": "2.3.1",
@@ -2700,7 +2680,6 @@
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
@@ -2847,23 +2826,25 @@
"object-inspect": "^1.9.0"
}
},
"sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
@@ -2948,7 +2929,6 @@
"version": "3.2.37",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz",
"integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==",
"dev": true,
"requires": {
"@vue/compiler-dom": "3.2.37",
"@vue/compiler-sfc": "3.2.37",
@@ -2957,6 +2937,14 @@
"@vue/shared": "3.2.37"
}
},
"vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"requires": {
"sortablejs": "1.14.0"
}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@@ -22,6 +22,7 @@
},
"dependencies": {
"@vueform/multiselect": "^2.5.1",
"sweetalert2": "^11.4.23"
"sweetalert2": "^11.4.23",
"vuedraggable": "^4.1.0"
}
}

View File

@@ -8,7 +8,7 @@ import Icon from '@/Components/Icon.vue'
import axios from 'axios'
import Swal from 'sweetalert2'
import Select from '@vueform/multiselect'
import Builder from './Builder.vue'
import Nested from './Nested.vue'
const self = getCurrentInstance()
const { permissions, routes, icons } = defineProps({
@@ -107,10 +107,9 @@ const destroy = async menu => {
const submit = () => form.id ? update() : store()
const up = menu => Inertia.patch(route('superuser.menu.up', menu.id))
const down = menu => Inertia.patch(route('superuser.menu.down', menu.id))
const right = menu => Inertia.patch(route('superuser.menu.right', menu.id))
const left = menu => Inertia.patch(route('superuser.menu.left', menu.id))
const save = () => {
return useForm({ menus: menus.value }).patch(route('superuser.menu.save'))
}
const esc = e => e.key === 'Escape' && close()
@@ -143,8 +142,19 @@ onUnmounted(() => window.removeEventListener('keydown', esc))
</template>
<template #body>
<div class="flex flex-col p-2">
<Builder :menus="menus" :edit="edit" :destroy="destroy" :up="up" :down="down" :left="left" :right="right" />
<div class="flex flex-col space-y-1 p-2 max-h-96 overflow-auto">
<Nested :menus="menus" :edit="edit" :destroy="destroy" />
</div>
</template>
<template #footer>
<div class="flex items-center space-x-1 dark:bg-gray-800 p-2">
<button @click.prevent="save" class="bg-green-600 hover:bg-green-700 rounded-md px-3 py-1 text-sm text-white transition-all">
<div class="flex items-center space-x-1">
<Icon name="save" />
<p class="uppercase font-semibold">save</p>
</div>
</button>
</div>
</template>
</Card>

View File

@@ -0,0 +1,38 @@
<script setup>
import { getCurrentInstance, onMounted, onUpdated, ref } from 'vue'
import Dragable from 'vuedraggable'
import Icon from '@/Components/Icon.vue'
const self = getCurrentInstance()
const { menus, edit, destroy } = defineProps({
menus: Array,
edit: Function,
destroy: Function,
})
</script>
<template>
<Dragable
tag="ul"
:list="menus"
:group="{ name: 'g1' }"
item-key="name">
<template #item="{ element }">
<div class="flex flex-col space-y-1">
<div class="flex items-center space-x-2 dark:bg-gray-800 dark:hover:bg-gray-900 rounded-md px-4 py-2">
<div class="flex items-center space-x-2 w-full">
<Icon :name="element.icon" />
<p class="uppercase">{{ element.name }}</p>
</div>
<div ref="container" class="flex-none flex items-center rounded-md space-x-1">
<Icon @click.prevent="edit(element)" name="edit" class="bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded-md text-sm text-white transition-all cursor-pointer" />
<Icon v-if="element.deleteable" @click.prevent="destroy(element)" name="trash" class="bg-red-600 hover:bg-red-700 px-2 py-1 rounded-md text-sm text-white transition-all cursor-pointer" />
</div>
</div>
<Nested :menus="element.childs" :edit="edit" :destroy="destroy" class="ml-8" />
</div>
</template>
</Dragable>
</template>

View File

@@ -40,15 +40,10 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified']
Route::patch('/permission/{permission}/detach', 'detachPermission')->name('permission.detach');
});
Route::patch('/menu/save', [App\Http\Controllers\Superuser\MenuController::class, 'save'])->name('menu.save');
Route::resource('menu', App\Http\Controllers\Superuser\MenuController::class)->only([
'index', 'store', 'update', 'destroy',
]);
Route::prefix('/menu/{menu}')->name('menu.')->controller(App\Http\Controllers\Superuser\MenuController::class)->group(function () {
Route::patch('/up', 'up')->name('up');
Route::patch('/down', 'down')->name('down');
Route::patch('/left', 'left')->name('left');
Route::patch('/right', 'right')->name('right');
});
});
});