问题描述
我的项目需要一个文件树视图来上传文档,我使用了一个主题森林模板,页面中包含了 jstree。
所以我决定使用它,但有必要将它连接到数据库。它需要一个数据库、一个 api 和它的所有代码。
我花了几个小时试图找出如何使用 Laravel 创建 JSTree 结构,包括拖放、移动、创建、重命名和排序功能。
解决方法
在深入研究 jstree 文档和 Stack Overflow 之后,这里是我对所有内容的工作解决方案的逐步编译。
我使用的是 JSTree 版本 3.3.11 和 Laravel 8。
步骤:
A) 创建数据库。 该表是“目录”。
class CreateDirectoriesTable extends Migration
{
public function up()
{
Schema::create('directories',function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('parent_id')->nullable();
$table->string('name');
$table->text('observations')->nullable();
$table->timestamps();
$table->foreign('parent_id')
->references('id')
->on('directories')
->cascadeOnDelete();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('directories');
}
}
B) 目录.model 使用它来定义哪些字段是可更新的,并定义递归关系。
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Directory extends Model
{
use HasFactory;
protected $fillable = [
'parent_id','name','observations',];
public function children()
{
return $this->hasMany(Directory::class,'parent_id');
}
}
C) 播种机(可选) 我使用 Seeder 来包含一些要测试的项目
<?php
namespace Database\Seeders;
use Faker\Factory;
use App\Models\Directory;
use Illuminate\Database\Seeder;
class DirectorySeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$faker = Factory::create();
$items = array(
[
'name' => $faker->lexify('???????????????'),'parent_id' => null,'observations' => $faker->optional()->paragraph(3),],[
'name' => $faker->lexify('???????????????'),'parent_id' => 1,'parent_id' => 2,'parent_id' => 3,'parent_id' => 5,'parent_id' => 7,'parent_id' => 8,);
foreach($items as $item) {
Directory::factory()->create($item);
}
}
}
D) web.php 上的路由 我已经定义了 4 个函数来处理 DragNDrop、Rename、Delete 和 Create。
Route::name('api.')->prefix('api/')->group(function() {
Route::post('/treeview/dnd','ApiController@treeviewDnd')->name('treeviewdnd');
Route::post('/treeview/rename','ApiController@treeviewRename')->name('treeviewrename');
Route::post('/treeview/delete','ApiController@treeviewDelete')->name('treeviewdelete');
Route::post('/treeview/create','ApiController@treeviewCreate')->name('treeviewcreate');
});
E) API 控制器
class ApiController extends Controller
{
// Move Node on Directory Tree
public function treeviewDnd()
{
$directory = Directory::find(request()->source);
if ($directory) {
if (request()->destination) {
if (request()->destination == '#') {
$directory->parent_id = null;
} else {
$directory->parent_id = request()->destination;
}
}
$directory->update();
}
}
// Rename Node on Directory Tree
public function treeviewRename()
{
$directory = Directory::find(request()->dbid);
if ($directory) {
$name = request()->name;
if ($name) {
$directory->name = $name;
$directory->update();
}
}
}
// Delete Node on Directory Tree
public function treeviewDelete()
{
$directory = Directory::find(request()->id);
if ($directory) {
$directory->delete();
}
}
// Create Node on Directory Tree
public function treeviewCreate()
{
$directory = [
"name" => request()->name,"parent_id" => request()->parentid,];
$result = Directory::create($directory);
return $result;
}
}
F) 在blade.php 中包含树
<div id="stackoverflowtree" class="tree-demo"></div>
G) 我在我的基本刀片上创建了一个“脚本”部分,所以我可以使用部分标签在页面末尾包含脚本。
@section('scripts')
<script>
"use strict";
var tree = {!! $treeJS !!};
var treeId = '#stackoverflowtree';
var nodeSelected = undefined;
var KTTreeview = function () {
var _demostackoverflow = function() {
$(treeId).jstree({
"core" : {
"themes" : {
"responsive": false
},// so that create works
"check_callback" : function (operation,node,node_parent,node_position,more) {
if (operation === 'delete_node') {
if (confirm('@lang("global.confirmation_title")') == true) {
return true;
}
else {
return false;
}
} else {
return true;
}
},'data': tree,},"types" : {
"default" : {
"icon" : "fa fa-folder text-primary"
},"file" : {
"icon" : "fa fa-file text-primary"
}
},"state" : { "key" : "demo2" },"plugins" : [ "dnd","state","types","sort","contextmenu" ],"sort" : function(a,b) {
if (a && b && this) {
var a1 = this.get_node(a);
var b1 = this.get_node(b);
if (a1.icon == b1.icon){
return a1.text.toLowerCase().localeCompare(b1.text.toLowerCase());
} else {
return a1.icon.toLowerCase().localeCompare(b1.icon.toLowerCase());
}
}
},"contextmenu": {
"items": function ($node) {
var tree = $(treeId).jstree(true);
return {
"Rename": {
"label": "@lang('global.directory_rename')","action": function (obj) {
tree.edit($node);
}
},"Create": {
"label": "@lang('global.directory_create')","action": function (obj) {
$node = tree.create_node($node);
tree.edit($node);
}
},"Delete": {
"label" : "@lang('global.directory_delete')","action" : function(obj) {
tree.delete_node($node);
}
}
};
}
}
})
.bind("move_node.jstree",function(e,data) {
var treeInst = $(treeId).jstree(true);
var parentNodeResult = null;
if (data.parent != '#') {
var aux = treeInst.get_node(data.parent);
parentNodeResult = aux.original.dbid;
} else {
parentNodeResult = '#';
}
$.ajax({
url: "{{ route('api.treeviewdnd') }}",type:'POST',data: {
"_token" : "{{ csrf_token() }}","source": data.node.original.dbid,"destination": parentNodeResult,success: function(data) {
console.log(data);
}
});
})
.bind("select_node.jstree",function(evt,data){
console.log("select");
nodeSelected = data.node;
$("#tree-subtitle").html(data.node.text)
})
.bind("rename_node.jstree",function (e,data) {
if (data.node.text && data.text != data.old) {
$.ajax({
url: "{{ route('api.treeviewrename') }}",data: {
"_token" : "{{ csrf_token() }}","dbid": data.node.original.dbid,"name": data.text,success: function(data) {
toastr.success('@lang("global.success_message")','@lang("global.success_title")');
},error: function(data) {
toastr.error('@lang("global.error_required")','@lang("global.error_title")');
}
});
}
})
.bind("create_node.jstree",data) {
var treeInst = $(treeId).jstree(true)
var parentNode = treeInst.get_node(data.parent)
$.ajax({
url: "{{ route('api.treeviewcreate') }}","entityid": {{ $entity->id }},"parentid": parentNode.original.dbid,"name": data.node.text,success: function(response) {
data.node.original = { "dbid" : response.id };
},error: function(response) {
toastr.error('@lang("global.error_message")','@lang("global.error_title")');
}
});
})
.bind("delete_node.jstree",data) {
$.ajax({
url: "{{ route('api.treeviewdelete') }}","id": data.node.original.dbid,success: function(data) {
toastr.success('@lang("global.success_message")','@lang("global.success_title")');
},error: function(data) {
toastr.error('@lang("global.error_message")','@lang("global.error_title")');
}
});
});
}
return {
//main function to initiate the module
init: function () {
_demostackoverflow();
}
};
}();
jQuery(document).ready(function() {
KTTreeview.init();
});
</script>
@endsection
H) 我差点忘了。服务器端树结构的创建,页面Controller:
<?php
namespace App\Http\Controllers;
use App\Models\Directory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class EntityController extends Controller
{
private function getTreeJS($entity,$path_id,&$treejs)
{
$directories = Directory::where('entity_id',$entity->id)->where('parent_id',$path_id)->get();
$treejs .= '[';
foreach($directories as $directory) {
$treejs .= '{';
$treejs .= ' "dbid" : "' . $directory->id . '",';
$treejs .= ' "text" : "' . $directory->name . '",';
$treejs .= '"children" : ';
$treejs .= $this->getTreeJS($entity,$directory->id,$treejs);
$treejs .= '},';
}
$treejs .= ']';
}
public function details(Entity $entity,Property $property = null)
{
// Create Tree JS
$treejs = '';
$this->getTreeJS($entity,null,$treejs);
return view('admin.entities.details',[
'treeJS' => $treejs,]);
}
}
I) 我用来向用户显示一些输出的消息位于 Laravel 中的语言文件 /resources/lang/en/ 中:
<?php
return [
// Success
'success_title' => 'Success!','success_message' => 'Operation successfully.',// Errors
'error_title' => 'Ups! There was an error.','error_required' => 'You must fill the information.','error_message' => 'It was not possible to finish the operation.','confirmation_title' => 'Do you confirm?','confirmation_success' => 'Operation successfully.','directory_rename' => 'Rename','directory_create' => 'Create folder','directory_delete' => 'Delete folder'
];
结论: 对于与数据库表中的 ID 相对应的每个树文件夹,我都使用了一个名为 dbid 的额外变量。
使用该数据库 ID,我可以通过使用 jstree 'get_node' 查找确切节点来在每个操作中使用它。
我刚刚开始学习 Laravel,这不是完美的解决方案,但这是我处理需求的方法。随意使用它并按照自己的方式进行更改。
这是我的图片: