浏览代码

Merge branch 'laravel' of https://github.com/iapt-platform/mint into laravel

bhikkhu-kosalla-china 2 年之前
父节点
当前提交
718d65fbae
共有 100 个文件被更改,包括 7387 次插入1172 次删除
  1. 1 0
      .env.example
  2. 11 0
      about.md
  3. 14 6
      app/Console/Commands/InitCs6sentence.php
  4. 30 0
      app/Console/Commands/InitSystemChannel.php
  5. 77 0
      app/Console/Commands/StatisticsDict.php
  6. 74 0
      app/Console/Commands/StatisticsExp.php
  7. 31 36
      app/Console/Commands/StatisticsNissaya.php
  8. 74 0
      app/Console/Commands/StatisticsWbw.php
  9. 50 0
      app/Console/Commands/TestJsonToXml.php
  10. 30 5
      app/Console/Commands/TestMdRender.php
  11. 57 0
      app/Console/Commands/UpgradeAt20230227.php
  12. 150 0
      app/Console/Commands/UpgradeChapterDynamicWeekly.php
  13. 165 0
      app/Console/Commands/UpgradeCommunityTerm.php
  14. 61 39
      app/Console/Commands/UpgradeCompound.php
  15. 5 6
      app/Console/Commands/UpgradeDaily.php
  16. 81 17
      app/Console/Commands/UpgradeDictDefaultMeaning.php
  17. 2 0
      app/Console/Commands/UpgradeDictSysWbwExtract.php
  18. 9 4
      app/Console/Commands/UpgradeDictVocabulary.php
  19. 102 0
      app/Console/Commands/UpgradeFts.php
  20. 124 116
      app/Console/Commands/UpgradePaliText.php
  21. 64 0
      app/Console/Commands/UpgradePcdBookId.php
  22. 106 0
      app/Console/Commands/UpgradeRelatedParagraph.php
  23. 49 0
      app/Console/Commands/UpgradeTestData.php
  24. 16 2
      app/Console/Commands/UpgradeWbwAnalyses.php
  25. 11 4
      app/Console/Commands/UpgradeWbwTemplate.php
  26. 47 0
      app/Console/Commands/UpgradeWeekly.php
  27. 72 0
      app/Console/Commands/UuidViranyani.php
  28. 1 1
      app/Console/Kernel.php
  29. 3 1
      app/Http/Api/AuthApi.php
  30. 12 1
      app/Http/Api/ChannelApi.php
  31. 65 0
      app/Http/Api/DictApi.php
  32. 18 0
      app/Http/Api/GroupApi.php
  33. 84 12
      app/Http/Api/MdRender.php
  34. 44 7
      app/Http/Api/ShareApi.php
  35. 3 0
      app/Http/Api/StudioApi.php
  36. 14 0
      app/Http/Api/SuggestionApi.php
  37. 45 6
      app/Http/Api/TemplateRender.php
  38. 18 0
      app/Http/Api/UserApi.php
  39. 263 84
      app/Http/Controllers/ArticleController.php
  40. 51 3
      app/Http/Controllers/ArticleMapController.php
  41. 256 56
      app/Http/Controllers/ChannelController.php
  42. 61 55
      app/Http/Controllers/CollectionController.php
  43. 1 1
      app/Http/Controllers/Controller.php
  44. 358 173
      app/Http/Controllers/CorpusController.php
  45. 7 14
      app/Http/Controllers/CourseController.php
  46. 29 13
      app/Http/Controllers/CourseMemberController.php
  47. 352 74
      app/Http/Controllers/DhammaTermController.php
  48. 71 13
      app/Http/Controllers/DictController.php
  49. 127 0
      app/Http/Controllers/DictMeaningController.php
  50. 54 10
      app/Http/Controllers/DiscussionController.php
  51. 83 0
      app/Http/Controllers/GrammarGuideController.php
  52. 73 44
      app/Http/Controllers/GroupController.php
  53. 9 9
      app/Http/Controllers/GroupMemberController.php
  54. 340 0
      app/Http/Controllers/NissayaEndingController.php
  55. 11 1
      app/Http/Controllers/PaliTextController.php
  56. 66 20
      app/Http/Controllers/ProgressChapterController.php
  57. 113 0
      app/Http/Controllers/RelatedParagraphController.php
  58. 237 0
      app/Http/Controllers/RelationController.php
  59. 341 0
      app/Http/Controllers/SearchController.php
  60. 49 11
      app/Http/Controllers/SentPrController.php
  61. 116 0
      app/Http/Controllers/SentSimController.php
  62. 147 56
      app/Http/Controllers/SentenceController.php
  63. 97 15
      app/Http/Controllers/ShareController.php
  64. 88 0
      app/Http/Controllers/StudioController.php
  65. 94 1
      app/Http/Controllers/TagController.php
  66. 95 0
      app/Http/Controllers/TermVocabularyController.php
  67. 97 83
      app/Http/Controllers/UserDictController.php
  68. 113 0
      app/Http/Controllers/UserOperationDailyController.php
  69. 150 0
      app/Http/Controllers/UserStatisticController.php
  70. 57 30
      app/Http/Controllers/ViewController.php
  71. 11 6
      app/Http/Controllers/VocabularyController.php
  72. 189 0
      app/Http/Controllers/WbwController.php
  73. 351 73
      app/Http/Controllers/WbwLookupController.php
  74. 94 0
      app/Http/Controllers/WordIndexController.php
  75. 1 0
      app/Http/Kernel.php
  76. 0 40
      app/Http/Middleware/EnableCrossRequestMiddleware.php
  77. 256 0
      app/Http/Middleware/UserOperation.php
  78. 23 6
      app/Http/Resources/ArticleResource.php
  79. 34 0
      app/Http/Resources/ChannelResource.php
  80. 39 0
      app/Http/Resources/CollectionResource.php
  81. 2 1
      app/Http/Resources/CourseMemberResource.php
  82. 1 0
      app/Http/Resources/DiscussionResource.php
  83. 55 0
      app/Http/Resources/GroupResource.php
  84. 30 0
      app/Http/Resources/NissayaEndingResource.php
  85. 38 0
      app/Http/Resources/RelatedParagraphResource.php
  86. 28 0
      app/Http/Resources/RelationResource.php
  87. 29 0
      app/Http/Resources/SearchBookResource.php
  88. 49 0
      app/Http/Resources/SearchResource.php
  89. 4 2
      app/Http/Resources/SentPrResource.php
  90. 38 7
      app/Http/Resources/SentResource.php
  91. 52 0
      app/Http/Resources/SentSimResource.php
  92. 17 2
      app/Http/Resources/ShareResource.php
  93. 41 0
      app/Http/Resources/TermResource.php
  94. 19 0
      app/Http/Resources/TermVocabularyResource.php
  95. 58 0
      app/Http/Resources/TocResource.php
  96. 42 0
      app/Http/Resources/UserDictResource.php
  97. 19 0
      app/Http/Resources/ViewResource.php
  98. 3 6
      app/Http/Resources/VocabularyResource.php
  99. 19 0
      app/Http/Resources/WbwResource.php
  100. 19 0
      app/Http/Resources/WordIndexResource.php

+ 1 - 0
.env.example

@@ -7,6 +7,7 @@ APP_ENV=local
 APP_KEY=
 APP_DEBUG=true
 APP_URL=http://localhost
+API_URL=http://localhost/api
 
 LOG_CHANNEL=stack
 LOG_DEPRECATIONS_CHANNEL=null

+ 11 - 0
about.md

@@ -0,0 +1,11 @@
+# wikipali
+
+巴利圣典教学与研究社区
+
+- 巴利圣典阅读与翻译
+- 在线课程发布与学习
+- 在线巴利语学习工具
+
+wikipali是一个在线巴利圣典学习,翻译,研究平台。也是一个巴利知识分享社区。
+不断完善的学习工具让巴利圣典的学习变得更容易。并为专业的翻译和研究者提供便利的创作和成功发布功能。
+我们希望,人人都能从巴利圣典中受益。

+ 14 - 6
app/Console/Commands/InitCs6sentence.php

@@ -58,6 +58,7 @@ class InitCs6sentence extends Command
 		}
 		$bar = $this->output->createProgressBar($pali->count());
 		$pali = $pali->select('book','paragraph','word_begin','word_end')->cursor();
+
 		foreach ($pali as $value) {
 			# code...
 			$words = WbwTemplate::where("book",$value->book)
@@ -69,11 +70,22 @@ class InitCs6sentence extends Command
 			$sent = '';
 			$boldStart = false;
 			$boldCount = 0;
+            $lastWord = null;
 			foreach ($words as $word) {
 				# code...
 				//if($word->style != "note" && $word->type != '.ctl.')
 				if( $word->type != '.ctl.')
                 {
+                    if($lastWord !== null){
+                        if($word->real !== "ti" ){
+
+                            if(!(empty($word->real) && empty($lastWord->real))) {
+                                    #如果不是标点符号,在词的前面加空格 。
+                                $sent .= " ";
+                            }
+                        }
+                    }
+
 					if(strpos($word->word,'{') >=0 ){
                         //一个单词里面含有黑体字的
 						$paliWord = \str_replace("{","<strong>",$word->word) ;
@@ -87,12 +99,8 @@ class InitCs6sentence extends Command
                         }
 					}
 
-					if(!empty($word->real) ){
-						#如果不是标点符号,在词的前面加空格 。
-						$sent .= " ";
-					}
-
 				}
+                $lastWord = $word;
 			}
 
 			#将wikipali风格的引用 改为缅文风格
@@ -114,6 +122,7 @@ class InitCs6sentence extends Command
 				[
 					'id' =>app('snowflake')->id(),
 					'uid' =>Str::uuid(),
+                    'create_time' => time()*1000,
 				]
 				);
             $newRow->editor_uid = config("app.admin.root_uuid");
@@ -121,7 +130,6 @@ class InitCs6sentence extends Command
             $newRow->strlen = mb_strlen($sent,"UTF-8");
             $newRow->status = 10;
             $newRow->content_type = "html";
-            $newRow->create_time = time()*1000;
             $newRow->modify_time = time()*1000;
             $newRow->language = 'en';
             $newRow->save();

+ 30 - 0
app/Console/Commands/InitSystemChannel.php

@@ -32,6 +32,36 @@ class InitSystemChannel extends Command
             'type'=>'original',
             'lang'=>'pali',
         ],
+        [
+            "name"=>'_System_Grammar_Term_zh-hans_',
+            'type'=>'translation',
+            'lang'=>'zh-Hans',
+        ],
+        [
+            "name"=>'_System_Grammar_Term_zh-hant_',
+            'type'=>'translation',
+            'lang'=>'zh-Hant',
+        ],
+        [
+            "name"=>'_System_Grammar_Term_en_',
+            'type'=>'translation',
+            'lang'=>'en',
+        ],
+        [
+            "name"=>'_community_term_zh-hans_',
+            'type'=>'translation',
+            'lang'=>'zh-Hans',
+        ],
+        [
+            "name"=>'_community_term_zh-hant_',
+            'type'=>'translation',
+            'lang'=>'zh-Hant',
+        ],
+        [
+            "name"=>'_community_term_en_',
+            'type'=>'translation',
+            'lang'=>'en',
+        ],
     ];
 
     /**

+ 77 - 0
app/Console/Commands/StatisticsDict.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Storage;
+use Carbon\Carbon;
+use App\Models\UserOperationLog;
+
+class StatisticsDict extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'statistics:dict';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '统计 字典 每月查询';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $file = "public/statistics/lookup-monthly.csv";
+        Storage::disk('local')->put($file, "");
+        #按月获取数据
+        $firstDay = UserOperationLog::where('op_type','dict_lookup')
+                            ->orderBy('created_at')
+                            ->select('created_at')
+                            ->first();
+        $firstDay = strtotime($firstDay->created_at);
+        $firstMonth = Carbon::create(date("Y-m",$firstDay));
+        $now = Carbon::now();
+        $current = $firstMonth;
+        $sumCount = 0;
+        while ($current <= $now) {
+            # code...
+            $start = Carbon::create($current)->startOfMonth();
+            $end = Carbon::create($current)->endOfMonth();
+            $date = $current->format('Y-m');
+            $count = UserOperationLog::where('op_type','dict_lookup')
+                              ->whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->count();
+            $sumCount += $count;
+            $editor = UserOperationLog::where('op_type','dict_lookup')
+                              ->whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->groupBy('user_id')
+                              ->select('user_id')->get();
+            $info = $date.','.$count.','.$sumCount.','.count($editor);
+            $this->info($info);
+            Storage::disk('local')->append($file, $info);
+            $current->addMonth(1);
+        }
+        return 0;
+    }
+}

+ 74 - 0
app/Console/Commands/StatisticsExp.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Storage;
+use Carbon\Carbon;
+use App\Models\UserOperationDaily;
+
+class StatisticsExp extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'statistics:exp';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '统计 经验值 每月查询';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $file = "public/statistics/exp-monthly.csv";
+        Storage::disk('local')->put($file, "");
+        #按月获取数据
+        $firstDay = UserOperationDaily::select('created_at')
+                            ->orderBy('created_at')
+                            ->first();
+        $firstDay = strtotime($firstDay->created_at);
+        $firstMonth = Carbon::create(date("Y-m",$firstDay));
+        $now = Carbon::now();
+        $current = $firstMonth;
+        $sumTime = 0;
+        while ($current <= $now) {
+            # code...
+            $start = Carbon::create($current)->startOfMonth();
+            $end = Carbon::create($current)->endOfMonth();
+            $date = $current->format('Y-m');
+            $time = UserOperationDaily::whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->sum('duration')/1000;
+            $sumTime += $time;
+            $editor = UserOperationDaily::whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->groupBy('user_id')
+                              ->select('user_id')->get();
+            $info = $date.','.(int)($time/3600).','.(int)($sumTime/3600).','.count($editor);
+            $this->info($info);
+            Storage::disk('local')->append($file, $info);
+            $current->addMonth(1);
+        }
+        return 0;
+    }
+}

+ 31 - 36
app/Console/Commands/StatisticsNissaya.php

@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
 use App\Models\Channel;
 use App\Models\Sentence;
 use Illuminate\Support\Facades\Storage;
+use Carbon\Carbon;
 
 class StatisticsNissaya extends Command
 {
@@ -21,7 +22,7 @@ class StatisticsNissaya extends Command
      *
      * @var string
      */
-    protected $description = '统计nissaya 每录入进度';
+    protected $description = '统计nissaya 每录入进度';
 
     /**
      * Create a new command instance.
@@ -41,45 +42,39 @@ class StatisticsNissaya extends Command
     public function handle()
     {
         $nissaya_channel = Channel::where('type','nissaya')->select('uid')->get();
-        $channels = [];
-        foreach ($nissaya_channel as $key => $value) {
-            # code...
-            $channels[] = $value->uid;
-        }
-        $this->info('channel:'.count($channels));
+        $this->info('channel:'.count($nissaya_channel));
         $maxDay = 360;
-        $file = "public/export/nissaya-daily.csv";
+        $file = "public/statistics/nissaya-monthly.csv";
         Storage::disk('local')->put($file, "");
-        #按天获取数据
-        for($i = 1; $i <= $maxDay; $i++){
-            $day = strtotime("today -{$i} day");
-            $date = date("Y-m-d",$day);
-            $strlen = Sentence::whereIn('channel_uid',$channels)
-                    ->whereDate('created_at','=',$date)
-                    ->sum('strlen');
-            $editor = Sentence::whereIn('channel_uid',$channels)
-                    ->whereDate('created_at','=',$date)
-                    ->groupBy('editor_uid')
-                    ->select('editor_uid')->get();
-            $info = $date.','.$strlen.','.count($editor);
-            $this->info($info);
-            Storage::disk('local')->append($file, $info);
-        }
-        $file = "public/export/nissaya-week.csv";
-        Storage::disk('local')->put($file, "");
-        for($i = 1; $i <= $maxDay; $i=$i+7){
-            $day1 = strtotime("today -{$i} day");
-            $date1 = date("Y-m-d",$day1);
-            $j = $i - 7;
-            $date2 = date("Y-m-d",strtotime("today -{$j} day"));
-            $editor = Sentence::whereIn('channel_uid',$channels)
-                    ->whereDate('created_at','>',$date1)
-                    ->whereDate('created_at','<=',$date2)
-                    ->groupBy('editor_uid')
-                    ->select('editor_uid')->get();
-            $info = $date2.','.$date1.','.count($editor);
+        #按月获取数据
+        $firstDay = Sentence::whereIn('channel_uid',$nissaya_channel)
+                            ->orderBy('created_at')
+                            ->select('created_at')
+                            ->first();
+        $firstDay = strtotime($firstDay->created_at);
+        $firstMonth = Carbon::create(date("Y-m",$firstDay));
+        $now = Carbon::now();
+        $current = $firstMonth;
+        $sumStrlen = 0;
+        while ($current <= $now) {
+            # code...
+            $start = Carbon::create($current)->startOfMonth();
+            $end = Carbon::create($current)->endOfMonth();
+            $date = $current->format('Y-m');
+            $strlen = Sentence::whereIn('channel_uid',$nissaya_channel)
+                              ->whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->sum('strlen');
+            $sumStrlen += $strlen;
+            $editor = Sentence::whereIn('channel_uid',$nissaya_channel)
+                              ->whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->groupBy('editor_uid')
+                              ->select('editor_uid')->get();
+            $info = $date.','.$strlen.','.$sumStrlen.','.count($editor);
             $this->info($info);
             Storage::disk('local')->append($file, $info);
+            $current->addMonth(1);
         }
         return 0;
     }

+ 74 - 0
app/Console/Commands/StatisticsWbw.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\Wbw;
+use Illuminate\Support\Facades\Storage;
+use Carbon\Carbon;
+
+class StatisticsWbw extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'statistics:wbw';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '统计 wbw 每月建立数量';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $file = "public/statistics/wbw-monthly.csv";
+        Storage::disk('local')->put($file, "");
+        #按月获取数据
+        $firstDay = Wbw::select('created_at')
+                        ->orderBy('created_at')
+                        ->first();
+        $firstDay = strtotime($firstDay->created_at);
+        $firstMonth = Carbon::create(date("Y-m",$firstDay));
+        $now = Carbon::now();
+        $current = $firstMonth;
+        $sumCount = 0;
+        while ($current <= $now) {
+            # code...
+            $start = Carbon::create($current)->startOfMonth();
+            $end = Carbon::create($current)->endOfMonth();
+            $date = $current->format('Y-m');
+            $count = Wbw::whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->count();
+            $sumCount += $count;
+            $editor = Wbw::whereDate('created_at','>=',$start)
+                              ->whereDate('created_at','<=',$end)
+                              ->groupBy('editor_id')
+                              ->select('editor_id')->get();
+            $info = $date.','.$count.','.$sumCount.','.count($editor);
+            $this->info($info);
+            Storage::disk('local')->append($file, $info);
+            $current->addMonth(1);
+        }
+        return 0;
+    }
+}

+ 50 - 0
app/Console/Commands/TestJsonToXml.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Tools\Tools;
+
+class TestJsonToXml extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'test:json.to.xml';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $array = [
+            'pali'=>['status'=>'7','value'=>'bārāṇasiyaṃ'],
+            'real'=>['status'=>'7','value'=>'bārāṇasiyaṃ'],
+            'id'=>'p171-2475-10'
+        ];
+        $xml = Tools::JsonToXml($array);
+        $this->info($xml);
+        return 0;
+    }
+}

+ 30 - 5
app/Console/Commands/TestMdRender.php

@@ -4,6 +4,7 @@ namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
 use App\Http\Api\MdRender;
+use Illuminate\Support\Str;
 
 class TestMdRender extends Command
 {
@@ -38,12 +39,32 @@ class TestMdRender extends Command
      */
     public function handle()
     {
-        $markdown = "# heading [[isipatana]] \n\n";
-        $markdown .= "[[isipatana]] `bla` [[dhammacakka]]\n\n";
-        $markdown .= "```haha\n";
+        $markdown = "";
+        //$markdown .= "[[isipatana]] `bla` [[dhammacakka]]\n\n";
+        //$markdown .= "前面的\n";
+        //$markdown .= "{{note|\n";
+        //$markdown .= "多**行注**释\n\n";
+        //$markdown .= "多行注释\n";
+        //$markdown .= "}}\n\n";
+        $markdown .= "## heading \n\n";
+        $markdown .= "ddd \n\n";
+        $markdown .= "- title \n";
+        $markdown .= "  \n";
+        $markdown .= "  content-1\n";
+        $markdown .= "- title-2 \n";
+        $markdown .= "  \n";
+        $markdown .= "  content-2\n";
+        $markdown .= "  \n";
+        $markdown .= "\n";
+        $markdown .= "\n";
+        $markdown .= "aaa bbb\n";
+        /*
+        $markdown .= "```\n";
         $markdown .= "content **content**\n";
         $markdown .= "content **content**\n";
         $markdown .= "```\n\n";
+        */
+        /*
         $markdown .= "{{168-916-10-37}}";
         $markdown .= "{{exercise|1|((168-916-10-37))}}";
 
@@ -51,14 +72,18 @@ class TestMdRender extends Command
         $markdown2 .= "{{exercise\n|id=1\n|content={{168-916-10-37}}}}";
         $markdown2 .= "{{exercise\n|id=2\n|content=# ddd}}";
 
-        $markdown2 = "{{note|trigger=kacayana|text={{99-556-8-12}}}}";
+        $markdown2 .= "{{note|trigger=kacayana|text={{99-556-8-12}}}}";
+        $markdown2 = "aaa=bbb\n";
+        $markdown2 .= "ccc=ddd\n";
+*/
         //echo MdRender::render($markdown,'00ae2c48-c204-4082-ae79-79ba2740d506');
         //$wiki = MdRender::markdown2wiki($markdown2);
         //$xml = MdRender::wiki2xml($wiki);
         //$html = MdRender::xmlQueryId($xml, "1");
         //$sent = MdRender::take_sentence($html);
         //print_r($sent);
-        echo MdRender::render2($markdown2,'00ae2c48-c204-4082-ae79-79ba2740d506');
+        echo Str::markdown($markdown);
+        echo MdRender::render2($markdown,'00ae2c48-c204-4082-ae79-79ba2740d506',null,'read','nissaya');
         return 0;
     }
 }

+ 57 - 0
app/Console/Commands/UpgradeAt20230227.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+
+class UpgradeAt20230227 extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:at20230227';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'update to 2.0';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->call('init:system.channel');
+        $this->call('init:system.dict');
+        $this->call('upgrade:dict');
+        $this->call('upgrade:dict.vocabulary');
+        $this->call('upgrade:dict.default.meaning');
+
+        //语料库
+        $this->call('init:cs6sentence');
+        $this->call('upgrade:palitext');
+        $this->call('upgrade:wbw.template');
+
+        $this->call('upgrade:related.paragraph');
+        $this->call('upgrade:fts',['--content'=>true]);
+        $this->call('upgrade:pcd.book.id');
+
+        return 0;
+    }
+}

+ 150 - 0
app/Console/Commands/UpgradeChapterDynamicWeekly.php

@@ -0,0 +1,150 @@
+<?php
+
+namespace App\Console\Commands;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Console\Command;
+use Illuminate\Support\Carbon;
+use App\Models\SentHistory;
+use App\Models\Sentence;
+use App\Models\ProgressChapter;
+use App\Models\PaliText;
+use Illuminate\Support\Facades\Cache;
+
+class UpgradeChapterDynamicWeekly extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:chapter.dynamic.weekly {--test} {--book=} {--offset=}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '更新章节活跃程度svg';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+		$this->info('upgrade:chapter.dynamic.weekly start.');
+
+        $startAt = time();
+        $weeks = 60; //统计多少周
+
+//更新总动态
+		$this->info("更新总动态");
+        $table = ProgressChapter::select('book','para')
+                                    ->groupBy('book','para')
+                                    ->orderBy('book');
+        if($this->option('book')){
+            $table = $table->where('book',$this->option('book'));
+        }
+        $chapters = $table->get();
+        $bar = $this->output->createProgressBar(count($chapters));
+        foreach ($chapters as $key => $chapter) {
+            #章节长度
+            $paraEnd = PaliText::where('book',$chapter->book)
+                            ->where('paragraph',$chapter->para)
+                            ->value('chapter_len')+$chapter->para-1;
+
+            $progress = [];
+            for ($i=$weeks; $i > 0 ; $i--) {
+                #这一周有多少次更新
+                $currDay = $i*7+$this->option('offset',0);
+                $count = SentHistory::whereBetween('sent_histories.created_at',
+                                                  [Carbon::today()->subDays($currDay)->startOfWeek(),
+                                                  Carbon::today()->subDays($currDay)->endOfWeek()])
+                           ->leftJoin('sentences', 'sent_histories.sent_uid', '=', 'sentences.uid')
+                             ->where('book_id',$chapter->book)
+                             ->whereBetween('paragraph',[$chapter->para,$paraEnd])
+                             ->count();
+                $progress[] = $count;
+            }
+            $key="/chapter_dynamic/{$chapter->book}/{$chapter->para}/global";
+            Cache::put($key,$progress,3600*24*7);
+            $bar->advance();
+
+            if($this->option('test')){
+                $this->info("key:{$key}");
+                if(Cache::has($key)){
+                    $this->info('has key '.$key);
+                }
+                break; //调试代码
+            }
+        }
+        $bar->finish();
+
+		$time = time()- $startAt;
+        $this->info("用时 {$time}");
+
+        $startAt = time();
+
+		$startAt = time();
+        //更新chennel动态
+        $this->info('更新chennel动态');
+
+        $table = ProgressChapter::select('book','para','channel_id');
+        if($this->option('book')){
+            $table = $table->where('book',$this->option('book'));
+        }
+        $bar = $this->output->createProgressBar($table->count());
+
+        foreach ($table->cursor() as $chapter) {
+            # code...
+            $max=0;
+            #章节长度
+            $paraEnd = PaliText::where('book',$chapter->book)
+                            ->where('paragraph',$chapter->para)
+                            ->value('chapter_len')+$chapter->para-1;
+            $progress = [];
+            for ($i=$weeks; $i > 0 ; $i--) {
+                #这一周有多少次更新
+                $currDay = $i*7+$this->option('offset',0);
+                $count = SentHistory::whereBetween('sent_histories.created_at',
+                                                [Carbon::today()->subDays($currDay)->startOfWeek(),
+                                                Carbon::today()->subDays($currDay)->endOfWeek()])
+                           ->leftJoin('sentences', 'sent_histories.sent_uid', '=', 'sentences.uid')
+                             ->where('book_id',$chapter->book)
+                             ->whereBetween('paragraph',[$chapter->para,$paraEnd])
+                             ->where('sentences.channel_uid',$chapter->channel_id)
+                             ->count();
+                $progress[] = $count;
+            }
+            $key="/chapter_dynamic/{$chapter->book}/{$chapter->para}/ch_{$chapter->channel_id}";
+            Cache::put($key,$progress,3600*24*7);
+            $bar->advance();
+
+            if($this->option('test')){
+                $this->info("key:{$key}");
+                if(Cache::has($key)){
+                    $this->info('has key '.$key);
+                }
+                break; //调试代码
+            }
+        }
+        $bar->finish();
+		$time = time()- $startAt;
+        $this->info("用时 {$time}");
+
+        $this->info("upgrade:chapter.dynamic done");
+
+        return 0;
+    }
+}

+ 165 - 0
app/Console/Commands/UpgradeCommunityTerm.php

@@ -0,0 +1,165 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Tools\Tools;
+use App\Models\DhammaTerm;
+use App\Models\UserOperationDaily;
+use App\Models\Sentence;
+
+use App\Http\Api\ChannelApi;
+use Illuminate\Support\Str;
+
+class UpgradeCommunityTerm extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:community.term {lang}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $lang = strtolower($this->argument('lang'));
+        $langFamily = explode('-',$lang)[0];
+        $localTerm = ChannelApi::getSysChannel(
+            "_community_term_{$lang}_"
+        );
+        if(!$localTerm){
+            return 1;
+        }
+        $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
+        if($channelId === false){
+            $this->error('no channel');
+            return 1;
+        }
+        $table = DhammaTerm::select('word')->whereIn('language',[$this->argument('lang'),$lang,$langFamily])
+                            ->groupBy('word');
+
+
+        $words = $table->get();
+        $bar = $this->output->createProgressBar(count($words));
+        foreach ($words as $key => $word) {
+            /**
+             * 最优算法
+             * 1. 找到最常见的意思
+             * 2. 找到分数最高的
+             */
+            $bestNote = "" ;
+            $allTerm = DhammaTerm::where('word',$word->word)
+                                ->whereIn('language',[$this->argument('lang'),$lang,$langFamily])
+                                ->get();
+            $score = [];
+            foreach ($allTerm as $key => $term) {
+                //经验值
+                $exp = UserOperationDaily::where('user_id',$term->editor_id)
+                                        ->where('date_int','<=',date_timestamp_get(date_create($term->updated_at))*1000)
+                                        ->sum('duration');
+                $iExp = (int)($exp/1000);
+                $noteStrLen = mb_strlen($term->note);
+                $paliStrLen = 0;
+                $tranStrLen = 0;
+                $noteWithoutPali = "";
+                if(!empty(trim($term->note))){
+                    #查找句子模版
+                    $pattern = "/\{\{[0-9].+?\}\}/";
+                    //获取去掉句子模版的剩余部分
+                    $noteWithoutPali = preg_replace($pattern,"",$term->note);
+                    $sentences = [];
+                    $iSent = preg_match_all($pattern,$term->note,$sentences);
+                    if($iSent>0){
+                        foreach ($sentences[0] as  $sentence) {
+                            $sentId = explode("-",trim($sentence,"{}"));
+                            if(count($sentId) === 4){
+                                $hasTran = Sentence::where('book_id',$sentId[0])
+                                                    ->where('paragraph',$sentId[1])
+                                                    ->where('word_start',$sentId[2])
+                                                    ->where('word_end',$sentId[3])
+                                                    ->exists();
+
+                                $sentLen = Sentence::where('book_id',$sentId[0])
+                                                    ->where('paragraph',$sentId[1])
+                                                    ->where('word_start',$sentId[2])
+                                                    ->where('word_end',$sentId[3])
+                                                    ->where("channel_uid", $channelId)
+                                                    ->value('strlen');
+                                if($sentLen){
+                                    $paliStrLen += $sentLen;
+                                    if($hasTran){
+                                        $tranStrLen += $sentLen;
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                }
+                //计算该术语总得分
+                $score["{$key}"] = $iExp*$noteStrLen;
+            }
+
+            $hotMeaning = DhammaTerm::selectRaw('meaning,count(*) as co')
+                        ->where('word',$word->word)
+                        ->whereIn('language',[$this->argument('lang'),$lang,$langFamily])
+                        ->groupBy('meaning')
+                        ->orderBy('co','desc')
+                        ->first();
+            if($hotMeaning){
+                $bestNote = "";
+                if(count($score)>0){
+                    arsort($score);
+                    $bestNote = $allTerm[(int)key($score)]->note;
+                }
+
+                $term = DhammaTerm::where('channal',$localTerm)->firstOrNew(
+                        [
+                            "word" => $word->word,
+                            "channal" => $localTerm,
+                        ],
+                        [
+                            'id' =>app('snowflake')->id(),
+                            'guid' =>Str::uuid(),
+                            'word_en' =>Tools::getWordEn($word->word),
+                            'meaning' => '',
+                            'language' => $this->argument('lang'),
+                            'owner' => config("app.admin.root_uuid"),
+                            'editor_id' => 0,
+                            'create_time' => time()*1000,
+                        ]
+                    );
+                    $term->meaning = $hotMeaning->meaning;
+                    $term->note = $bestNote;
+                    $term->modify_time = time()*1000;
+                    $term->save();
+            }
+            $bar->advance();
+        }
+        $bar->finish();
+
+        return 0;
+    }
+}

+ 61 - 39
app/Console/Commands/UpgradeCompound.php

@@ -16,7 +16,7 @@ class UpgradeCompound extends Command
      *
      * @var string
      */
-    protected $signature = 'upgrade:compound {word?} {--test}';
+    protected $signature = 'upgrade:compound {word?} {--book=} {--debug} {--test}';
 
     /**
      * The console command description.
@@ -55,11 +55,16 @@ class UpgradeCompound extends Command
 		$_word = $this->argument('word');
 		if(!empty($_word)){
 			$ts = new TurboSplit();
+            if($this->option('debug')){
+                $ts->debug(true);
+            }
 			$results = $ts->splitA($_word);
 			Storage::disk('local')->put("tmp/compound1.csv", "word,type,grammar,parent,factors");
 			foreach ($results as $key => $value) {
 				# code...
-				Storage::disk('local')->append("tmp/compound1.csv", "{$value['word']},{$value['type']},{$value['grammar']},{$value['parent']},{$value['factors']}");
+                $output = "{$value['word']},{$value['type']},{$value['grammar']},{$value['parent']},{$value['factors']}";
+                $this->info($output);
+				Storage::disk('local')->append("tmp/compound1.csv", $output);
 			}
 			return 0;
 		}
@@ -85,8 +90,9 @@ class UpgradeCompound extends Command
 					$parts = $ts->splitA($word->word);
 					foreach ($parts as $part) {
 						# code...
-						$this->info("{$part['word']},{$part['factors']},{$part['confidence']}");
-						Storage::disk('local')->append("tmp/compound.md", "- `{$part['word']}`,{$part['factors']},{$part['confidence']}");
+                        $info = "`{$part['word']}`,{$part['factors']},{$part['confidence']}";
+						$this->info($info);
+						Storage::disk('local')->append("tmp/compound.md", "- {$info}");
 					}
 				}
 			}
@@ -94,13 +100,24 @@ class UpgradeCompound extends Command
 			return 0;
 		}
 
-		//$words = WordIndex::where('final',0)->select('word')->orderBy('count','desc')->skip(72300)->cursor();
-		$words = WbwTemplate::select('real')
-						->where('type','<>','.ctl.')
-						->where('real','<>','')
-						->groupBy('real')->cursor();
+        if($this->option('book')){
+            $words = WbwTemplate::select('real')
+                            ->where('book',$this->option('book'))
+                            ->where('type','<>','.ctl.')
+                            ->where('real','<>','')
+                            ->groupBy('real')->cursor();
+        }else{
+            $words = WbwTemplate::select('real')
+                            ->where('type','<>','.ctl.')
+                            ->where('real','<>','')
+                            ->groupBy('real')->cursor();
+        }
+
 		$count = 0;
 		foreach ($words as $key => $word) {
+            UserDict::where('word',$word->real)
+                    ->where('dict_id',$dict_id)
+                    ->update(['flag'=>2]);
 			//先看目前字典里有没有
 			$isExists = UserDict::where('word',$word->real)
 								->where('dict_id',"<>",$dict_id)
@@ -108,43 +125,48 @@ class UpgradeCompound extends Command
 
 			if($isExists){
 				$this->info("Exists:{$word->real}");
-				continue;
+				//continue;
 			}
 			# code...
 			$count++;
 			$this->info("{$count}:{$word->real}");
 			$ts = new TurboSplit();
-			$parts = $ts->splitA($word->real);
-			foreach ($parts as $part) {
-				$new = UserDict::firstOrNew(
-					[
-						'word' => $part['word'],
-						'factors' => $part['factors'],
-						'dict_id' => $dict_id,
-					],
-					[
-						'id' => app('snowflake')->id(),
-						'source' => '_ROBOT_',
-						'create_time'=>(int)(microtime(true)*1000),
-					]
-				);
-				if(isset($part['type'])){
-					$new->type = $part['type'];
-				}else{
-					$new->type = ".cp.";
-				}
-				if(isset($part['grammar'])) $new->parent = $part['grammar'];
-				if(isset($part['parent'])) $new->parent = $part['parent'];
-				$new->confidence = 50*$part['confidence'];
-				$new->note = $part['confidence'];
-				$new->language = 'cm';
-				$new->creator_id = 1;
-				$new->flag = 1;
-				$new->save();
-			}
+
+            $parts = $ts->splitA($word->real);
+            foreach ($parts as $part) {
+                if(isset($part['type']) && $part['type'] === ".v."){
+                    continue;
+                }
+
+                $new = UserDict::firstOrNew(
+                    [
+                        'word' => $part['word'],
+                        'factors' => $part['factors'],
+                        'dict_id' => $dict_id,
+                    ],
+                    [
+                        'id' => app('snowflake')->id(),
+                        'source' => '_ROBOT_',
+                        'create_time'=>(int)(microtime(true)*1000),
+                    ]
+                );
+                if(isset($part['type'])){
+                    $new->type = $part['type'];
+                }else{
+                    $new->type = ".cp.";
+                }
+                if(isset($part['grammar'])) $new->grammar = $part['grammar'];
+                if(isset($part['parent'])) $new->parent = $part['parent'];
+                $new->confidence = 50*$part['confidence'];
+                $new->note = $part['confidence'];
+                $new->language = 'cm';
+                $new->creator_id = 1;
+                $new->flag = 1;
+                $new->save();
+            }
 		}
 		//删除旧数据
-		UserDict::where('dict_id',$dict_id)->where('flag',0)->delete();
+		UserDict::where('dict_id',$dict_id)->where('flag',2)->delete();
 		UserDict::where('dict_id',$dict_id)->where('flag',1)->update(['flag'=>0]);
 
         return 0;

+ 5 - 6
app/Console/Commands/UpgradeDaily.php

@@ -49,12 +49,10 @@ class UpgradeDaily extends Command
 			]);
 		}
 
-        # 刷巴利语句子uuid 仅调用一次
-        //$this->call('upgrade:palitextid');
         //巴利原文段落库目录结构改变时运行
-        $this->call('upgrade:palitext');
+        //$this->call('upgrade:palitext');
         #巴利段落标签
-        $this->call('upgrade:palitexttag');
+        //$this->call('upgrade:palitexttag');
 
         //更新单词首选意思
         $this->call('upgrade:dict.default.meaning');
@@ -62,8 +60,9 @@ class UpgradeDaily extends Command
         #译文进度
         $this->call('upgrade:progress');
         $this->call('upgrade:progresschapter');
-        # 段落更新图
-        $this->call('upgrade:chapterdynamic');
+        //社区术语表
+        $this->call('upgrade:community.term',['zh-Hans']);
+
         # 逐词译数据库分析
         $this->call('upgrade:wbwanalyses');
 

+ 81 - 17
app/Console/Commands/UpgradeDictDefaultMeaning.php

@@ -1,5 +1,12 @@
 <?php
-
+/**
+ * 刷新字典单词的默认意思
+ * 目标:
+ *    可以查询到某个单词某种语言的首选意思
+ * 算法:
+ * 1. 某种语言会有多个字典。按照字典重要程度人工排序
+ * 2. 按照顺序搜索这些字典。找到第一个意思就停止。
+ */
 namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
@@ -14,8 +21,48 @@ class UpgradeDictDefaultMeaning extends Command
      *
      * @var string
      */
-    protected $signature = 'upgrade:dict.default.meaning';
-
+    protected $signature = 'upgrade:dict.default.meaning {word?}';
+    protected $dict = [
+        "zh-Hans"=>[
+            "8833de18-0978-434c-b281-a2e7387f69be",	/*巴汉字典明法尊者修订版*/
+            "f364d3dc-b611-471b-9a4f-531286b8c2c3",	/*《巴汉词典》Mahāñāṇo Bhikkhu编著*/
+            "0e4dc5c8-a228-4693-92ba-7d42918d8a91",	/*汉译パーリ语辞典-黃秉榮*/
+            "6aa9ec8b-bba4-4bcd-abd2-9eae015bad2b",	/*汉译パーリ语辞典-李瑩*/
+            "eb99f8b4-c3e5-43af-9102-6a93fcb97db6",	/*パーリ语辞典--勘误表*/
+            "0d79e8e8-1430-4c99-a0f1-b74f2b4b26d8",	/*《巴汉词典》增订*/
+        ],
+        "zh-Hant"=>[
+            "3acf0c0f-59a7-4d25-a3d9-bf394a266ebd",	/*汉译パーリ语辞典-黃秉榮*/
+            "5293ffb9-887e-4cf2-af78-48bf52a85304",	/*巴利詞根*/
+        ],
+        "jp"=>[
+            "91d3ec93-3811-4973-8d84-ced99179a0aa",	/*パーリ语辞典*/
+            "6d6c6812-75e7-457d-874f-5b049ad4b6de",	/*パーリ语辞典-增补*/
+        ],
+        "en"=>[
+            "c6e70507-4a14-4687-8b70-2d0c7eb0cf21",	/*	Concise P-E Dict*/
+            "eae9fd6f-7bac-4940-b80d-ad6cd6f433bf",	/*	Concise P-E Dict*/
+            "2f93d0fe-3d68-46ee-a80b-11fa445a29c6",	/*	unity*/
+            "b9163baf-2bca-41a5-a936-5a0834af3945",	/*	Pali-Dict Vri*/
+            "b089de57-f146-4095-b886-057863728c43",	/*	Buddhist Dictionary*/
+            "6afb8c05-5cbe-422e-b691-0d4507450cb7",	/*	PTS P-E dictionary*/
+            "0bfd87ec-f3ac-49a2-985e-28388779078d",	/*	Pali Proper Names Dict*/
+            "1cdc29e0-6783-4241-8784-5430b465b79c",	/*	Pāḷi Root In Saddanīti*/
+            "5718cbcf-684c-44d4-bbf2-4fa12f2588a4",	/*	Critical Pāli Dictionary*/
+        ],
+        "my"=>[
+            "e740ef40-26d7-416e-96c2-925d6650ac6b",	/*	Tipiṭaka Pāḷi-Myanmar*/
+            "beb45062-7c20-4047-bcd4-1f636ba443d1",	/*	U Hau Sein’s Pāḷi-Myanmar Dictionary*/
+            "1e299ccb-4fc4-487d-8d72-08f63d84c809",	/*	Pali Roots Dictionary*/
+            "6f9caea1-17fa-41f1-92e5-bd8e6e70e1d7",	/*	U Hau Sein’s Pāḷi-Myanmar*/
+        ],
+        "vi"=>[
+            "23f67523-fa03-48d9-9dda-ede80d578dd2",	/*	Pali Viet Dictionary*/
+            "4ac8a0d5-9c6f-4b9f-983d-84288d47f993",	/*	Pali Viet Abhi-Terms*/
+            "7c7ee287-35ba-4cf3-b87b-30f1fa6e57c9",	/*	Pali Viet Vinaya Terms*/
+        ],
+        "cm"=>[],
+    ];
     /**
      * The console command description.
      *
@@ -40,6 +87,7 @@ class UpgradeDictDefaultMeaning extends Command
      */
     public function handle()
     {
+        $_word = $this->argument('word');
         # 获取字典中所有的语言
         $langInDict = UserDict::select('language')->groupBy('language')->get();
         $languages = [];
@@ -48,30 +96,46 @@ class UpgradeDictDefaultMeaning extends Command
                 $languages[] = $lang["language"];
             }
         }
-		print_r($languages);
-        foreach ($languages as $thisLang) {
-            Log::info("running $thisLang");
+		//print_r($languages);
+        foreach ($this->dict as $thisLang=>$dictId) {
+            $this->info("running $thisLang");
+
             $bar = $this->output->createProgressBar(UserDict::where('source','_PAPER_')
                                                         ->where('language',$thisLang)->count());
             foreach (UserDict::where('source','_PAPER_')
                                 ->where('language',$thisLang)
                                 ->select('word','note')
                                 ->cursor() as $word) {
-                Cache::put("dict_first_mean/{$thisLang}/{$word['word']}", mb_substr($word['note'],0,50,"UTF-8") ,24*3600);
+                if(!empty($word['note'])){
+                    Cache::put("dict_first_mean/{$thisLang}/{$word['word']}", mb_substr($word['note'],0,50,"UTF-8") ,30*24*3600);
+                }
                 $bar->advance();
             }
             $bar->finish();
+
+            for ($i=count($dictId)-1; $i >=0 ; $i--) {
+                # code...
+                $this->info("running $thisLang - {$dictId[$i]}");
+                $count = 0;
+                foreach (UserDict::where('dict_id',$dictId[$i])
+                    ->select('word','note')
+                    ->cursor() as $word) {
+                        $cacheKey = "dict_first_mean/{$thisLang}/{$word['word']}";
+                        if(!empty($word['note'])){
+                            $cacheValue = mb_substr($word['note'],0,50,"UTF-8");
+                            if(!empty($_word) && $word['word'] === $_word ){
+                                Log::info($cacheKey.':'.$cacheValue);
+                            }
+                            Cache::put($cacheKey, $cacheValue ,30*24*3600);
+                        }
+
+                        if($count % 1000 === 0){
+                            $this->info("{$count}");
+                        }
+                        $count++;
+                    }
+            }
         }
-        Log::info("running com");
-        $bar = $this->output->createProgressBar(UserDict::where('source','_PAPER_')->count());
-        foreach (UserDict::where('source','_PAPER_')
-                            ->select('word','note')
-                            ->cursor() as $word) {
-            $key = "dict_first_mean/com/{$word['word']}";
-            Cache::put($key, mb_substr($word['note'],0,50,"UTF-8") ,24*3600);
-            $bar->advance();
-        }
-        $bar->finish();
 
         return 0;
     }

+ 2 - 0
app/Console/Commands/UpgradeDictSysWbwExtract.php

@@ -129,6 +129,8 @@ class UpgradeDictSysWbwExtract extends Command
 				$bar->advance();
 		}
 		$bar->finish();
+
+        //TODO 删除旧数据
         return 0;
     }
 }

+ 9 - 4
app/Console/Commands/UpgradeDictVocabulary.php

@@ -42,15 +42,20 @@ class UpgradeDictVocabulary extends Command
     {
         $words = UserDict::where('source','_PAPER_')->selectRaw('word,count(*)')->groupBy('word')->cursor();
 
-		$bar = $this->output->createProgressBar(200000);
+		$bar = $this->output->createProgressBar(230000);
 		foreach ($words as $word) {
-			$update = Vocabulary::where('word',$word->word)->updateOrCreate(
-                ['count' => $word->count, 'flag' => 1],
-                ['word' => $word->word, 'word_en'=>Tools::getWordEn($word->word)]
+			$update = Vocabulary::firstOrNew(
+                ['word' => $word->word],
+                ['word_en'=>Tools::getWordEn($word->word)]
             );
+            $update->count = $word->count;
+            $update->flag = 1;
+            $update->strlen = mb_strlen($word->word,"UTF-8");
+            $update->save();
             $bar->advance();
 		}
         $bar->finish();
+        Vocabulary::where('flag',0)->delete();
         return 0;
     }
 }

+ 102 - 0
app/Console/Commands/UpgradeFts.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\BookTitle;
+use App\Models\FtsText;
+use App\Models\WbwTemplate;
+
+class UpgradeFts extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:fts {--content : upgrade col content}
+        {para?}
+        {--test : output log}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'upgrade full text search table';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+
+        if($this->option('content')){
+            if(!empty($this->argument('para'))){
+                $para = explode('-',$this->argument('para'));
+            }
+            for ($iBook=1; $iBook <= 217; $iBook++) {
+                if(isset($para[0]) && $para[0] != $iBook){
+                    continue;
+                }
+                # code...
+                $this->info('book:'.$iBook);
+                $maxParagraph = WbwTemplate::where('book',$iBook)->max('paragraph');
+                $bar = $this->output->createProgressBar($maxParagraph-1);
+                for($iPara=1; $iPara <= $maxParagraph; $iPara++){
+                    if(isset($para[1]) && $para[1] != $iPara){
+                        $bar->advance();
+                        continue;
+                    }
+                    $content = $this->getContent($iBook,$iPara);
+                    if($this->option('test')){
+                        $this->info($content);
+                    }else{
+                        //TODO update bold
+                        FtsText::where('book',$iBook)->where('paragraph',$iPara)->update(['content'=>$content]);
+                    }
+                    $bar->advance();
+                }
+                $bar->finish();
+                $this->info('done');
+            }
+        }
+        return 0;
+    }
+
+    private function getContent($book,$para){
+        $words = WbwTemplate::where('book',$book)
+                            ->where('paragraph',$para)
+                            ->where('type',"<>",".ctl.")
+                            ->orderBy('wid')->get();
+        $content = '';
+        foreach ($words as  $word) {
+            if($word->style === 'bld'){
+                if(strpos($word->word,"{")===FALSE){
+                    $content .= "**{$word->word}** ";
+                }else{
+                    $content .= str_replace(['{','}'],['**','** '],$word->word);
+                }
+            }else if($word->style === 'note'){
+                $content .= " _{$word->word}_ ";
+            }else{
+                $content .= $word->word . " ";
+            }
+        }
+        return $content;
+    }
+}
+
+

+ 124 - 116
app/Console/Commands/UpgradePaliText.php

@@ -1,9 +1,13 @@
 <?php
-
+/**
+ * 计算章节的父子,前后关系
+ * 输入: csv文件
+ */
 namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
 use App\Models\PaliText;
+use App\Models\BookTitle;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
@@ -42,7 +46,7 @@ class UpgradePaliText extends Command
     {
 		$this->info("upgrade pali text");
 		$startTime = time();
-        
+
 		$_from = $this->argument('from');
 		$_to = $this->argument('to');
 		if(empty($_from) && empty($_to)){
@@ -78,7 +82,7 @@ class UpgradePaliText extends Command
 					$inputRow++;
 				}
 				fclose($fp);
-				
+
 			} else {
 				$this->error( "can not open csv file. filename=" . $csvFile. PHP_EOL) ;
 				Log::error( "can not open csv file. filename=" . $csvFile) ;
@@ -86,140 +90,144 @@ class UpgradePaliText extends Command
 			}
 			$title_data = PaliText::select(['book','paragraph','level','parent','toc','lenght'])
 								->where('book',$from)->orderby('paragraph','asc')->get();
-            {
-				$paragraph_count = count($title_data);
-				$paragraph_info = array();
-				$paragraph_info[] = array($from, -1, $paragraph_count, -1, -1, -1);
 
+            $paragraph_count = count($title_data);
+            $paragraph_info = array();
+            $paragraph_info[] = array($from, -1, $paragraph_count, -1, -1, -1);
 
-                for ($iPar = 0; $iPar < count($title_data); $iPar++) {
-                    $title_data[$iPar]["level"] = $arrInserString[$iPar][3];
-                }
 
+            for ($iPar = 0; $iPar < count($title_data); $iPar++) {
+                $title_data[$iPar]["level"] = $arrInserString[$iPar][3];
+            }
 
-				for ($iPar = 0; $iPar < count($title_data); $iPar++) {
-					$book = $from ;
-					$paragraph = $title_data[$iPar]["paragraph"];
+            for ($iPar = 0; $iPar < count($title_data); $iPar++) {
+                $book = $from ;
+                $paragraph = $title_data[$iPar]["paragraph"];
+                $true_level = (int) $title_data[$iPar]["level"];
 
-					if ((int) $title_data[$iPar]["level"] == 8) {
-						$title_data[$iPar]["level"] = 100;
-					}
+                if ((int) $title_data[$iPar]["level"] == 8) {
+                    $title_data[$iPar]["level"] = 100;
+                }
+                $curr_level = (int) $title_data[$iPar]["level"];
+                # 计算这个chapter的段落数量
+                $length = -1;
 
-					$curr_level = (int) $title_data[$iPar]["level"];
-					# 计算这个chapter的段落数量
-					$length = -1;
-				
-					
-					for ($iPar1 = $iPar + 1; $iPar1 < count($title_data); $iPar1++) {
-						$thislevel = (int) $title_data[$iPar1]["level"];
-						if ($thislevel <= $curr_level) {
-							$length = (int) $title_data[$iPar1]["paragraph"] - $paragraph;
-							break;
-						}
-					}
 
-					if ($length == -1) {
-						$length = $paragraph_count - $paragraph + 1;
-					}
+                for ($iPar1 = $iPar + 1; $iPar1 < count($title_data); $iPar1++) {
+                    $thislevel = (int) $title_data[$iPar1]["level"];
+                    if ($thislevel <= $curr_level) {
+                        $length = (int) $title_data[$iPar1]["paragraph"] - $paragraph;
+                        break;
+                    }
+                }
+
+                if ($length == -1) {
+                    $length = $paragraph_count - $paragraph + 1;
+                }
 
-                    /*
-                    上一个段落
-                    算法:查找上一个标题段落。而且该标题段落的下一个段落不是标题段落
-                    */
-                    $prev = -1;
-                    if ($iPar > 0) {
-                        for ($iPar1 = $iPar - 1; $iPar1 >= 0; $iPar1--) {
-                            if ($title_data[$iPar1]["level"] < 8 && $title_data[$iPar1+1]["level"]==100) {
-                                $prev = $title_data[$iPar1]["paragraph"];
-                                break;
-                            }
+                /*
+                上一个段落
+                算法:查找上一个标题段落。而且该标题段落的下一个段落不是标题段落
+                */
+                $prev = -1;
+                if ($iPar > 0) {
+                    for ($iPar1 = $iPar - 1; $iPar1 >= 0; $iPar1--) {
+                        if ($title_data[$iPar1]["level"] < 8 && $title_data[$iPar1+1]["level"]==100) {
+                            $prev = $title_data[$iPar1]["paragraph"];
+                            break;
                         }
                     }
-                    /*
-                    下一个段落
-                    算法:查找下一个标题段落。而且该标题段落的下一个段落不是标题段落
-                    */
-                    $next = -1;
-                    if ($iPar < count($title_data) - 1) {
-                        for ($iPar1 = $iPar + 1; $iPar1 < count($title_data)-1; $iPar1++) {
-                            if ($title_data[$iPar1]["level"] <8 && $title_data[$iPar1+1]["level"]==100) {
-                                $next = $title_data[$iPar1]["paragraph"];
-                                break;
-                            }
+                }
+                /*
+                下一个段落
+                算法:查找下一个标题段落。而且该标题段落的下一个段落不是标题段落
+                */
+                $next = -1;
+                if ($iPar < count($title_data) - 1) {
+                    for ($iPar1 = $iPar + 1; $iPar1 < count($title_data)-1; $iPar1++) {
+                        if ($title_data[$iPar1]["level"] <8 && $title_data[$iPar1+1]["level"]==100) {
+                            $next = $title_data[$iPar1]["paragraph"];
+                            break;
                         }
                     }
+                }
+                //查找parent
+                $parent = -1;
+                if ($iPar > 0) {
+                    for ($iPar1 = $iPar - 1; $iPar1 >= 0; $iPar1--) {
+                        if ($title_data[$iPar1]["level"] < $true_level) {
+                            $parent = $title_data[$iPar1]["paragraph"];
+                            break;
+                        }
+                    }
+                }
+                //计算章节包含总字符数
+                $iChapter_strlen = 0;
 
-					$parent = -1;
-					if ($iPar > 0) {
-						for ($iPar1 = $iPar - 1; $iPar1 >= 0; $iPar1--) {
-							if ($title_data[$iPar1]["level"] < $curr_level) {
-								$parent = $title_data[$iPar1]["paragraph"];
-								break;
-							}
-						}
-					}
-					//计算章节包含总字符数
-					$iChapter_strlen = 0;
-
-					for ($i = $iPar; $i < $iPar + $length; $i++) {
-						$iChapter_strlen += $title_data[$i]["lenght"];
-					}
+                for ($i = $iPar; $i < $iPar + $length; $i++) {
+                    $iChapter_strlen += $title_data[$i]["lenght"];
+                }
 
-					$newData = [
-						'level' => $arrInserString[$iPar][3],
-						'toc' => $arrInserString[$iPar][5],
-						'chapter_len' => $length,
-						'next_chapter' => $next,
-						'prev_chapter' => $prev,
-						'parent' => $parent,
-						'chapter_strlen'=> $iChapter_strlen,
-					];
-
-                    $path = [];
-
-                    $title_data[$iPar]["level"] = $newData["level"];
-                    $title_data[$iPar]["toc"] = $newData["toc"];
-                    $title_data[$iPar]["parent"] = $newData["parent"];
-
-					/*
-                    *获取路径
-                    */
-                    $currParent = $parent;
-                    
-                    $iLoop = 0;
-                    while ($currParent != -1 && $iLoop<7) {
-                        # code...
-                        $pathTitle = $title_data[$currParent-1]["toc"];
-                        $pathLevel = $title_data[$currParent-1]['level'];
-                        $path[] = ["book"=>$book,"paragraph"=>$currParent,"title"=>$pathTitle,"level"=>$pathLevel];
-                        $currParent = $title_data[$currParent-1]["parent"];
-                        $iLoop++;
-                    }
-                    # 将路径反向
-                    $path1 = [];
-                    for ($i=count($path)-1; $i >=0 ; $i--) { 
-                        # code...
-                        $path1[] = $path[$i];
-                    }
-                    $newData['path'] = $path1;
+                $newData = [
+                    'level' => $arrInserString[$iPar][3],
+                    'toc' => $arrInserString[$iPar][5],
+                    'chapter_len' => $length,
+                    'next_chapter' => $next,
+                    'prev_chapter' => $prev,
+                    'parent' => $parent,
+                    'chapter_strlen'=> $iChapter_strlen,
+                ];
+
+                $path = [];
+
+                $title_data[$iPar]["level"] = $newData["level"];
+                $title_data[$iPar]["toc"] = $newData["toc"];
+                $title_data[$iPar]["parent"] = $newData["parent"];
+
+                /*
+                *获取路径
+                */
+                $currParent = $parent;
+
+                $iLoop = 0;
+                while ($currParent != -1 && $iLoop<7) {
+                    # code...
+                    $pathTitle = $title_data[$currParent-1]["toc"];
+                    $pathLevel = $title_data[$currParent-1]['level'];
+                    $path[] = ["book"=>$book,"paragraph"=>$currParent,"title"=>$pathTitle,"level"=>$pathLevel];
+                    $currParent = $title_data[$currParent-1]["parent"];
+                    $iLoop++;
+                }
+                if(count($path)>0){
+                    //插入书名
+                    $bookTitle = BookTitle::where('book',$book)
+                                        ->where('paragraph',end($path)['paragraph'])
+                                        ->value('title');
+                    $path[] = ["book"=>0,"paragraph"=>0,"title"=>$bookTitle,"level"=>0];
+                }
 
+                # 将路径反向
+                $path1 = [];
+                for ($i=count($path)-1; $i >=0 ; $i--) {
+                    # code...
+                    $path1[] = $path[$i];
+                }
+                $newData['path'] = $path1;
 
-					PaliText::where('book',$book)
-							->where('paragraph',$paragraph)
-							->update($newData);
 
-					if ($curr_level > 0 && $curr_level < 8) {
-						$paragraph_info[] = array($book, $paragraph, $length, $prev, $next, $parent);
-					}
-				}
-			}
+                PaliText::where('book',$book)
+                        ->where('paragraph',$paragraph)
+                        ->update($newData);
 
-            
+                if ($curr_level > 0 && $curr_level < 8) {
+                    $paragraph_info[] = array($book, $paragraph, $length, $prev, $next, $parent);
+                }
+            }
 			$bar->advance();
 		}
 		$bar->finish();
-	
-		$this->info("instert pali text finished. in ". time()-$startTime . "s" .PHP_EOL);
+
+		$this->info("instert pali text finished. in ". time()-$startTime . "s" );
 		Log::info("instert pali text finished. in ". time()-$startTime . "s");
         return 0;
     }

+ 64 - 0
app/Console/Commands/UpgradePcdBookId.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\FtsText;
+use App\Models\WbwTemplate;
+use App\Models\BookTitle;
+
+class UpgradePcdBookId extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:pcd.book.id {--table=all}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $table = $this->option('table');
+        $bookTitles = BookTitle::orderBy('id')->get();
+        $bar = $this->output->createProgressBar(count($bookTitles));
+        foreach ($bookTitles as $key => $value) {
+            # code...
+            if($table === 'all' || $table ==='fts'){
+                FtsText::where('book',$value->book)
+                    ->where('paragraph','>=',$value->paragraph)
+                    ->update(['pcd_book_id'=>$value->id]);
+            }
+            if($table === 'all' || $table ==='wbw'){
+                WbwTemplate::where('book',$value->book)
+                    ->where('paragraph','>=',$value->paragraph)
+                    ->update(['pcd_book_id'=>$value->id]);
+            }
+            $bar->advance();
+        }
+        $bar->finish();
+
+        return 0;
+    }
+}

+ 106 - 0
app/Console/Commands/UpgradeRelatedParagraph.php

@@ -0,0 +1,106 @@
+<?php
+/**
+ * 更新段落关联数据库
+ * 用于找到根本和义注复注的对应段落
+ */
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\RelatedParagraph;
+use App\Models\BookTitle;
+use Illuminate\Support\Facades\Log;
+
+class UpgradeRelatedParagraph extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:related.paragraph {book?}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->info("upgrade related.paragraph");
+		$startTime = time();
+        #删除目标数据库中数据
+        RelatedParagraph::where('book','>',0)->delete();
+		// 打开csv文件并读取数据
+        $strFileName = config("app.path.pali_title") . "/cs6_para.csv";
+        if(!file_exists($strFileName)){
+            return 1;
+        }
+        $inputRow = 0;
+        $fp = fopen($strFileName, "r");
+        if (!$fp ) {
+            $this->error("can not open csv $strFileName");
+            Log::error("can not open csv $strFileName");
+        }
+        $bookTitles = BookTitle::orderBy('id','desc')->get();
+
+        while (($data = fgetcsv($fp, 0, ',')) !== false) {
+            if($inputRow>0){
+                if(!empty($this->argument('book'))){
+                    if($this->argument('book') !=$data[0] ){
+                        continue;
+                    }
+                }
+                //获取书号
+                $bookId = 0;
+                foreach ($bookTitles as $bookTitle) {
+                    # code...
+                    if((int)$data[0] === $bookTitle->book){
+                        if((int)$data[1] >= $bookTitle->paragraph){
+                            $bookId = $bookTitle->id;
+                            break;
+                        }
+                    }
+                }
+                $begin = (int) $data[3];
+                $end = (int) $data[4];
+                $arrPara = array();
+                for ($i = $begin; $i <= $end; $i++) {
+                    $arrPara[] = $i;
+                }
+                foreach ($arrPara as $key => $para) {
+                    $newRow = new RelatedParagraph();
+                    $newRow->book = $data[0];
+                    $newRow->para = $data[1];
+                    $newRow->book_id = $bookId;
+                    $newRow->cs_para = $para;
+                    $newRow->book_name = $data[2];
+                    $newRow->save();
+                }
+            }
+            $inputRow++;
+            if($inputRow % 1000 == 0){
+                $this->info($inputRow);
+            }
+        }
+        fclose($fp);
+		$this->info("all done. in ". time()-$startTime . "s" );
+        return 0;
+    }
+}

+ 49 - 0
app/Console/Commands/UpgradeTestData.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * 局部刷新语料库
+ */
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+
+class UpgradeTestData extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:test.data {book}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $this->call('init:cs6sentence',[$this->argument('book')]);
+        $this->call('upgrade:wbw.template',[$this->argument('book')]);
+        $this->call('upgrade:chapter.dynamic.weekly',["--book"=>$this->argument('book'),"--offset"=>300]);
+        $this->call('upgrade:palitext',[$this->argument('book')]);
+        $this->call('upgrade:compound',["--book"=>$this->argument('book')]);
+        return 0;
+    }
+}

+ 16 - 2
app/Console/Commands/UpgradeWbwAnalyses.php

@@ -50,7 +50,7 @@ class UpgradeWbwAnalyses extends Command
         }else{
             $it = Wbw::where('id',$this->argument('id'))->orderby('id')->cursor();
         }
-        
+
         foreach ($it as $wbwrow) {
             $counter++;
             WbwAnalysis::where('wbw_id',$wbwrow->id)->delete();
@@ -64,10 +64,11 @@ class UpgradeWbwAnalyses extends Command
             }catch(Exception $e){
                 continue;
             }
-            
+
             $wordsList = $xmlWord->xpath('//word');
             foreach ($wordsList as $word) {
                 $pali = $word->real->__toString();
+                $factors = [];
                 foreach ($word as $key => $value) {
                     $strValue = $value->__toString();
                     if ($strValue !== "?" && $strValue !== "" && $strValue !== ".ctl." && $strValue !== ".a." && $strValue !== " " && mb_substr($strValue, 0, 3, "UTF-8") !== "[a]" && $strValue !== "_un_auto_factormean_" && $strValue !== "_un_auto_mean_") {
@@ -104,10 +105,23 @@ class UpgradeWbwAnalyses extends Command
                             case 'org':
                                 $newData['type']=4;
                                 WbwAnalysis::insert($newData);
+                                $factors=explode("+",$strValue);
                                 break;
                             case 'om':
                                 $newData['type']=5;
                                 WbwAnalysis::insert($newData);
+                                # 存储拆分意思
+                                $newData['type']=7;
+                                $factorMeaning = explode('+',$strValue);
+                                foreach ( $factors as $index => $factor) {
+                                    if(isset($factorMeaning[$index]) &&
+                                      !empty($factorMeaning[$index]) &&
+                                      $factorMeaning[$index] !== "↓↓" ){
+                                        $newData['wbw_word'] = $factor;
+                                        $newData['data'] = $factorMeaning[$index];
+                                        WbwAnalysis::insert($newData);
+                                    }
+                                }
                                 break;
                             case 'parent':
                                 $newData['type']=6;

+ 11 - 4
app/Console/Commands/UpgradeWbwTemplate.php

@@ -72,13 +72,19 @@ class UpgradeWbwTemplate extends Command
                 $type = $wbw_word->type=='?'? '':$wbw_word->type;
                 $grammar = $wbw_word->gramma=='?'? '':$wbw_word->gramma;
                 $part = $wbw_word->part=='?'? '':$wbw_word->part;
+                if(!empty($type) || !empty($grammar)){
+                    $case = "{$type}#$grammar";
+                }else{
+                    $case = "";
+                }
                 $wbwContent[] = [
+                    'sn'=>[$wbw_word->wid],
                     'word'=>['value'=>$wbw_word->word,'status'=>0],
-                    'real'=> ['value'=>$wbw_word->word,'status'=>0],
-                    'meaning'=> ['value'=>[],'status'=>0],
+                    'real'=> ['value'=>$wbw_word->real,'status'=>0],
+                    'meaning'=> ['value'=>'','status'=>0],
                     'type'=> ['value'=>$type,'status'=>0],
                     'grammar'=> ['value'=>$grammar,'status'=>0],
-                    'case'=> ['value'=>[],'status'=>0],
+                    'case'=> ['value'=>$case,'status'=>0],
                     'style'=> ['value'=>$wbw_word->style,'status'=>0],
                     'factors'=> ['value'=>$part,'status'=>0],
                     'factorMeaning'=> ['value'=>'','status'=>0],
@@ -103,8 +109,9 @@ class UpgradeWbwTemplate extends Command
 				);
             $newRow->editor_uid = config("app.admin.root_uuid");
             $newRow->content = trim($sent);
+            $newRow->content_type = "json";
             $newRow->strlen = mb_strlen($sent,"UTF-8");
-            $newRow->status = 30;
+            $newRow->status = 10;
             $newRow->create_time = time()*1000;
             $newRow->modify_time = time()*1000;
             $newRow->language = 'en';

+ 47 - 0
app/Console/Commands/UpgradeWeekly.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+
+class UpgradeWeekly extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'upgrade:weekly';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '周更';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        # 段落更新图
+        $this->call('upgrade:chapterdynamic');
+        $this->call('upgrade:chapter.dynamic.weekly');
+        $this->call('export:offline');
+
+        return 0;
+    }
+}

+ 72 - 0
app/Console/Commands/UuidViranyani.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use App\Models\Channel;
+use App\Models\Collection;
+use App\Models\DhammaTerm;
+use App\Models\GroupInfo;
+use App\Models\GroupMember;
+use App\Models\SentBlock;
+use App\Models\SentHistory;
+use App\Models\SentPr;
+use App\Models\Sentence;
+use App\Models\Share;
+use App\Models\WbwBlock;
+use App\Models\Wbw;
+
+class UuidViranyani extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'uuid:viranyani';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '修改各个表中的viranyani 的 user_uid 为小写';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $old = "C1AB2ABF-EAA8-4EEF-B4D9-3854321852B4";
+		$result = DB::select('UPDATE "articles" set "owner"=? where "owner"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "channels" set "owner_uid"=? where "owner_uid"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "collections" set "owner"=? where "owner"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "dhamma_terms" set "owner"=? where "owner"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "group_infos" set "owner"=? where "owner"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "group_members" set "user_id"=? where "user_id"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "sent_blocks" set "owner_uid"=? where "owner_uid"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "sent_blocks" set "editor_uid"=? where "editor_uid"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "sent_histories" set "user_uid"=? where "user_uid"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "sent_prs" set "editor_uid"=? where "editor_uid"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "sentences" set "editor_uid"=? where "editor_uid"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "shares" set "cooperator_id"=? where "cooperator_id"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "wbw_blocks" set "creator_uid"=? where "creator_uid"=? ',[strtolower($old),$old]);
+		$result = DB::select('UPDATE "wbws" set "creator_uid"=? where "creator_uid"=? ',[strtolower($old),$old]);
+
+        $this->info('done');
+        return 0;
+    }
+}

+ 1 - 1
app/Console/Kernel.php

@@ -20,7 +20,7 @@ class Kernel extends ConsoleKernel
                  ->emailOutputTo(config("app.email.ScheduleEmailOutputTo"))
 				 ->emailOutputOnFailure(config("app.email.ScheduleEmailOutputOnFailure"));
 
-        $schedule->command('export:offline')
+        $schedule->command('upgrade:weekly')
                  ->weekly()
                  ->emailOutputOnFailure(config("app.email.ScheduleEmailOutputOnFailure"));
     }

+ 3 - 1
app/Http/Api/AuthApi.php

@@ -10,9 +10,11 @@ class AuthApi{
     public static function current(Request $request){
         if($request->hasHeader('Authorization')){
             $token = $request->header('Authorization');
-            Log::info('token:'.$token);
             if(\substr($token,0,6) === 'Bearer'){
                 $token = trim(substr($token,6));
+                if($token === "null"){
+                    return false;
+                }
                 $jwt = JWT::decode($token,new Key(env('APP_KEY'),'HS512'));
                 if($jwt->exp < time()){
                     return false;

+ 12 - 1
app/Http/Api/ChannelApi.php

@@ -10,6 +10,7 @@ class ChannelApi{
                     'id'=>$id,
                     'name'=>$channel['name'],
                     'type'=>$channel['type'],
+                    'lang'=>$channel['lang'],
                     'studio_id'=>$channel['owner_uid'],
                 ];
         }else{
@@ -19,11 +20,21 @@ class ChannelApi{
     public static function getListByUser(){
 
     }
-    public static function getSysChannel($channel_name){
+    public static function getSysChannel($channel_name,$fallback=""){
         $channel=  Channel::where('name',$channel_name)
                     ->where('owner_uid',config("app.admin.root_uuid"))
                     ->first();
         if(!$channel){
+            if(!empty($fallback)){
+                $channel = Channel::where('name',$fallback)
+                                  ->where('owner_uid',config("app.admin.root_uuid"))
+                                  ->first();
+                if(!$channel){
+                    return false;
+                }else{
+                    return $channel->uid;
+                }
+            }
             return false;
         }else{
             return $channel->uid;

+ 65 - 0
app/Http/Api/DictApi.php

@@ -3,6 +3,71 @@ namespace App\Http\Api;
 use App\Models\DictInfo;
 
 class DictApi{
+    public static function langOrder(string $lang){
+        switch ($lang) {
+            case 'zh':
+                $output = ["zh","jp","en","my"];
+                break;
+            case 'en':
+                $output = ["en","my"];
+                break;
+            case 'my':
+                $output = ["my","en"];
+                break;
+            default:
+                $output = [$lang,"en","my"];
+                break;
+        }
+        $output[] = "others";
+        return $output;
+    }
+
+    public static function dictOrder($lang){
+        $output = [];
+        switch ($lang) {
+            case 'zh':
+                $output = [
+                "0d79e8e8-1430-4c99-a0f1-b74f2b4b26d8",	/*《巴汉词典》增订*/
+                "f364d3dc-b611-471b-9a4f-531286b8c2c3",	/*《巴汉词典》Mahāñāṇo Bhikkhu编著*/
+                "0e4dc5c8-a228-4693-92ba-7d42918d8a91",	/*汉译パーリ语辞典-黃秉榮*/
+                "6aa9ec8b-bba4-4bcd-abd2-9eae015bad2b",	/*汉译パーリ语辞典-李瑩*/
+                "eb99f8b4-c3e5-43af-9102-6a93fcb97db6",	/*パーリ语辞典--勘误表*/
+                ];
+                break;
+            case 'jp':
+                $output = [
+                "91d3ec93-3811-4973-8d84-ced99179a0aa",	/*パーリ语辞典*/
+                "6d6c6812-75e7-457d-874f-5b049ad4b6de",	/*パーリ语辞典-增补*/
+                ];
+                break;
+            case 'en':
+                $output = [
+                "c6e70507-4a14-4687-8b70-2d0c7eb0cf21",	/*	Concise P-E Dict*/
+                "6afb8c05-5cbe-422e-b691-0d4507450cb7",	/*	PTS P-E dictionary*/
+                ];
+                break;
+            case 'my':
+                $output =[
+                "e740ef40-26d7-416e-96c2-925d6650ac6b",	/*	Tipiṭaka Pāḷi-Myanmar*/
+                "beb45062-7c20-4047-bcd4-1f636ba443d1",	/*	U Hau Sein’s Pāḷi-Myanmar Dictionary*/
+                "1e299ccb-4fc4-487d-8d72-08f63d84c809",	/*	Pali Roots Dictionary*/
+                "6f9caea1-17fa-41f1-92e5-bd8e6e70e1d7",	/*	U Hau Sein’s Pāḷi-Myanmar*/
+                ];
+                break;
+            case 'vi':
+                $output = [
+                "23f67523-fa03-48d9-9dda-ede80d578dd2",	/*	Pali Viet Dictionary*/
+                "4ac8a0d5-9c6f-4b9f-983d-84288d47f993",	/*	Pali Viet Abhi-Terms*/
+                "7c7ee287-35ba-4cf3-b87b-30f1fa6e57c9",	/*	Pali Viet Vinaya Terms*/
+                ];
+                break;
+            default:
+                $output = [];
+                break;
+        };
+        $output[] = "others";
+        return $output;
+    }
     public static function getSysDict($name){
         $dict_info=  DictInfo::where('name',$name)
                     ->where('owner_id',config("app.admin.root_uuid"))

+ 18 - 0
app/Http/Api/GroupApi.php

@@ -0,0 +1,18 @@
+<?php
+namespace App\Http\Api;
+use App\Models\GroupInfo;
+
+class GroupApi{
+    public static function getById($id){
+        $group = GroupInfo::where("uid",$id)->first();
+        if($group){
+            return [
+                    'id'=>$id,
+                    'name'=>$group->name,
+                ];
+        }else{
+            return false;
+        }
+    }
+
+}

+ 84 - 12
app/Http/Api/MdRender.php

@@ -5,6 +5,7 @@ use Illuminate\Support\Str;
 use mustache\mustache;
 use App\Models\DhammaTerm;
 use App\Models\PaliText;
+use App\Models\Channel;
 use App\Http\Controllers\CorpusController;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Log;
@@ -36,7 +37,12 @@ class MdRender{
         return $html;
     }
     public static function xmlQueryId(string $xml, string $id):string{
-        $dom = simplexml_load_string($xml);
+        try{
+            $dom = simplexml_load_string($xml);
+        }catch(\Exception $e){
+            Log::error($e);
+            return "<div></div>";
+        }
         $tpl_list = $dom->xpath('//MdTpl');
         foreach ($tpl_list as $key => $tpl) {
             foreach ($tpl->children() as  $param) {
@@ -57,7 +63,12 @@ class MdRender{
     }
     public static function take_sentence(string $xml):array{
         $output = [];
-        $dom = simplexml_load_string($xml);
+        try{
+            $dom = simplexml_load_string($xml);
+        }catch(\Exception $e){
+            Log::error($e);
+            return $output;
+        }
         $tpl_list = $dom->xpath('//MdTpl');
         foreach ($tpl_list as $key => $tpl) {
             foreach($tpl->attributes() as $a => $a_value){
@@ -85,8 +96,15 @@ class MdRender{
          * 获取模版参数
          * 生成react 组件参数
          */
-        //$xml = str_replace(['<b>','</b>'],['',''],$xml);
-        $dom = simplexml_load_string($xml);
+        try{
+            $dom = simplexml_load_string($xml);
+        }catch(\Exception $e){
+            Log::error($e);
+            Log::error($xml);
+            return "<span>xml解析错误{$e}</span>";
+        }
+
+        $channelInfo = Channel::find($channelId);
 
         $tpl_list = $dom->xpath('//MdTpl');
         foreach ($tpl_list as $key => $tpl) {
@@ -116,7 +134,7 @@ class MdRender{
             /**
              * 生成模版参数
              */
-            $tplRender = new TemplateRender($props,$channelId,$mode);
+            $tplRender = new TemplateRender($props,$channelInfo,$mode);
             $tplProps = $tplRender->render($tpl_name);
             if($tplProps){
                 $tpl->addAttribute("props",$tplProps['props']);
@@ -129,16 +147,62 @@ class MdRender{
         return $html;
     }
 
-    public static function render2($markdown,$channelId='',$queryId=null,$mode='read'){
-        $wiki = MdRender::markdown2wiki($markdown);
+    public static function render2($markdown,$channelId='',$queryId=null,$mode='read',$channelType,$contentType="markdown"){
+        $wiki = MdRender::markdown2wiki($markdown,$channelType,$contentType);
         $html = MdRender::wiki2xml($wiki);
         if(!is_null($queryId)){
             $html = MdRender::xmlQueryId($html, $queryId);
         }
         $tpl = MdRender::xml2tpl($html,$channelId,$mode);
+        //生成可展开组件
+        $tpl = str_replace("<div/>","<div></div>",$tpl);
+        $pattern = '/<li><div>(.+?)<\/div><\/li>/';
+        $replacement = '<li><MdTpl name="toggle" tpl="toggle" props=""><div>$1</div></MdTpl></li>';
+        $tpl = preg_replace($pattern,$replacement,$tpl);
         return $tpl;
     }
-    public static function markdown2wiki(string $markdown): string{
+    public static function markdown2wiki(string $markdown,$channelType,$contentType): string{
+                /**
+         * nissaya
+         * aaa=bbb\n
+         * {{nissaya|aaa|bbb}}
+         */
+        if($channelType==='nissaya'){
+            if($contentType === "json"){
+                $json = json_decode($markdown);
+                $nissayaWord = [];
+                foreach ($json as $word) {
+                    if(count($word->sn) === 1){
+                        //只输出第一层级
+                        $str = "{{nissaya|";
+                        if(isset($word->word->value)){
+                            $str .= $word->word->value;
+                        }
+                        $str .= "|";
+                        if(isset($word->meaning->value)){
+                            $str .= $word->meaning->value;
+                        }
+                        $str .= "}}";
+                        $nissayaWord[] = $str;
+                    }
+
+                }
+                $markdown = implode('',$nissayaWord);
+            }else{
+                $pattern = '/(.+?)=(.+?)\n/';
+                $replacement = '{{nissaya|$1|$2}}';
+                $markdown = preg_replace($pattern,$replacement,$markdown);
+                $pattern = '/(.+?)=(.?)\n/';
+                $replacement = '{{nissaya|$1|$2}}';
+                $markdown = preg_replace($pattern,$replacement,$markdown);
+                $pattern = '/(.?)=(.+?)\n/';
+                $replacement = '{{nissaya|$1|$2}}';
+                $markdown = preg_replace($pattern,$replacement,$markdown);
+            }
+        }
+        //$markdown = preg_replace("/\n\n/","<div></div>",$markdown);
+
+
         /**
          * 替换换行符
          * react 无法处理 <br> 替换为<div></div>代替换行符作用
@@ -160,20 +224,28 @@ class MdRender{
         $replacement = '{{sent|$1}}';
         $html = preg_replace($pattern,$replacement,$html);
 
-        #替换注释
+        #替换单行注释
         #<code>bla</code>
-        #{{note:bla}}
+        #{{note|bla}}
         $pattern = '/<code>(.+?)<\/code>/';
         $replacement = '{{note|$1}}';
         $html = preg_replace($pattern,$replacement,$html);
+
+        #替换多行注释
+        #<pre><code>bla</code></pre>
+        #{{note|bla}}
+        $pattern = '/<pre><code>([\w\W]+?)<\/code><\/pre>/';
+        $replacement = '{{note|$1}}';
+        $html = preg_replace($pattern,$replacement,$html);
+
         return $html;
     }
 
     /**
      *
      */
-    public static function render($markdown,$channelId,$queryId=null,$mode='read'){
-        return MdRender::render2($markdown,$channelId,$queryId,$mode);
+    public static function render($markdown,$channelId,$queryId=null,$mode='read',$channelType='translation',$contentType="markdown"){
+        return MdRender::render2($markdown,$channelId,$queryId,$mode,$channelType,$contentType);
     }
 
 }

+ 44 - 7
app/Http/Api/ShareApi.php

@@ -5,13 +5,22 @@ use App\Models\Share;
 use App\Models\Article;
 use App\Models\Channel;
 use App\Models\Collection;
+use App\Http\Api\ChannelApi;
 
 class ShareApi{
 
-    /*
-    获取某用户的可见的协作资源
-    $res_type 见readme.md#资源类型 -1全部类型资源
-    */
+    /**
+     * 获取某用户的可见的协作资源
+     * $res_type 见readme.md#资源类型 -1全部类型资源
+     * ## 资源类型
+     *  1 PCS 文档
+     *  2 Channel 版本
+     *  3 Article 文章
+     *  4 Collection 文集
+     *  5 版本片段
+     * power 权限 10: 只读  20:编辑 30: 拥有者
+     */
+
     public static function getResList($user_uid,$res_type=-1){
         # 找我加入的群
         $my_group = GroupMember::where("user_id",$user_uid)->select('group_id')->get();
@@ -111,13 +120,41 @@ class ShareApi{
     /**
      * 获取对某个共享资源的权限
      */
-    public static function getResPower($user_uid,$res_id){
-            if($userid==='0'){
+    public static function getResPower($user_uid,$res_id,$res_type=0){
+            if(empty($user_uid)){
                 #未登录用户 没有共享资源
                 return 0;
             }
+            //查看是否为资源拥有者
+            if($res_type!=0){
+                switch ($res_type) {
+                    case 2:
+                        # channel
+                        $channel = ChannelApi::getById($res_id);
+                        if($channel){
+                            if($channel['studio_id'] === $user_uid){
+                                return 30;
+                            }
+                        }
+                        break;
+                    case 3:
+                        //Article
+                        $owner = Article::where('uid',$res_id)->value('owner');
+                        if($owner === $user_uid){
+                            return 30;
+                        }
+                        break;
+                    case 4:
+                        $owner = Collection::where('uid',$res_id)->value('owner');
+                        if($owner === $user_uid){
+                            return 30;
+                        }
+                        //文集
+                        break;
+                }
+            }
             # 找我加入的群
-            $my_group = Group::where("user_id",$user_uid)->select('group_id')->get();
+            $my_group = GroupMember::where("user_id",$user_uid)->select('group_id')->get();
             $userList[] = $user_uid;
             foreach ($my_group as $key => $value) {
                 $userList[]=$value["group_id"];

+ 3 - 0
app/Http/Api/StudioApi.php

@@ -5,6 +5,9 @@ require_once __DIR__.'/../../../public/app/ucenter/function.php';
 
 class StudioApi{
     public static function getIdByName($name){
+        /**
+         * 获取 uuid
+         */
         //TODO 改为studio table
         if(empty($name)){
             return false;

+ 14 - 0
app/Http/Api/SuggestionApi.php

@@ -2,6 +2,8 @@
 namespace App\Http\Api;
 
 use App\Models\SentPr;
+use App\Models\Discussion;
+use App\Models\Sentence;
 use App\Http\Api\PaliTextApi;
 
 class SuggestionApi{
@@ -12,6 +14,18 @@ class SuggestionApi{
                                     ->where('word_end',$end)
                                     ->where('channel_uid',$channel)
                                     ->count();
+        $sentId = Sentence::where('book_id',$book)
+                            ->where('paragraph',$para)
+                            ->where('word_start',$start)
+                            ->where('word_end',$end)
+                            ->where('channel_uid',$channel)
+                            ->value('uid');
+        if($sentId){
+            $count['discussion'] = Discussion::where('res_id',$sentId)
+                                            ->whereNull('parent')
+                                            ->count();
+        }
+
         return $count;
     }
 }

+ 45 - 6
app/Http/Api/TemplateRender.php

@@ -17,10 +17,11 @@ class TemplateRender{
      * int $mode  'read' | 'edit'
      * @return void
      */
-    public function __construct($param, $channel_id, $mode)
+    public function __construct($param, $channelInfo, $mode)
     {
         $this->param = $param;
-        $this->channel_id = $channel_id;
+        $this->channel_id = $channelInfo->uid;
+        $this->channelInfo = $channelInfo;
         $this->mode = $mode;
     }
 
@@ -45,6 +46,9 @@ class TemplateRender{
             case 'article':
                 $result = $this->render_article();
                 break;
+            case 'nissaya':
+                $result = $this->render_nissaya();
+                break;
             default:
                 # code...
                 $result = [
@@ -61,22 +65,39 @@ class TemplateRender{
     private function render_term(){
         $word = $this->get_param($this->param,"word",1);
         $channelId = $this->channel_id;
+        $channelInfo = $this->channelInfo;
         $props = Cache::remember("/term/{$this->channel_id}/{$word}",
               60,
-              function() use($word,$channelId){
-                $tplParam = DhammaTerm::where("word",$word)->first();
+              function() use($word,$channelId,$channelInfo){
+                //先查属于这个channel 的
+                $tplParam = DhammaTerm::where("word",$word)->where('channal',$channelId)->first();
+                if(!$tplParam){
+                    //没有,再查这个studio的
+                    $tplParam = DhammaTerm::where("word",$word)
+                                          ->where('owner',$channelInfo->owner_uid)
+                                          ->first();
+                }
                 $output = [
                     "word" => $word,
-                    "channel" => $channelId,
+                    "parentChannelId" => $channelId,
+                    "parentStudioId" => $channelInfo->owner_uid,
                     ];
                     $innerString = $output["word"];
                 if($tplParam){
                     $output["id"] = $tplParam->guid;
                     $output["meaning"] = $tplParam->meaning;
+                    $output["channel"] = $tplParam->channal;
                     $innerString = "{$output["meaning"]}({$output["word"]})";
                     if(!empty($tplParam->other_meaning)){
                         $output["meaning2"] = $tplParam->other_meaning;
                     }
+                    if($tplParam->note){
+                        $output["summary"] = $tplParam->note;
+                    }else{
+                        //使用社区note
+                        //获取channel 语言
+                        //查找社区解释
+                    }
                 }
                 $output['innerHtml'] = $innerString;
                 return $output;
@@ -105,7 +126,22 @@ class TemplateRender{
             'tpl'=>'note',
             ];
     }
+    private  function render_nissaya(){
 
+        $pali =  $this->get_param($this->param,"pali",1);
+        $meaning = $this->get_param($this->param,"meaning",2);
+        $innerString = "";
+        $props = [
+            "pali" => $pali,
+            "meaning" => $meaning,
+        ];
+        return [
+            'props'=>base64_encode(\json_encode($props)),
+            'html'=>$innerString,
+            'tag'=>'span',
+            'tpl'=>'nissaya',
+            ];
+    }
     private  function render_exercise(){
 
         $id = $this->get_param($this->param,"id",1);
@@ -202,6 +238,9 @@ class TemplateRender{
         }
         $Sent = new CorpusController();
         $props = $Sent->getSentTpl($sentId,$channels,$this->mode,true);
+        if($props === false){
+            $props['error']="句子模版渲染错误。句子参数个数不符。应该是四个。";
+        }
         if($this->mode==='read'){
             $tpl = "sentread";
         }else{
@@ -219,7 +258,7 @@ class TemplateRender{
         if(isset($param[$name])){
             return trim($param[$name]);
         }else if(isset($param["{$id}"])){
-            return trim($param["1"]);
+            return trim($param["{$id}"]);
         }else{
             return $default;
         }

+ 18 - 0
app/Http/Api/UserApi.php

@@ -1,5 +1,6 @@
 <?php
 namespace App\Http\Api;
+use App\Models\UserInfo;
 
 require_once __DIR__.'/../../../public/app/ucenter/function.php';
 
@@ -8,6 +9,13 @@ class UserApi{
         $userinfo = new \UserInfo();
         return $userinfo->getUserByName($name)['userid'];
     }
+    public static function getIdByUuid($uuid){
+        return UserInfo::where('userid',$uuid)->value('id');
+    }
+    public static function getIntIdByName($name){
+        $userinfo = new \UserInfo();
+        return $userinfo->getUserByName($name)['id'];
+    }
     public static function getById($id){
         $userinfo = new \UserInfo();
         $studio = $userinfo->getName($id);
@@ -18,4 +26,14 @@ class UserApi{
             'avatar'=>'',
         ];
     }
+    public static function getByUuid($id){
+        $userinfo = new \UserInfo();
+        $studio = $userinfo->getName($id);
+        return [
+            'id'=>$id,
+            'nickName'=>$studio['nickname'],
+            'userName'=>$studio['username'],
+            'avatar'=>'',
+        ];
+    }
 }

+ 263 - 84
app/Http/Controllers/ArticleController.php

@@ -3,14 +3,117 @@
 namespace App\Http\Controllers;
 
 use App\Models\Article;
+use App\Models\ArticleCollection;
+use App\Models\Collection;
+
 use Illuminate\Http\Request;
 use Illuminate\Support\Str;
 use App\Http\Resources\ArticleResource;
 use App\Http\Api\AuthApi;
+use App\Http\Api\ShareApi;
+use App\Http\Api\StudioApi;
 use Illuminate\Support\Facades\DB;
 
 class ArticleController extends Controller
 {
+    public static function userCanRead($user_uid,Article $article){
+        if($article->status === 30 ){
+            return true;
+        }
+        if(empty($user_uid)){
+            return false;
+        }
+            //私有文章,判断是否为所有者
+        if($user_uid === $article->owner){
+            return true;
+        }
+        //非所有者
+        //判断是否为文章协作者
+        $power = ShareApi::getResPower($user_uid,$article->uid);
+        if($power >= 10 ){
+            return true;
+        }
+        //无读取权限
+        //判断文集是否有读取权限
+        $inCollection = ArticleCollection::where('article_id',$article->uid)
+                                        ->select('collect_id')
+                                        ->groupBy('collect_id')->get();
+        if(!$inCollection){
+            return false;
+        }
+        //查找与文章同主人的文集
+        $collections = Collection::whereIn('uid',$inCollection)
+                                    ->where('owner',$article->owner)
+                                    ->select('uid')
+                                    ->get();
+        if(!$collections){
+            return false;
+        }
+        //查找与文章同主人的文集是否是共享的
+        $power = 0;
+        foreach ($collections as $collection) {
+            # code...
+            $currPower = ShareApi::getResPower($user_uid,$collection->uid);
+            if($currPower >= 10){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static function userCanEdit($user_uid,$article){
+        if(empty($user_uid)){
+            return false;
+        }
+        //私有文章,判断是否为所有者
+        if($user_uid === $article->owner){
+            return true;
+        }
+        //非所有者
+        //判断是否为文章协作者
+        $power = ShareApi::getResPower($user_uid,$article->uid);
+        if($power >= 20 ){
+            return true;
+        }
+        //无读取权限
+        //判断文集是否有读取权限
+        $inCollection = ArticleCollection::where('article_id',$article->uid)
+                                        ->select('collect_id')
+                                        ->groupBy('collect_id')->get();
+        if(!$inCollection){
+            return false;
+        }
+        //查找与文章同主人的文集
+        $collections = Collection::whereIn('uid',$inCollection)
+                                    ->where('owner',$article->owner)
+                                    ->select('uid')
+                                    ->get();
+        if(!$collections){
+            return false;
+        }
+        //查找与文章同主人的文集是否是共享的
+        $power = 0;
+        foreach ($collections as $collection) {
+            # code...
+            $currPower = ShareApi::getResPower($user_uid,$collection->uid);
+            if($currPower >= 20){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static function userCanManage($user_uid,$studioName){
+        if(empty($user_uid)){
+            return false;
+        }
+        //判断是否为所有者
+        if($user_uid === StudioApi::getIdByName($studioName)){
+            return true;
+        }else{
+            return false;
+        }
+    }
     /**
      * Display a listing of the resource.
      *
@@ -24,15 +127,47 @@ class ArticleController extends Controller
             case 'studio':
 				# 获取studio内所有channel
                 $user = \App\Http\Api\AuthApi::current($request);
-                if($user){
-                    //判断当前用户是否有指定的studio的权限
-                    if($user['user_uid'] === \App\Http\Api\StudioApi::getIdByName($request->get('name'))){
-                        $table = Article::select($indexCol)->where('owner', $user["user_uid"]);
-                    }else{
-                        return $this->error(__('auth.failed'));
-                    }
-                }else{
+                if(!$user){
                     return $this->error(__('auth.failed'));
+                }
+                //判断当前用户是否有指定的studio的权限
+                $studioId = StudioApi::getIdByName($request->get('name'));
+                if($user['user_uid'] !== $studioId){
+                    return $this->error(__('auth.failed'));
+                }
+                $table = Article::select($indexCol);
+                if($request->get('view2','my')==='my'){
+                    $table = $table->where('owner', $studioId);
+                }else{
+                    //协作
+                    $resList = ShareApi::getResList($studioId,3);
+                    $resId=[];
+                    foreach ($resList as $res) {
+                        $resId[] = $res['res_id'];
+                    }
+                    $table = $table->whereIn('uid', $resId)->where('owner','<>', $studioId);
+                }
+
+                //根据anthology过滤
+                if($request->has('anthology')){
+                    switch ($request->get('anthology')) {
+                        case 'all':
+                            break;
+                        case 'none':
+                            # 我的文集
+                            $myCollection = Collection::where('owner',$studioId)->select('uid')->get();
+                            //收录在我的文集里面的文章
+                            $articles = ArticleCollection::whereIn('collect_id',$myCollection)
+                                                         ->select('article_id')->groupBy('article_id')->get();
+                            //不在这些范围之内的文章
+                            $table =  $table->whereNotIn('uid',$articles);
+                            break;
+                        default:
+                            $articles = ArticleCollection::where('collect_id',$request->get('anthology'))
+                                                         ->select('article_id')->get();
+                            $table =  $table->whereIn('uid',$articles);
+                            break;
+                    }
                 }
 				break;
         }
@@ -62,29 +197,40 @@ class ArticleController extends Controller
         //获取数据
         $result = $table->get();
         if($result){
-            /*
-            foreach ($result as $key => $value) {
-                # 获取studio信息
-                $studio = $userinfo->getName($value->owner_uid);
-                $value->studio = [
-                    'id'=>$value->owner_uid,
-                    'nickName'=>$studio['nickname'],
-                    'studioName'=>$studio['username'],
-                    'avastar'=>'',
-                    'owner' => [
-                        'id'=>$value->owner_uid,
-                        'nickName'=>$studio['nickname'],
-                        'userName'=>$studio['username'],
-                        'avastar'=>'',
-                    ]
-                ];
-            }*/
-			return $this->ok(["rows"=>$result,"count"=>$count]);
+			return $this->ok(["rows"=>ArticleResource::collection($result),"count"=>$count]);
 		}else{
 			return $this->error("没有查询到数据");
 		}
     }
 
+        /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function showMyNumber(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //判断当前用户是否有指定的studio的权限
+        $studioId = StudioApi::getIdByName($request->get('studio'));
+        if($user['user_uid'] !== $studioId){
+            return $this->error(__('auth.failed'));
+        }
+        //我的
+        $my = Article::where('owner', $studioId)->count();
+        //协作
+        $resList = ShareApi::getResList($studioId,3);
+        $resId=[];
+        foreach ($resList as $res) {
+            $resId[] = $res['res_id'];
+        }
+        $collaboration = Article::whereIn('uid', $resId)->where('owner','<>', $studioId)->count();
+
+        return $this->ok(['my'=>$my,'collaboration'=>$collaboration]);
+    }
+
     /**
      * Store a newly created resource in storage.
      *
@@ -93,35 +239,56 @@ class ArticleController extends Controller
      */
     public function store(Request $request)
     {
-        //
-        $user = \App\Http\Api\AuthApi::current($request);
-        if($user){
-            //判断当前用户是否有指定的studio的权限
-            if($user['user_uid'] === \App\Http\Api\StudioApi::getIdByName($request->get('studio'))){
-                //查询是否重复
-                if(Article::where('title',$request->get('title'))->where('owner',$user['user_uid'])->exists()){
-                    return $this->error(__('validation.exists'));
-                }else{
+        //判断权限
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'),[],401);
+        }else{
+            $user_uid=$user['user_uid'];
+        }
 
-                    $newOne = new Article;
-                    $newOne->id = app('snowflake')->id();
-                    $newOne->uid = Str::uuid();
-                    $newOne->title = $request->get('title');
-                    $newOne->lang = $request->get('lang');
-                    $newOne->owner = $user['user_uid'];
-                    $newOne->owner_id = $user['user_id'];
-                    $newOne->editor_id = $user['user_id'];
-                    $newOne->create_time = time()*1000;
-                    $newOne->modify_time = time()*1000;
-                    $newOne->save();
-                    return $this->ok($newOne);
-                }
-            }else{
-                return $this->error(__('auth.failed'));
+        $canManage = ArticleController::userCanManage($user_uid,$request->get('studio'));
+        if(!$canManage){
+            return $this->error(__('auth.failed'),[],403);
+        }
+        //权限判断结束
+
+        //查询标题是否重复
+        /*
+        if(Article::where('title',$request->get('title'))->where('owner',$studioUuid)->exists()){
+            return $this->error(__('validation.exists'));
+        }*/
+        $newArticle = new Article;
+        DB::transaction(function() use($user,$request,$newArticle){
+            $studioUuid = StudioApi::getIdByName($request->get('studio'));
+            //新建文章,加入文集必须都成功。否则回滚
+            $newArticle->id = app('snowflake')->id();
+            $newArticle->uid = Str::uuid();
+            $newArticle->title = $request->get('title');
+            $newArticle->lang = $request->get('lang');
+            $newArticle->owner = $studioUuid;
+            $newArticle->owner_id = $user['user_id'];
+            $newArticle->editor_id = $user['user_id'];
+            $newArticle->create_time = time()*1000;
+            $newArticle->modify_time = time()*1000;
+            $newArticle->save();
+
+            if(Str::isUuid($request->get('anthologyId'))){
+                $articleMap = new ArticleCollection();
+                $articleMap->id = app('snowflake')->id();
+                $articleMap->article_id = $newArticle->uid;
+                $articleMap->collect_id = $request->get('anthologyId');
+                $articleMap->title = Article::find($newArticle->uid)->title;
+                $articleMap->level = 1;
+                $articleMap->save();
             }
+        });
+        if(Str::isUuid($newArticle->uid)){
+            return $this->ok($newArticle);
         }else{
-            return $this->error(__('auth.failed'));
+            return $this->error('fail');
         }
+
     }
 
     /**
@@ -136,19 +303,17 @@ class ArticleController extends Controller
         if(!$article){
             return $this->error("no recorder");
         }
-        if($article->status<30){
-            //私有文章,判断权限
-            $user = \App\Http\Api\AuthApi::current($request);
-            if(!$user){
-                //判断当前用户是否有指定的studio的权限
-                return $this->error(__('auth.failed'));
-            }
-            if($user['user_uid'] !== $article->owner){
-                //非所有者
-                return $this->error(__('auth.failed'));
-            }else{
-                //TODO 判断是否协作
-            }
+        //判断权限
+        $user = AuthApi::current($request);
+        if(!$user){
+            $user_uid="";
+        }else{
+            $user_uid=$user['user_uid'];
+        }
+
+        $canRead = ArticleController::userCanRead($user_uid,$article);
+        if(!$canRead){
+            return $this->error(__('auth.failed'),[],401);
         }
         return $this->ok(new ArticleResource($article));
     }
@@ -163,28 +328,41 @@ class ArticleController extends Controller
     public function update(Request $request, Article $article)
     {
         //
-        if($article){
-            //鉴权
-            $user = \App\Http\Api\AuthApi::current($request);
-            if($user && $article->owner === $user["user_uid"]){
-                $article->title = $request->get('title');
-                $article->subtitle = $request->get('subtitle');
-                $article->summary = $request->get('summary');
-                $article->content = $request->get('content');
-                $article->lang = $request->get('lang');
-                $article->status = $request->get('status');
-                $article->modify_time = time()*1000;
-                $article->save();
-                return $this->ok($article);
-            }else{
-                //鉴权失败
-                //TODO 判断是否为协作
-                return $this->error(__('auth.failed'));
-            }
-
-        }else{
+        if(!$article){
             return $this->error("no recorder");
         }
+        //鉴权
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'),[],401);
+        }else{
+            $user_uid=$user['user_uid'];
+        }
+
+        $canEdit = ArticleController::userCanEdit($user_uid,$article);
+        if(!$canEdit){
+            return $this->error(__('auth.failed'),[],401);
+        }
+
+        /*
+        //查询标题是否重复
+        if(Article::where('title',$request->get('title'))
+                  ->where('owner',$article->owner)
+                  ->where('uid',"<>",$article->uid)
+                  ->exists()){
+            return $this->error(__('validation.exists'));
+        }*/
+
+        $article->title = $request->get('title');
+        $article->subtitle = $request->get('subtitle');
+        $article->summary = $request->get('summary');
+        $article->content = $request->get('content');
+        $article->lang = $request->get('lang');
+        $article->status = $request->get('status',10);
+        $article->editor_id = $user['user_id'];
+        $article->modify_time = time()*1000;
+        $article->save();
+        return $this->ok($article);
 
     }
 
@@ -209,6 +387,7 @@ class ArticleController extends Controller
         DB::transaction(function() use($article,$delete){
             //TODO 删除文集中的文章
             $delete = $article->delete();
+            ArticleMapController::deleteArticle($article->uid);
         });
 
         return $this->ok($delete);

+ 51 - 3
app/Http/Controllers/ArticleMapController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
 
 use App\Models\ArticleCollection;
 use App\Models\Article;
+use App\Models\Collection;
 
 use Illuminate\Http\Request;
 use App\Http\Resources\ArticleMapResource;
@@ -26,7 +27,8 @@ class ArticleMapController extends Controller
                 $table = ArticleCollection::where('article_id',$request->get('id'));
                 break;
         }
-        $result = $table->select(['id','collect_id','article_id','level','title','children'])->orderBy('id')->get();
+        $result = $table->select(['id','collect_id','article_id','level','title','children','deleted_at'])
+                        ->orderBy('id')->get();
         return $this->ok(["rows"=>ArticleMapResource::collection($result),"count"=>count($result)]);
     }
 
@@ -45,7 +47,7 @@ class ArticleMapController extends Controller
             ]);
         switch ($validated['operation']) {
             case 'add':
-                # code...
+                # 添加多个文章到文集
                 $count=0;
                 foreach ($request->get('article_id') as $key => $article) {
                     # code...
@@ -67,7 +69,7 @@ class ArticleMapController extends Controller
                 return $this->ok($count);
                 break;
             default:
-                # code...
+                return $this->error('unknown operation');
                 break;
         }
     }
@@ -112,6 +114,7 @@ class ArticleMapController extends Controller
                     $new->save();
                     $count++;
                 }
+                ArticleMapController::updateCollection($id);
                 return $this->ok($count);
                 break;
         }
@@ -127,4 +130,49 @@ class ArticleMapController extends Controller
     {
         //
     }
+
+    public static function deleteArticle(string $articleId){
+        //查找有这个文章的文集
+        $collections = ArticleCollection::where('article_id',$articleId)
+                                        ->select('collect_id')
+                                        ->groupBy('collect_id')
+                                        ->get();
+        //设置为删除
+        ArticleCollection::where('article_id',$articleId)
+                         ->update(['deleted_at'=>now()]);
+        //查找没有下级文章的文集
+        $updateCollections = ArticleCollection::where('article_id',$articleId)
+                                            ->where('children',0)
+                                            ->select('collect_id')
+                                            ->groupBy('collect_id')
+                                            ->get();
+        //真的删除没有下级文章的文集中的文章
+        $count = ArticleCollection::where('article_id',$articleId)
+                                  ->where('children',0)
+                                  ->delete();
+        //更新改动的文集
+        foreach ($updateCollections as  $collection) {
+            # code...
+            ArticleMapController::updateCollection($collection->collect_id);
+        }
+        return [count($collections),$count];
+    }
+
+    public static function deleteCollection(string $collectionId){
+        $count = ArticleCollection::where('collect_id',$collectionId)
+                                  ->delete();
+        return $count;
+    }
+
+    /**
+     * 用表中的数据生成json,更新collection 表中的字段
+     */
+    public static function updateCollection(string $collectionId){
+        $result = ArticleCollection::where('collect_id',$collectionId)
+                        ->select(['article_id','level','title'])
+                        ->orderBy('id')->get();
+        Collection::where('uid',$collectionId)
+                  ->update(['article_list'=>json_encode($result,JSON_UNESCAPED_UNICODE)]);
+        return count($result);
+    }
 }

+ 256 - 56
app/Http/Controllers/ChannelController.php

@@ -36,17 +36,59 @@ class ChannelController extends Controller
             case 'studio':
 				# 获取studio内所有channel
                 $user = AuthApi::current($request);
-                if($user){
-                    //判断当前用户是否有指定的studio的权限
-                    if($user['user_uid'] === \App\Http\Api\StudioApi::getIdByName($request->get('name'))){
-                        $table = Channel::select($indexCol)->where('owner_uid', $user["user_uid"]);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                //判断当前用户是否有指定的studio的权限
+                $studioId = StudioApi::getIdByName($request->get('name'));
+                if($user['user_uid'] !== $studioId){
+                    return $this->error(__('auth.failed'));
+                }
+
+                $table = Channel::select($indexCol);
+                if($request->get('view2','my')==='my'){
+                    $table = $table->where('owner_uid', $studioId);
+                }else{
+                    //协作
+                    $resList = ShareApi::getResList($studioId,2);
+                    $resId=[];
+                    foreach ($resList as $res) {
+                        $resId[] = $res['res_id'];
+                    }
+                    $table = $table->whereIn('uid', $resId);
+                    if($request->get('collaborator','all') !== 'all'){
+                        $table = $table->where('owner_uid', $request->get('collaborator'));
                     }else{
-                        return $this->error(__('auth.failed'));
+                        $table = $table->where('owner_uid','<>', $studioId);
                     }
-                }else{
-                    return $this->error(__('auth.failed'));
                 }
 				break;
+            case 'studio-all':
+                /**
+                 * studio 的和协作的
+                 */
+                #获取user所有有权限的channel列表
+                $user = AuthApi::current($request);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                //判断当前用户是否有指定的studio的权限
+                if($user['user_uid'] !== \App\Http\Api\StudioApi::getIdByName($request->get('name'))){
+                    return $this->error(__('auth.failed'));
+                }
+                $channelById = [];
+                $channelId = [];
+                //获取共享channel
+                $allSharedChannels = ShareApi::getResList($user['user_uid'],2);
+                foreach ($allSharedChannels as $key => $value) {
+                    # code...
+                    $channelId[] = $value['res_id'];
+                    $channelById[$value['res_id']] = $value;
+                }
+                $table = Channel::select($indexCol)
+                            ->whereIn('uid', $channelId)
+                            ->orWhere('owner_uid',$user['user_uid']);
+                break;
             case 'user-edit':
                 /**
                  * 某用户有编辑权限的
@@ -74,60 +116,59 @@ class ChannelController extends Controller
             case 'user-in-chapter':
                 #获取user 在某章节 所有有权限的channel列表
                 $user = AuthApi::current($request);
-                if($user){
-                    $channelById = [];
-                    $channelId = [];
-                    //获取共享channel
-                    $allSharedChannels = ShareApi::getResList($user['user_uid'],2);
-                    foreach ($allSharedChannels as $key => $value) {
-                        # code...
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                $channelById = [];
+                $channelId = [];
+                //获取共享channel
+                $allSharedChannels = ShareApi::getResList($user['user_uid'],2);
+                foreach ($allSharedChannels as $key => $value) {
+                    # code...
+                    $channelId[] = $value['res_id'];
+                    $channelById[$value['res_id']] = $value;
+                }
+                //获取全网公开channel
+                $chapter = PaliTextApi::getChapterStartEnd($request->get('book'),$request->get('para'));
+                $publicChannelsWithContent = Sentence::where('book_id',$request->get('book'))
+                                            ->whereBetween('paragraph',$chapter)
+                                            ->where('strlen','>',0)
+                                            ->where('status',30)
+                                            ->groupBy('channel_uid')
+                                            ->select('channel_uid')
+                                            ->get();
+                foreach ($publicChannelsWithContent as $key => $value) {
+                    # code...
+                    $value['res_id']=$value->channel_uid;
+                    $value['power'] = 10;
+                    $value['type'] = 2;
+                    if(!isset($channelById[$value['res_id']])){
                         $channelId[] = $value['res_id'];
                         $channelById[$value['res_id']] = $value;
                     }
-                    //获取全网公开channel
-                    $chapter = PaliTextApi::getChapterStartEnd($request->get('book'),$request->get('para'));
-                    $publicChannelsWithContent = Sentence::where('book_id',$request->get('book'))
-                                                ->whereBetween('paragraph',$chapter)
-                                                ->where('strlen','>',0)
-                                                ->where('status',30)
-                                                ->groupBy('channel_uid')
-                                                ->select('channel_uid')
-                                                ->get();
-                    foreach ($publicChannelsWithContent as $key => $value) {
-                        # code...
-                        $value['res_id']=$value->channel_uid;
-                        $value['power'] = 10;
-                        $value['type'] = 2;
-                        if(!isset($channelById[$value['res_id']])){
-                            $channelId[] = $value['res_id'];
-                            $channelById[$value['res_id']] = $value;
-                        }
-                    }
-                    $table = Channel::select($indexCol)
-                            ->whereIn('uid', $channelId)
-                            ->orWhere('owner_uid',$user['user_uid']);
-                }else{
-                    return $this->error(__('auth.failed'));
                 }
+                $table = Channel::select($indexCol)
+                        ->whereIn('uid', $channelId)
+                        ->orWhere('owner_uid',$user['user_uid']);
+
                 break;
 
         }
         //处理搜索
-        if(isset($_GET["search"])){
-            $table = $table->where('title', 'like', $_GET["search"]."%");
+        if($request->has("search")){
+            $table = $table->where('name', 'like', "%".$request->get("search")."%");
         }
         //获取记录总条数
         $count = $table->count();
         //处理排序
-        if(isset($_GET["order"]) && isset($_GET["dir"])){
-            $table = $table->orderBy($_GET["order"],$_GET["dir"]);
+        if($request->has("order") && $request->has("dir")){
+            $table = $table->orderBy($request->get("order"),$request->get("dir"));
         }else{
             //默认排序
             $table = $table->orderBy('updated_at','desc');
         }
         //处理分页
         if($request->has("limit")){
-
             if($request->has("offset")){
                 $offset = $request->get("offset");
             }else{
@@ -173,6 +214,7 @@ class ChannelController extends Controller
                     }
 
                 }
+                //角色
                 if($value->owner_uid===$user['user_uid']){
                     $value['role'] = 'owner';
                 }else{
@@ -196,19 +238,7 @@ class ChannelController extends Controller
                     }
                 }
                 # 获取studio信息
-                $studio = $userinfo->getName($value->owner_uid);
-                $value->studio = [
-                    'id'=>$value->owner_uid,
-                    'nickName'=>$studio['nickname'],
-                    'studioName'=>$studio['username'],
-                    'avatar'=>'',
-                    'owner' => [
-                        'id'=>$value->owner_uid,
-                        'nickName'=>$studio['nickname'],
-                        'userName'=>$studio['username'],
-                        'avatar'=>'',
-                    ]
-                ];
+                $value->studio = \App\Http\Api\StudioApi::getById($value->owner_uid);
             }
 			return $this->ok(["rows"=>$result,"count"=>$count]);
 		}else{
@@ -217,6 +247,176 @@ class ChannelController extends Controller
 
     }
 
+    /**
+     * 获取我的,和协作channel数量
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function showMyNumber(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //判断当前用户是否有指定的studio的权限
+        $studioId = StudioApi::getIdByName($request->get('studio'));
+        if($user['user_uid'] !== $studioId){
+            return $this->error(__('auth.failed'));
+        }
+        //我的
+        $my = Channel::where('owner_uid', $studioId)->count();
+        //协作
+        $resList = ShareApi::getResList($studioId,2);
+        $resId=[];
+        foreach ($resList as $res) {
+            $resId[] = $res['res_id'];
+        }
+        $collaboration = Channel::whereIn('uid', $resId)->where('owner_uid','<>', $studioId)->count();
+
+        return $this->ok(['my'=>$my,'collaboration'=>$collaboration]);
+    }
+    /**
+     * 获取章节的进度
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function progress(Request $request){
+		$indexCol = ['uid','name','summary','type','owner_uid','lang','status','updated_at','created_at'];
+
+        $sent = $request->get('sentence') ;
+        $query = [];
+        $sentContainer = [];
+        $sentLenContainer = [];
+
+        foreach ($sent as $value) {
+            $ids = explode('-',$value);
+            if(count($ids)===4){
+                $sentContainer[$value] = false;
+                $query[] = $ids;
+            }
+        }
+        //获取单句长度
+        if(count($query)>0){
+            $table = PaliSentence::whereIns(['book','paragraph','word_begin','word_end'],$query)
+                                    ->select(['book','paragraph','word_begin','word_end','length']);
+            $sentLen = $table->get();
+
+            foreach ($sentLen as $value) {
+                $sentLenContainer["{$value->book}-{$value->paragraph}-{$value->word_begin}-{$value->word_end}"] = $value->length;
+            }
+        }
+
+        #获取 user 在某章节 所有有权限的 channel 列表
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        $channelById = [];
+        $channelId = [];
+        //获取共享channel
+        $allSharedChannels = ShareApi::getResList($user['user_uid'],2);
+        foreach ($allSharedChannels as $key => $value) {
+            # code...
+            $channelId[] = $value['res_id'];
+            $channelById[$value['res_id']] = $value;
+        }
+        //获取全网公开的有译文的channel
+        if(count($query)>0){
+            $publicChannelsWithContent = Sentence::whereIns(['book_id','paragraph','word_start','word_end'],$query)
+                                        ->where('strlen','>',0)
+                                        ->where('status',30)
+                                        ->groupBy('channel_uid')
+                                        ->select('channel_uid')
+                                        ->get();
+            foreach ($publicChannelsWithContent as $key => $value) {
+                # code...
+                $value['res_id']=$value->channel_uid;
+                $value['power'] = 10;
+                $value['type'] = 2;
+                if(!isset($channelById[$value['res_id']])){
+                    $channelId[] = $value['res_id'];
+                    $channelById[$value['res_id']] = $value;
+                }
+            }
+        }
+        //所有有这些句子译文的channel
+        if(count($query) > 0){
+            $allChannels = Sentence::whereIns(['book_id','paragraph','word_start','word_end'],$query)
+                                        ->where('strlen','>',0)
+                                        ->groupBy('channel_uid')
+                                        ->select('channel_uid')
+                                        ->get();
+        }
+
+
+
+        //所有需要查询的channel
+        $result = Channel::select(['uid','name','summary','type','owner_uid','lang','status','updated_at','created_at'])
+                        ->whereIn('uid', $channelId)
+                        ->orWhere('owner_uid',$user['user_uid'])
+                        ->get();
+
+        foreach ($result as $key => $value) {
+            //角色
+            if($value->owner_uid===$user['user_uid']){
+                $value['role'] = 'owner';
+            }else{
+                if(isset($channelById[$value->uid])){
+                    switch ($channelById[$value->uid]['power']) {
+                        case 10:
+                            # code...
+                            $value['role'] = 'member';
+                            break;
+                        case 20:
+                            $value['role'] = 'editor';
+                            break;
+                        case 30:
+                            $value['role'] = 'owner';
+                            break;
+                        default:
+                            # code...
+                            $value['role'] = $channelById[$value->uid]['power'];
+                            break;
+                    }
+                }
+            }
+            # 获取studio信息
+            $result[$key]["studio"] = \App\Http\Api\StudioApi::getById($value->owner_uid);
+
+            //获取进度
+            if(count($query) > 0){
+                $currChannelId = $value->uid;
+                $hasContent = Arr::first($allChannels, function ($value, $key) use($currChannelId) {
+                        return ($value->channel_uid===$currChannelId);
+                    });
+                if($hasContent && count($query)>0){
+                    $finalTable = Sentence::whereIns(['book_id','paragraph','word_start','word_end'],$query)
+                                            ->where('channel_uid',$currChannelId)
+                                            ->where('strlen','>',0)
+                                            ->select(['strlen','book_id','paragraph','word_start','word_end']);
+                    if($finalTable->count()>0){
+                        $finished = $finalTable->get();
+                        $currChannel = [];
+                        foreach ($finished as $rowFinish) {
+                            $currChannel["{$rowFinish->book_id}-{$rowFinish->paragraph}-{$rowFinish->word_start}-{$rowFinish->word_end}"] = 1;
+                        }
+                        $final=[];
+                        foreach ($sentContainer as $sentId=>$rowSent) {
+                            # code...
+                            if(isset($currChannel[$sentId])){
+                                $final[] = [$sentLenContainer[$sentId],true];
+                            }else{
+                                $final[] = [$sentLenContainer[$sentId],false];
+                            }
+                        }
+                        $result[$key]['final'] = $final;
+                    }
+                }
+            }
+        }
+        return $this->ok(["rows"=>$result,count($result)]);
+
+    }
     /**
      * Store a newly created resource in storage.
      *

+ 61 - 55
app/Http/Controllers/CollectionController.php

@@ -7,6 +7,9 @@ use Illuminate\Http\Request;
 use Illuminate\Support\Str;
 use Illuminate\Support\Facades\Log;
 use App\Http\Api\AuthApi;
+use App\Http\Api\StudioApi;
+use App\Http\Api\ShareApi;
+use App\Http\Resources\CollectionResource;
 use Illuminate\Support\Facades\DB;
 
 require_once __DIR__.'/../../../public/app/ucenter/function.php';
@@ -35,25 +38,37 @@ class CollectionController extends Controller
 				# code...
 				//$table = Collection::select($indexCol)->where('owner', $_COOKIE["user_uid"]);
                 # 获取studio内所有channel
-                $user = \App\Http\Api\AuthApi::current($request);
-                if($user){
-                    //判断当前用户是否有指定的studio的权限
-                    if($user['user_uid'] === \App\Http\Api\StudioApi::getIdByName($request->get('name'))){
-                        $table = Collection::select($indexCol)->where('owner', $user["user_uid"]);
-                    }else{
-                        return $this->error(__('auth.failed'));
-                    }
-                }else{
+                $user = AuthApi::current($request);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                $studioId = StudioApi::getIdByName($request->get('name'));
+                //判断当前用户是否有指定的studio的权限
+                if($user['user_uid'] !== $studioId){
                     return $this->error(__('auth.failed'));
                 }
+                $table = Collection::select($indexCol);
+                if($request->get('view2','my')==='my'){
+                    $table = $table->where('owner', $studioId);
+                }else{
+                    //协作
+                    $resList = ShareApi::getResList($studioId,4);
+                    $resId=[];
+                    foreach ($resList as $res) {
+                        $resId[] = $res['res_id'];
+                    }
+                    $table = $table->whereIn('uid', $resId)->where('owner','<>', $studioId);
+                }
+
 				break;
 			case 'public':
+                //全网公开
 				$table = Collection::select($indexCol)->where('status', 30);
+                if($request->has('studio')){
+                    $studioId = StudioApi::getIdByName($request->get('studio'));
+                    $table = $table->where('owner',$studioId);
+                }
 				break;
-            case 'public_studio':
-                $user = $userinfo->getUserByName($request->get('studio'));
-                $table = Collection::select($indexCol)->where('status', 30)->where('owner',$user['userid']);
-                break;
 			default:
 				# code...
 			    return $this->error("没有查询到数据");
@@ -63,8 +78,8 @@ class CollectionController extends Controller
             $table = $table->where('title', 'like', "%".$request->get("search")."%");
         }
         $count = $table->count();
-        if(isset($_GET["order"]) && isset($_GET["dir"])){
-            $table = $table->orderBy($_GET["order"],$_GET["dir"]);
+        if($request->has("order") && $request->has("dir")){
+            $table = $table->orderBy($request->get("order"),$request->get("dir"));
         }else{
             if($request->get('view') === 'studio_list'){
                 $table = $table->orderBy('count','desc');
@@ -72,47 +87,40 @@ class CollectionController extends Controller
                 $table = $table->orderBy('updated_at','desc');
             }
         }
-
-        if(isset($_GET["limit"])){
-            $offset = 0;
-            if(isset($_GET["offset"])){
-                $offset = $_GET["offset"];
-            }
-            $table = $table->skip($offset)->take($_GET["limit"]);
+        if($request->has("limit")){
+            $table = $table->skip($request->get("offset",0))
+                           ->take($request->get("limit"));
         }
         $result = $table->get();
-		if($result){
-            foreach ($result as $key => $value) {
-                # code...
-                if(is_array(\json_decode($value->article_list))){
-                    $value->childrenNumber = count(\json_decode($value->article_list));
-                }else{
-                    $value->childrenNumber = 0;
-                }
+		return $this->ok(["rows"=>CollectionResource::collection($result),"count"=>$count]);
+    }
 
-                if(isset($value->article_list) && !empty($value->article_list) ){
-                    $arrList = \json_decode($value->article_list);
-                    if(is_array($arrList)){
-                        $result[$key]->article_list = array_slice($arrList,0,4);
-                    }
-                }
-                $value->studio = [
-                    'id'=>$value->owner,
-                    'nickName'=>$userinfo->getName($value->owner)['nickname'],
-                    'studioName'=>$userinfo->getName($value->owner)['username'],
-                    'avastar'=>'',
-                    'owner' => [
-                        'id'=>$value->owner,
-                        'nickName'=>$userinfo->getName($value->owner)['nickname'],
-                        'userName'=>$userinfo->getName($value->owner)['username'],
-                        'avastar'=>'',
-                    ]
-                ];
-            }
-			return $this->ok(["rows"=>$result,"count"=>$count]);
-		}else{
-			return $this->error("没有查询到数据");
-		}
+            /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function showMyNumber(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //判断当前用户是否有指定的studio的权限
+        $studioId = StudioApi::getIdByName($request->get('studio'));
+        if($user['user_uid'] !== $studioId){
+            return $this->error(__('auth.failed'));
+        }
+        //我的
+        $my = Collection::where('owner', $studioId)->count();
+        //协作
+        $resList = ShareApi::getResList($studioId,4);
+        $resId=[];
+        foreach ($resList as $res) {
+            $resId[] = $res['res_id'];
+        }
+        $collaboration = Collection::whereIn('uid', $resId)->where('owner','<>', $studioId)->count();
+
+        return $this->ok(['my'=>$my,'collaboration'=>$collaboration]);
     }
 
     /**
@@ -203,7 +211,6 @@ class CollectionController extends Controller
         $collection  = Collection::find($id);
         if($collection){
             //鉴权
-            Log::info("找到文集");
             $user = \App\Http\Api\AuthApi::current($request);
             if($user && $collection->owner === $user["user_uid"]){
                 $collection->title = $request->get('title');
@@ -219,7 +226,6 @@ class CollectionController extends Controller
                 return $this->ok($collection);
             }else{
                 //鉴权失败
-                Log::info("鉴权失败");
 
                 //TODO 判断是否为协作
                 return $this->error(__('auth.failed'));

+ 1 - 1
app/Http/Controllers/Controller.php

@@ -32,7 +32,7 @@ class Controller extends BaseController
 		return response()->json($response,$code);
 	}
 
-    public function error($error, $errorMessages="", $code=404){
+    public function error($error, $errorMessages=[], $code=404){
         return $this->sendError($error, $errorMessages, $code);
     }
 }

+ 358 - 173
app/Http/Controllers/CorpusController.php

@@ -9,14 +9,20 @@ use App\Models\WbwTemplate;
 use App\Models\WbwBlock;
 use App\Models\Wbw;
 use App\Models\Discussion;
+use App\Models\PaliSentence;
+use App\Models\SentSimIndex;
+use Illuminate\Support\Str;
+
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Cache;
 use App\Http\Api\MdRender;
 use App\Http\Api\SuggestionApi;
 use App\Http\Api\ChannelApi;
 use App\Http\Api\UserApi;
+use App\Http\Api\StudioApi;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Arr;
+use App\Http\Resources\TocResource;
 
 class CorpusController extends Controller
 {
@@ -28,13 +34,28 @@ class CorpusController extends Controller
         "summary"=> '',
         "content"=> '',
         "content_type"=> "html",
+        "toc" => [],
         "status"=>30,
         "lang"=> "",
         "created_at"=> "",
         "updated_at"=> "",
     ];
     protected $wbwChannels = [];
-    protected $selectCol = ['book_id','paragraph','word_start',"word_end",'channel_uid','content','updated_at'];
+    //句子需要查询的列
+    protected $selectCol = [
+        'uid',
+        'book_id',
+        'paragraph',
+        'word_start',
+        "word_end",
+        'channel_uid',
+        'content',
+        'content_type',
+        'editor_uid',
+        'acceptor_uid',
+        'pr_edit_at',
+        'updated_at'
+    ];
     public function __construct()
     {
 
@@ -45,9 +66,17 @@ class CorpusController extends Controller
      *
      * @return \Illuminate\Http\Response
      */
-    public function index()
+    public function index(Request $request)
     {
         //
+        switch ($request->get('view')) {
+            case 'para':
+                return $this->showPara($request);
+                break;
+            default:
+                # code...
+                break;
+        }
     }
 
     /**
@@ -74,6 +103,9 @@ class CorpusController extends Controller
     public function getSentTpl($id,$channels,$mode='edit',$onlyProps=false){
         $sent = [];
         $sentId = \explode('-',$id);
+        if(count($sentId) !== 4){
+            return false;
+        }
         if($mode==='read'){
             $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
         }else{
@@ -90,8 +122,6 @@ class CorpusController extends Controller
         ->where('word_end',(int)$sentId[3])
         ->whereIn('channel_uid',$channels)
         ->get();
-        Log::info("sent count:".count($record));
-
 
         $channelIndex = $this->getChannelIndex($channels);
 
@@ -168,18 +198,118 @@ class CorpusController extends Controller
         $this->result['content'] = $this->makeContent($record,$mode,$indexChannel);
         return $this->ok($this->result);
     }
+    /**
+     * Store a newly created resource in storage.
 
-    public function showChapter($id,$mode='read')
+     * @param  \Illuminate\Http\Request  $request
+     * @param string $id
+     * @param string $mode
+     * @return \Illuminate\Http\Response
+     */
+    public function showPara(Request $request)
     {
         //
-        $param = \explode('_',$id);
-        $sentId = \explode('-',$param[0]);
         $channels = [];
-        if(count($param)>1){
-            $channels = array_slice($param,1);
+        if($request->get('mode') === 'edit'){
+            //翻译模式加载json格式原文
+            $channels[] = ChannelApi::getSysChannel('_System_Wbw_VRI_');
+        }else{
+            //阅读模式加载html格式原文
+            $channels[] = ChannelApi::getSysChannel('_System_Pali_VRI_');
+        }
+
+        if($request->has('channels')){
+            $getChannel = explode(",",$request->get('channels'));
+            $channels = array_merge($channels,$getChannel );
+        }
+        $para = explode(",",$request->get('par'));
+
+        //段落所在章节
+        $parent = PaliText::where('book',$request->get('book'))
+                            ->where('paragraph',$para[0])->first();
+        $chapter = PaliText::where('book',$request->get('book'))
+                            ->where('paragraph',$parent->parent)->first();
+        if($chapter){
+            if(empty($chapter->toc)){
+                $this->result['title'] = "unknown";
+            }else{
+                $this->result['title'] = $chapter->toc;
+                $this->result['sub_title'] = $chapter->toc;
+                $this->result['path'] = json_decode($chapter->path);
+            }
         }
+
+        $paraFrom = $para[0];
+        $paraTo = end($para);
+
+        $indexedHeading = [];
+
+		#获取channel索引表
+        $tranChannels = [];
+		$channelInfo = Channel::whereIn("uid",$channels)->select(['uid','type','name'])->get();
+		foreach ($channelInfo as $key => $value) {
+			# code...
+            if($value->type==="translation" ){
+                $tranChannels[] = $value->uid;
+            }
+		}
+        $indexChannel = [];
+        $indexChannel = $this->getChannelIndex($channels);
+        //获取wbw channel
+        //目前默认的 wbw channel 是第一个translation channel
+        foreach ($channels as $key => $value) {
+            # code...
+            if($indexChannel[$value]->type==='translation'){
+                $this->wbwChannels[] = $value;
+                break;
+            }
+        }
+        //章节译文标题
+        $title = Sentence::select($this->selectCol)
+                    ->where('book_id',$parent->book)
+                    ->where('paragraph',$parent->paragraph)
+                    ->whereIn('channel_uid',$tranChannels)
+                    ->first();
+        if($title){
+            $this->result['title'] = MdRender::render($title->content,$title->channel_uid);
+        }
+
+        /**
+         * 获取句子数据
+         */
+        $record = Sentence::select($this->selectCol)
+                    ->where('book_id',$request->get('book'))
+                    ->whereIn('paragraph',$para)
+                    ->whereIn('channel_uid',$channels)
+                    ->orderBy('paragraph')
+                    ->orderBy('word_start')
+                    ->get();
+        if(count($record) ===0){
+            $this->result['content'] = "<span>No Data</span>";
+        }else{
+            $this->result['content'] = $this->makeContent($record,$request->get('mode','read'),$indexChannel,$indexedHeading);
+        }
+
+        return $this->ok($this->result);
+    }
+    /**
+     * Store a newly created resource in storage.
+
+     * @param  \Illuminate\Http\Request  $request
+     * @param string $id
+     * @return \Illuminate\Http\Response
+     */
+    public function showChapter(Request $request, string $id)
+    {
+        //
+        $sentId = \explode('-',$id);
+        $channels = [];
+        if($request->has('channels')){
+            $channels = explode('_',$request->get('channels'));
+        }
+        $mode = $request->get('mode','read');
         if($mode === 'read'){
-            //阅读模式加载md格式原文
+            //阅读模式加载html格式原文
             $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
         }else{
             //翻译模式加载json格式原文
@@ -219,14 +349,14 @@ class CorpusController extends Controller
 		#获取channel索引表
         $tranChannels = [];
 		$channelInfo = Channel::whereIn("uid",$channels)->select(['uid','type','name'])->get();
-		$indexChannel = [];
 		foreach ($channelInfo as $key => $value) {
 			# code...
-			$indexChannel[$value->uid] = $value;
             if($value->type==="translation" ){
                 $tranChannels[] = $value->uid;
             }
 		}
+        $indexChannel = [];
+        $indexChannel = $this->getChannelIndex($channels);
         //获取wbw channel
         //目前默认的 wbw channel 是第一个translation channel
         foreach ($channels as $key => $value) {
@@ -245,7 +375,45 @@ class CorpusController extends Controller
             $this->result['title'] = MdRender::render($title->content,$title->channel_uid);
         }
 
-		//获取句子数据
+        /**
+         * 获取句子数据
+         * 算法:
+         * 1. 如果标题和下一级第一个标题之间有段落。只输出这些段落和子目录
+         * 2. 如果标题和下一级第一个标题之间没有间隔 且 chapter 长度大于10000个字符 且有子目录,只输出子目录
+         * 3. 如果二者都不是,lazy load
+         */
+		//1. 计算 标题和下一级第一个标题之间 是否有间隔
+        $nextChapter =  PaliText::where('book',$sentId[0])
+                                ->where('paragraph',">",$sentId[1])
+                                ->where('level','<',8)
+                                ->orderBy('paragraph')
+                                ->value('paragraph');
+        $between = $nextChapter - $sentId[1];
+        //输出子目录
+        $chapterLen = $chapter->chapter_len;
+        $toc = PaliText::where('book',$sentId[0])
+                        ->whereBetween('paragraph',[$paraFrom+1,$paraFrom+$chapterLen-1])
+                        ->where('level','<',8)
+                        ->orderBy('paragraph')
+                        ->select(['book','paragraph','level','toc'])
+                        ->get();
+
+        if($between > 1){
+            //有间隔
+            $paraTo = $nextChapter - 1;
+        }else{
+            if($chapter->chapter_strlen>2000){
+                if(count($toc)>0){
+                    //有子目录只输出标题和目录
+                    $paraTo = $paraFrom;
+                }else{
+                    //没有子目录 全部输出
+                }
+            }else{
+                //章节小。全部输出 不输出章节
+                $toc = [];
+            }
+        }
         $record = Sentence::select($this->selectCol)
                     ->where('book_id',$sentId[0])
                     ->whereBetween('paragraph',[$paraFrom,$paraTo])
@@ -256,19 +424,27 @@ class CorpusController extends Controller
         if(count($record) ===0){
             return $this->error("no data");
         }
-
         $this->result['content'] = $this->makeContent($record,$mode,$indexChannel,$indexedHeading);
+        $this->result['toc'] = TocResource::collection($toc);
+        Log::info("show chapter");
         return $this->ok($this->result);
     }
 
-    private function getChannelIndex($channels){
+    private function getChannelIndex($channels,$type=null){
         #获取channel索引表
-        $channelInfo = Channel::whereIn("uid",$channels)->select(['uid','type','name'])->get();
+        $channelInfo = Channel::whereIn("uid",$channels)->select(['uid','type','name','owner_uid'])->get();
         $indexChannel = [];
         foreach ($channelInfo as $key => $value) {
             # code...
+            if($type !== null && $value->type !== $type){
+                continue;
+            }
             $indexChannel[$value->uid] = $value;
         }
+        foreach ($indexChannel as $uid => $value) {
+            # 查询studio
+            $indexChannel[$uid]['studio'] = StudioApi::getById($value->owner_uid);
+        }
         return $indexChannel;
     }
     /**
@@ -278,15 +454,15 @@ class CorpusController extends Controller
      * $indexChannel channel索引
      * $indexedHeading 标题索引 用于给段落加标题标签 <h1> ect.
      */
-    private function makeContent($record,$mode,$indexChannel,$indexedHeading=[],$onlyProps=false){
+    private function makeContent($record,$mode,$indexChannel,$indexedHeading=[],$onlyProps=false,$paraMark=false){
         $content = [];
 		$lastSent = "0-0";
 		$sentCount = 0;
         $sent = [];
         $sent["origin"] = [];
         $sent["translation"] = [];
-        //获取句子编号列表
 
+        //获取句子编号列表
         $sentList = [];
         foreach ($record as $key => $value) {
             $currSentId = "{$value->book_id}-{$value->paragraph}-{$value->word_start}-{$value->word_end}";
@@ -294,11 +470,12 @@ class CorpusController extends Controller
             $value['sid'] = "{$currSentId}_{$value->channel_uid}";
         }
         //遍历列表查找每个句子的所有channel的数据,并填充
+        $currPara = "";
         foreach ($sentList as $currSentId => $arrSentId) {
-            # code...
+            if($currPara === ""){
+                $currPara = $arrSentId[0]."-".$arrSentId[1];
+            }
             $sent = $this->newSent($arrSentId[0],$arrSentId[1],$arrSentId[2],$arrSentId[3]);
-            $sent["origin"] = [];
-            $sent["translation"] = [];
             foreach ($indexChannel as $channelId => $info) {
                 # code...
                 $sid = "{$currSentId}_{$channelId}";
@@ -314,6 +491,7 @@ class CorpusController extends Controller
                         "type"=>$info->type,
                         "id"=> $info->uid,
                     ],
+                    "studio" => $info['studio'],
                     "updateAt"=> "",
                     "suggestionCount" => SuggestionApi::getCountBySent($arrSentId[0],$arrSentId[1],$arrSentId[2],$arrSentId[3],$channelId),
                 ];
@@ -322,10 +500,18 @@ class CorpusController extends Controller
                     return $value['sid']===$sid;
                 });
                 if($row){
+                    $newSent['id'] = $row->uid;
                     $newSent['content'] = $row->content;
+                    $newSent['contentType'] = $row->content_type;
                     $newSent['html'] = "";
                     $newSent["editor"]=UserApi::getById($row->editor_uid);
                     $newSent['updateAt'] = $row->updated_at;
+                    if($mode !== "read"){
+                        if(isset($row->acceptor_uid) && !empty($row->acceptor_uid)){
+                            $newSent["acceptor"]=UserApi::getById($row->acceptor_uid);
+                            $newSent["prEditAt"]=$row->pr_edit_at;
+                        }
+                    }
                     switch ($info->type) {
                         case 'wbw':
                         case 'original':
@@ -337,21 +523,31 @@ class CorpusController extends Controller
                                 // 传过来的数据一定有一个原文channel
                                 //
                             if($mode !== "read"){
+
                                 $newSent['channel']['type'] = "wbw";
+
                                 if(isset($this->wbwChannels[0])){
+                                    $newSent['channel']['name'] = $indexChannel[$this->wbwChannels[0]]->name;
+                                    $newSent['channel']['id'] = $this->wbwChannels[0];
                                     //存在一个translation channel
                                     //尝试查找逐词解析数据。找到,替换现有数据
-                                    $wbwData = $this->getWbw($arrSentId[0],$arrSentId[1],$arrSentId[2],$arrSentId[3],$channelId);
+                                    $wbwData = $this->getWbw($arrSentId[0],$arrSentId[1],$arrSentId[2],$arrSentId[3],$this->wbwChannels[0]);
                                     if($wbwData){
                                         $newSent['content'] = $wbwData;
                                         $newSent['html'] = "";
                                     }
                                 }
                             }else{
-                                $newSent['html'] = $row->content;
                                 $newSent['content'] = "";
+                                $newSent['html'] = $row->content;
                             }
 
+                            break;
+                        case 'nissaya':
+                            $newSent['html'] = Cache::remember("/sent/{$channelId}/{$currSentId}",10,
+                            function() use($row,$mode){
+                                return MdRender::render($row->content,$row->channel_uid,null,$mode,"nissaya",$row->content_type);
+                            });
                             break;
                         default:
                             //译文需要markdown渲染
@@ -378,141 +574,10 @@ class CorpusController extends Controller
             $content = $this->pushSent($content,$sent,0,$mode);
         }
 
-/*
-        foreach ($record as $key => $value) {
-            # 遍历结果生成html文件
-            $currSentId = $value->book_id.'-'.$value->paragraph.'-'.$value->word_start.'-'.$value->word_end;
-			if($currSentId !== $lastSent){
-				if($sentCount > 0){
-					//保存上一个句子
-					//增加标题的html标记
-					$level = 0;
-					if(isset($indexedHeading["{$value->book_id}-{$value->paragraph}"])){
-						$level = $indexedHeading["{$value->book_id}-{$value->paragraph}"];
-					}
-					$content = $this->pushSent($content,$sent,$level,$mode);
-				}
-				//新建句子
-				$sent = $this->newSent($value->book_id,$value->paragraph,$value->word_start,$value->word_end);
-
-				$lastSent = $currSentId;
-			}
-			$sentContent=$value->content;
-			$channelType = $indexChannel[$value->channel_uid]->type;
-			if($indexChannel[$value->channel_uid]->type==="original" && $mode !== 'read'){
-                //非阅读模式下。原文使用逐词解析数据。优先加载第一个translation channel 如果没有。加载默认逐词解析。
-				$channelType = 'wbw';
-                $html = "";
-
-                if(count($this->wbwChannels)>0){
-                    //获取逐词解析数据
-                    $wbwBlock = WbwBlock::where('channel_uid',$this->wbwChannels[0])
-                                        ->where('book_id',$value->book_id)
-                                        ->where('paragraph',$value->paragraph)
-                                        ->select('uid')
-                                        ->first();
-                    if($wbwBlock){
-                        //找到逐词解析数据
-                        $wbwData = Wbw::where('block_uid',$wbwBlock->uid)
-                                      ->whereBetween('wid',[$value->word_start,$value->word_end])
-                                      ->select(['data','uid'])
-                                      ->orderBy('wid')
-                                      ->get();
-                        $wbwContent = [];
-                        foreach ($wbwData as $wbwrow) {
-                            $wbw = str_replace("&nbsp;",' ',$wbwrow->data);
-                            $wbw = str_replace("<br>",' ',$wbw);
-
-                            $xmlString = "<root>" . $wbw . "</root>";
-                            try{
-                                $xmlWord = simplexml_load_string($xmlString);
-                            }catch(Exception $e){
-                                continue;
-                            }
-                            $wordsList = $xmlWord->xpath('//word');
-                            foreach ($wordsList as $word) {
-                                $case = \str_replace(['#','.'],['$',''],$word->case->__toString());
-                                $case = \str_replace('$$','$',$case);
-                                $case = trim($case);
-                                $case = trim($case,"$");
-                                $wbwContent[] = [
-                                    'uid'=>$wbwrow->uid,
-                                    'word'=>['value'=>$word->pali->__toString(),'status'=>0],
-                                    'real'=> ['value'=>$word->real->__toString(),'status'=>0],
-                                    'meaning'=> ['value'=>\explode('$',$word->mean->__toString()) ,'status'=>0],
-                                    'type'=> ['value'=>$word->type->__toString(),'status'=>0],
-                                    'grammar'=> ['value'=>$word->gramma->__toString(),'status'=>0],
-                                    'case'=> ['value'=>\explode('$',$case),'status'=>0],
-                                    'parent'=> ['value'=>$word->parent->__toString(),'status'=>0],
-                                    'style'=> ['value'=>$word->style->__toString(),'status'=>0],
-                                    'factors'=> ['value'=>$word->org->__toString(),'status'=>0],
-                                    'factorMeaning'=> ['value'=>$word->om->__toString(),'status'=>0],
-                                    'confidence'=> 0.5,
-                                    'hasComment'=>Discussion::where('res_id',$wbwrow->uid)->exists(),
-                                ];
-                            }
-                        }
-                        $sentContent = \json_encode($wbwContent);
-                    }
-                }
-			}else{
-                if($indexChannel[$value->channel_uid]->type==="original"){
-                    //原文直接使用
-                    $html = Cache::remember("/sent/{$value->channel_uid}/{$currSentId}",10,
-                            function() use($value){
-                                return $value->content;
-                            });
-                }else{
-                    //译文需要markdown渲染
-                    $html = Cache::remember("/sent/{$value->channel_uid}/{$currSentId}",10,
-                            function() use($value){
-                                return MdRender::render($value->content,$value->channel_uid);
-                            });
-                }
-            }
-
-            $newSent = [
-                "content"=>$sentContent,
-                "html"=> $html,
-                "book"=> $value->book_id,
-                "para"=> $value->paragraph,
-                "wordStart"=> $value->word_start,
-                "wordEnd"=> $value->word_end,
-                "editor"=> [
-                    'id'=>$value->editor_uid,
-                    'nickName'=>'nickname',
-                    'realName'=>'realName',
-                    'avatar'=>'',
-                ],
-                "channel"=> [
-                    "name"=>$indexChannel[$value->channel_uid]->name,
-                    "type"=>$channelType,
-	                "id"=> $value->channel_uid,
-                ],
-                "updateAt"=> $value->updated_at,
-                "suggestionCount" => SuggestionApi::getCountBySent($value->book_id,$value->paragraph,$value->word_start,$value->word_end,$value->channel_uid),
-            ];
-			switch ($indexChannel[$value->channel_uid]->type) {
-				case 'original';
-				case 'wbw';
-					array_push($sent["origin"],$newSent);
-					break;
-				default:
-					array_push($sent["translation"],$newSent);
-					break;
-			}
-
-			$sentCount++;
-        }
-        if($onlyProps){
-            return $sent;
-        }
-        $content = $this->pushSent($content,$sent,0,$mode);
-*/
         $output = \implode("",$content);
         return "<div>{$output}</div>";
     }
-    private function getWbw($book,$para,$start,$end,$channel){
+    public function getWbw($book,$para,$start,$end,$channel){
         /**
          * 非阅读模式下。原文使用逐词解析数据。
          * 优先加载第一个translation channel 如果没有。加载默认逐词解析。
@@ -530,7 +595,7 @@ class CorpusController extends Controller
         //找到逐词解析数据
         $wbwData = Wbw::where('block_uid',$wbwBlock->uid)
                       ->whereBetween('wid',[$start,$end])
-                      ->select(['data','uid'])
+                      ->select(['book_id','paragraph','wid','data','uid'])
                       ->orderBy('wid')
                       ->get();
         $wbwContent = [];
@@ -550,23 +615,111 @@ class CorpusController extends Controller
                 $case = \str_replace('$$','$',$case);
                 $case = trim($case);
                 $case = trim($case,"$");
-                $wbwContent[] = [
+                $wbwId = explode('-',$word->id->__toString());
+
+                $wbwData = [
                     'uid'=>$wbwrow->uid,
+                    'book'=>$wbwrow->book_id,
+                    'para'=>$wbwrow->paragraph,
+                    'sn'=> array_slice($wbwId,2),
                     'word'=>['value'=>$word->pali->__toString(),'status'=>0],
                     'real'=> ['value'=>$word->real->__toString(),'status'=>0],
-                    'meaning'=> ['value'=>\explode('$',$word->mean->__toString()) ,'status'=>0],
+                    'meaning'=> ['value'=>$word->mean->__toString() ,'status'=>0],
                     'type'=> ['value'=>$word->type->__toString(),'status'=>0],
                     'grammar'=> ['value'=>$word->gramma->__toString(),'status'=>0],
-                    'case'=> ['value'=>\explode('$',$case),'status'=>0],
+                    'case'=> ['value'=>$word->case->__toString(),'status'=>0],
                     'parent'=> ['value'=>$word->parent->__toString(),'status'=>0],
                     'style'=> ['value'=>$word->style->__toString(),'status'=>0],
                     'factors'=> ['value'=>$word->org->__toString(),'status'=>0],
                     'factorMeaning'=> ['value'=>$word->om->__toString(),'status'=>0],
-                    'confidence'=> 0.5,
+                    'confidence'=> $word->cf->__toString(),
                     'hasComment'=>Discussion::where('res_id',$wbwrow->uid)->exists(),
                 ];
+                if(isset($word->parent2)){
+                    $wbwData['parent2']['value'] = $word->parent2->__toString();
+                    if(isset($word->parent2['status'])){
+                        $wbwData['parent2']['status'] = (int)$word->parent2['status'];
+                    }else{
+                        $wbwData['parent2']['status'] = 0;
+                    }
+                }
+                if(isset($word->pg)){
+                    $wbwData['grammar2']['value'] = $word->pg->__toString();
+                    if(isset($word->pg['status'])){
+                        $wbwData['grammar2']['status'] = (int)$word->pg['status'];
+                    }else{
+                        $wbwData['grammar2']['status'] = 0;
+                    }
+                }
+                if(isset($word->rela)){
+                    $wbwData['relation']['value'] = $word->rela->__toString();
+                    if(isset($word->rela['status'])){
+                        $wbwData['relation']['status'] = (int)$word->rela['status'];
+                    }else{
+                        $wbwData['relation']['status'] = 7;
+                    }
+                }
+                if(isset($word->bmt)){
+                    $wbwData['bookMarkText']['value'] = $word->bmt->__toString();
+                    if(isset($word->bmt['status'])){
+                        $wbwData['bookMarkText']['status'] = (int)$word->bmt['status'];
+                    }else{
+                        $wbwData['bookMarkText']['status'] = 7;
+                    }
+                }
+                if(isset($word->bmc)){
+                    $wbwData['bookMarkColor']['value'] = $word->bmc->__toString();
+                    if(isset($word->bmc['status'])){
+                        $wbwData['bookMarkColor']['status'] = (int)$word->bmc['status'];
+                    }else{
+                        $wbwData['bookMarkColor']['status'] = 7;
+                    }
+                }
+                if(isset($word->note)){
+                    $wbwData['note']['value'] = $word->note->__toString();
+                    if(isset($word->note['status'])){
+                        $wbwData['note']['status'] = (int)$word->note['status'];
+                    }else{
+                        $wbwData['note']['status'] = 7;
+                    }
+                }
+                if(isset($word->cf)){
+                    $wbwData['confidence'] = (float)$word->cf->__toString();
+                }
+                if(isset($word->pali['status'])){
+                    $wbwData['word']['status'] = (int)$word->pali['status'];
+                }
+                if(isset($word->real['status'])){
+                    $wbwData['real']['status'] = (int)$word->real['status'];
+                }
+                if(isset($word->mean['status'])){
+                    $wbwData['meaning']['status'] = (int)$word->mean['status'];
+                }
+                if(isset($word->type['status'])){
+                    $wbwData['type']['status'] = (int)$word->type['status'];
+                }
+                if(isset($word->gramma['status'])){
+                    $wbwData['grammar']['status'] = (int)$word->gramma['status'];
+                }
+                if(isset($word->case['status'])){
+                    $wbwData['case']['status'] = (int)$word->case['status'];
+                }
+                if(isset($word->parent['status'])){
+                    $wbwData['parent']['status'] = (int)$word->parent['status'];
+                }
+                if(isset($word->org['status'])){
+                    $wbwData['factors']['status'] = (int)$word->org['status'];
+                }
+                if(isset($word->om['status'])){
+                    $wbwData['factorMeaning']['status'] = (int)$word->om['status'];
+                }
+
+                $wbwContent[] = $wbwData;
             }
         }
+        if(count($wbwContent)===0){
+            return false;
+        }
         return \json_encode($wbwContent,JSON_UNESCAPED_UNICODE);
 
     }
@@ -591,32 +744,70 @@ class CorpusController extends Controller
 	private function newSent($book,$para,$word_start,$word_end){
 		$sent = [
             "id"=>"{$book}-{$para}-{$word_start}-{$word_end}",
+            "book"=>$book,
+            "para"=>$para,
+            "wordStart"=>$word_start,
+            "wordEnd"=>$word_end,
 			"origin"=>[],
 			"translation"=>[],
 		];
 
 		#生成channel 数量列表
 		$sentId = "{$book}-{$para}-{$word_start}-{$word_end}";
-		$channelCount = Cache::remember("/sent1/{$sentId}/channels/count",
+        $channelCount = CorpusController::sentResCount($book,$para,$word_start,$word_end);
+        $path = json_decode(PaliText::where('book',$book)->where('paragraph',$para)->value("path"),true);
+        $sent["path"] = [];
+        foreach ($path as $key => $value) {
+            # code...
+            $value['paliTitle'] = $value['title'];
+            $sent["path"][] = $value;
+        }
+		$sent["tranNum"] = $channelCount['tranNum'];
+		$sent["nissayaNum"] = $channelCount['nissayaNum'];
+		$sent["commNum"] = $channelCount['commNum'];
+		$sent["originNum"] = $channelCount['originNum'];
+		$sent["simNum"] = $channelCount['simNum'];
+		return $sent;
+	}
+
+    /**
+     * 获取某个句子的相关资源的句子数量
+     */
+    public static function sentResCount($book,$para,$start,$end){
+		$sentId = "{$book}-{$para}-{$start}-{$end}";
+		$channelCount = Cache::remember("/sentence/{$sentId}/channels/count",
                           60,
-                          function() use($book,$para,$word_start,$word_end){
+                          function() use($book,$para,$start,$end){
 			$channels =  Sentence::where('book_id',$book)
 							->where('paragraph',$para)
-							->where('word_start',$word_start)
-							->where('word_end',$word_end)
+							->where('word_start',$start)
+							->where('word_end',$end)
 							->select('channel_uid')
                             ->groupBy('channel_uid')
 							->get();
             $channelList = [];
             foreach ($channels as $key => $value) {
                 # code...
-                $channelList[] = $value->channel_uid;
+                if(Str::isUuid($value->channel_uid)){
+                    $channelList[] = $value->channel_uid;
+                }
+            }
+            $simId = PaliSentence::where('book',$book)
+                                 ->where('paragraph',$para)
+                                 ->where('word_begin',$start)
+                                 ->where('word_end',$end)
+                                 ->value('id');
+            if($simId){
+                $output["simNum"]=SentSimIndex::where('sent_id',$simId)->value('count');
+            }else{
+                $output["simNum"]=0;
             }
             $channelInfo = Channel::whereIn("uid",$channelList)->select('type')->get();
             $output["tranNum"]=0;
             $output["nissayaNum"]=0;
             $output["commNum"]=0;
             $output["originNum"]=0;
+
             foreach ($channelInfo as $key => $value) {
                 # code...
                 switch($value->type){
@@ -637,14 +828,8 @@ class CorpusController extends Controller
 			return $output;
 
 		});
-
-		$sent["tranNum"] = $channelCount['tranNum'];
-		$sent["nissayaNum"] = $channelCount['nissayaNum'];
-		$sent["commNum"] = $channelCount['commNum'];
-		$sent["originNum"] = $channelCount['originNum'];
-		return $sent;
-	}
-
+        return $channelCount;
+    }
     private function markdownRender($input){
 
     }

+ 7 - 14
app/Http/Controllers/CourseController.php

@@ -96,23 +96,16 @@ class CourseController extends Controller
                 break;
         }
         $table = $table->select($indexCol);
-        if(isset($_GET["search"])){
-            $table = $table->where('title', 'like', $_GET["search"]."%");
+        if($request->has('search')){
+            $table = $table->where('title', 'like', $request->get('search')."%");
         }
         $count = $table->count();
-        if(isset($_GET["order"]) && isset($_GET["dir"])){
-            $table = $table->orderBy($_GET["order"],$_GET["dir"]);
-        }else{
-            $table = $table->orderBy('updated_at','desc');
-        }
+        $table = $table->orderBy($request->get('order','updated_at'),
+                                 $request->get('dir','desc'));
+
+        $table = $table->skip($request->get('offset',0))
+                       ->take($request->get('limit',1000));
 
-        if(isset($_GET["limit"])){
-            $offset = 0;
-            if(isset($_GET["offset"])){
-                $offset = $_GET["offset"];
-            }
-            $table = $table->skip($offset)->take($_GET["limit"]);
-        }
         $result = $table->get();
 		if($result){
 			return $this->ok(["rows"=>CourseResource::collection($result),"count"=>$count]);

+ 29 - 13
app/Http/Controllers/CourseMemberController.php

@@ -103,6 +103,7 @@ class CourseMemberController extends Controller
             'user_id' => 'required',
             'course_id' => 'required',
             'role' => 'required',
+            'operating' => 'required',
         ]);
         //查找重复的项目
         if(CourseMember::where('course_id', $validated['course_id'])
@@ -122,12 +123,30 @@ class CourseMemberController extends Controller
         $course  = Course::find($validated['course_id']);
         if($course){
             switch ($course->join) {
-                case 'open':
-                    $newMember->status = 'accepted';
+                case 'open': //开放学习课程
+                    switch ($validated['operating']) {
+                        case 'invite':
+                            $newMember->status = 'invited';
+                            break;
+                        case 'sign_up':
+                            $newMember->status = 'normal';
+                            break;
+                    }
                     break;
-                case 'manual':
-                    $newMember->status = 'progressing';
+                case 'manual': //人工审核课程
+                    switch ($validated['operating']) {
+                        case 'invite':
+                            $newMember->status = 'invited';
+                            break;
+                        case 'sign_up':
+                            $newMember->status = 'sign_up';
+                            break;
+                    }
                     break;
+                case 'invite': //仅限邀请
+                    $newMember->status = 'invited';
+                    break;
+
                 default:
                     # code...
                     break;
@@ -135,9 +154,7 @@ class CourseMemberController extends Controller
         }else{
             return $this->error('invalid course');
         }
-
         $newMember->save();
-
         return $this->ok(new CourseMemberResource($newMember));
 
     }
@@ -221,10 +238,6 @@ class CourseMemberController extends Controller
             return $this->error(__('auth.failed'));
         }
 
-
-        Log::info('course'.$courseMember->course_id);
-        Log::info('user id'.$user["user_uid"]);
-
         $isOwner = Course::where('id',$courseMember->course_id)->where('studio_id',$user["user_uid"])->exists();
         if(!$isOwner){
             $courseUser = CourseMember::where('course_id',$courseMember->course_id)
@@ -232,9 +245,12 @@ class CourseMemberController extends Controller
                 ->select('role')->first();
             //open 课程 可以删除自己
 
-           if(!$courseUser || $courseUser->role ==="student"){
-                //普通成员没有删除权限
-                return $this->error(__('auth.failed'));
+            if(!$courseUser){
+                //被删除的不是自己
+                if($courseUser->role ==="student"){
+                    //普通成员没有删除权限
+                    return $this->error(__('auth.failed'));
+                }
             }
         }
 

+ 352 - 74
app/Http/Controllers/DhammaTermController.php

@@ -10,6 +10,14 @@ use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Str;
 use App\Http\Api\AuthApi;
 use App\Http\Api\StudioApi;
+use App\Http\Api\ChannelApi;
+use App\Http\Api\ShareApi;
+use App\Tools\Tools;
+use App\Http\Resources\TermResource;
+use Illuminate\Support\Facades\App;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+use Illuminate\Support\Facades\Log;
 
 class DhammaTermController extends Controller
 {
@@ -21,7 +29,7 @@ class DhammaTermController extends Controller
     public function index(Request $request)
     {
         $result=false;
-		$indexCol = ['id','guid','word','meaning','other_meaning','note','language','channal','created_at','updated_at'];
+		$indexCol = ['id','guid','word','meaning','other_meaning','note','tag','language','channal','owner','editor_id','created_at','updated_at'];
 
 		switch ($request->get('view')) {
             case 'create-by-channel':
@@ -69,24 +77,40 @@ class DhammaTermController extends Controller
                     "meaningCount"=>$meaningCount,
                     "studioChannels"=>$studioChannels,
                     "language"=>$currChannel->lang,
+                    'studio'=>StudioApi::getById($currChannel->owner_uid),
                 ]);
                 break;
             case 'studio':
-				# 获取studio内所有channel
-                $search = $request->get('search');
+				# 获取 studio 内所有 term
                 $user = AuthApi::current($request);
-                if($user){
-                    //判断当前用户是否有指定的studio的权限
-                    if($user['user_uid'] === StudioApi::getIdByName($request->get('name'))){
-                        $table = DhammaTerm::select($indexCol)
-                                    ->where('owner', $user["user_uid"]);
-                    }else{
-                        return $this->error(__('auth.failed'));
-                    }
-                }else{
-                    return $this->error(__('auth.failed'));
+                if(!$user){
+                    return $this->error(__('auth.failed'),[],401);
+                }
+                //判断当前用户是否有指定的studio的权限
+                if($user['user_uid'] !== StudioApi::getIdByName($request->get('name'))){
+                    return $this->error(__('auth.failed'),[],403);
                 }
+                $table = DhammaTerm::select($indexCol)
+                                   ->where('owner', $user["user_uid"]);
 				break;
+            case 'channel':
+                # 获取 studio 内所有 term
+                $user = AuthApi::current($request);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                //判断当前用户是否有指定的 channel 的权限
+                $channel = Channel::find($request->get('id'));
+                if($user['user_uid'] !== $channel->owner_uid ){
+                    //看是否为协作
+                    $power = ShareApi::getResPower($user['user_uid'],$request->get('id'));
+                    if($power === 0){
+                        return $this->error(__('auth.failed'),[],403);
+                    }
+                }
+                $table = DhammaTerm::select($indexCol)
+                                    ->where('channal', $request->get('id'));
+                break;
             case 'show':
                 return $this->ok(DhammaTerm::find($request->get('id')));
                 break;
@@ -136,28 +160,23 @@ class DhammaTermController extends Controller
 				# code...
 				break;
 		}
+
+        $search = $request->get('search');
         if(!empty($search)){
-            $table->where('word', 'like', $search."%")
+            $table = $table->where(function($query) use($search){
+                $query->where('word', 'like', $search."%")
                   ->orWhere('word_en', 'like', $search."%")
                   ->orWhere('meaning', 'like', "%".$search."%");
-        }
-        if(!empty($request->get('order')) && !empty($request->get('dir'))){
-            $table->orderBy($request->get('order'),$request->get('dir'));
-        }else{
-            $table->orderBy('updated_at','desc');
+            });
         }
         $count = $table->count();
-        if(!empty($request->get('limit'))){
-            $offset = 0;
-            if(!empty($request->get("offset"))){
-                $offset = $request->get("offset");
-            }
-            $table->skip($offset)->take($request->get('limit'));
-        }
+        $table = $table->orderBy($request->get('order','updated_at'),$request->get('dir','desc'));
+        $table = $table->skip($request->get("offset",0))
+                       ->take($request->get('limit',1000));
         $result = $table->get();
 
 		if($result){
-			return $this->ok(["rows"=>$result,"count"=>$count]);
+			return $this->ok(["rows"=>TermResource::collection($result),"count"=>$count]);
 		}else{
 			return $this->error("没有查询到数据");
 		}
@@ -171,26 +190,21 @@ class DhammaTermController extends Controller
      */
     public function store(Request $request)
     {
-                // validate
-        // read more on validation at http://laravel.com/docs/validation
-        $rules = array(
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        $validated = $request->validate([
             'word' => 'required',
             'meaning' => 'required',
             'language' => 'required'
-        );
-        $validator = Validator::make($request->all(), $rules);
-
-        // process the login
-        if ($validator->fails()) {
-            return $this->error($validator);
-        }
-
+        ]);
         #查询重复的
         /*
         重复判定:
         一个channel下面word+tag+language 唯一
         */
-        $table = DhammaTerm::where('owner', $_COOKIE["user_uid"])
+        $table = DhammaTerm::where('owner', $user["user_uid"])
                 ->where('word',$request->get("word"))
                 ->where('tag',$request->get("tag"));
         if($request->get("channel")){
@@ -204,24 +218,34 @@ class DhammaTermController extends Controller
         if($isDoesntExist){
             #不存在插入数据
             $term = new DhammaTerm;
-            $term->id=app('snowflake')->id();
-            $term->guid=Str::uuid();
-            $term->word=$request->get("word");
-            $term->meaning=$request->get("meaning");
+            $term->id = app('snowflake')->id();
+            $term->guid = Str::uuid();
+            $term->word = $request->get("word");
+            $term->word_en = Tools::getWordEn($request->get("word"));
+            $term->meaning = $request->get("meaning");
+            $term->other_meaning = $request->get("other_meaning");
+            $term->note = $request->get("note");
+            $term->tag = $request->get("tag");
+            $term->channal = $request->get("channal");
+            $term->language = $request->get("language");
+            if($request->has("channal")){
+                $channelInfo = ChannelApi::getById($request->get("channal"));
+                if(!$channelInfo){
+                    return $this->error("channel id failed");
+                }else{
+                    $term->owner = $channelInfo['studio_id'];
+                }
+            }else{
+                $term->owner = StudioApi::getIdByName($request->get("studioName"));
+            }
+            $term->editor_id = $user["user_id"];
+            $term->create_time = time()*1000;
+            $term->modify_time = time()*1000;
             $term->save();
-            return $this->ok($data);
-
+            return $this->ok($term);
         }else{
-            return $this->error("word existed");
+            return $this->error("word existed",[],200);
         }
-        // store
-        /*
-        $data = $request->all();
-        $data['id'] = app('snowflake')->id();
-        $data['guid'] = Str::uuid();
-        DhammaTerm::create($data);
-        */
-
 
     }
 
@@ -234,11 +258,9 @@ class DhammaTermController extends Controller
     public function show(Request  $request,$id)
     {
         //
-		$indexCol = ['id','guid','word','meaning','other_meaning','note','language','channal','created_at','updated_at'];
-
-		$result  = DhammaTerm::select($indexCol)->where('guid', $id)->first();
+		$result  = DhammaTerm::where('guid', $id)->first();
 		if($result){
-			return $this->ok($result);
+			return $this->ok(new TermResource($result));
 		}else{
 			return $this->error("没有查询到数据");
 		}
@@ -252,9 +274,44 @@ class DhammaTermController extends Controller
      * @param  \App\Models\DhammaTerm  $dhammaTerm
      * @return \Illuminate\Http\Response
      */
-    public function update(Request $request, DhammaTerm $dhammaTerm)
+    public function update(Request $request, string $id)
     {
         //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        $dhammaTerm = DhammaTerm::find($id);
+        $dhammaTerm->word = $request->get("word");
+        $dhammaTerm->word_en = Tools::getWordEn($request->get("word"));
+        $dhammaTerm->meaning = $request->get("meaning");
+        $dhammaTerm->other_meaning = $request->get("other_meaning");
+        $dhammaTerm->note = $request->get("note");
+        $dhammaTerm->tag = $request->get("tag");
+        $dhammaTerm->channal = $request->get("channal");
+        $dhammaTerm->language = $request->get("language");
+        if($request->has("channal") && Str::isUuid($request->has("channal"))){
+            $channelInfo = ChannelApi::getById($request->get("channal"));
+            if(!$channelInfo){
+                return $this->error("channel id failed");
+            }else{
+                $dhammaTerm->owner = $channelInfo['studio_id'];
+            }
+        }
+        if($request->has("studioName")){
+            $dhammaTerm->owner = StudioApi::getIdByName($request->get("studioName"));
+        }else if($request->has("studioId")){
+            $dhammaTerm->owner = $request->get("studioId");
+        }else{
+            $dhammaTerm->owner = null;
+        }
+
+        $dhammaTerm->editor_id = $user["user_id"];
+        $dhammaTerm->create_time = time()*1000;
+        $dhammaTerm->modify_time = time()*1000;
+        $dhammaTerm->save();
+		return $this->ok($dhammaTerm);
+
     }
 
     /**
@@ -268,28 +325,34 @@ class DhammaTermController extends Controller
         /**
          * 一次删除多个单词
          */
-        if(isset($_COOKIE["user_uid"])){
-            $user_uid = $_COOKIE["user_uid"];
-        }else{
-            $user = AuthApi::current($request);
-            if(!$user){
-                return $this->error(__('auth.failed'));
-            }
-            $user_uid = $user['user_uid'];
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
         }
-
+        $count = 0;
         if($request->has("uuid")){
-            $count = DhammaTerm::whereIn('guid', $request->get("id"))
-                            ->where('owner', $user_uid)
-                            ->delete();
+            //查看是否有删除权限
+            foreach ($request->get("id") as $key => $uuid) {
+                $term = DhammaTerm::find($uuid);
+                if($term->owner !== $user['user_uid']){
+                    if(!empty($term->channal)){
+                        //看是否为协作
+                        $power = ShareApi::getResPower($user['user_uid'],$term->channal);
+                        if($power < 20){
+                            continue;
+                        }
+                    }else{
+                        continue;
+                    }
+                }
+                $count += $term->delete();
+            }
         }else{
             $arrId = json_decode($request->get("id"),true) ;
-            $count = 0;
-
             foreach ($arrId as $key => $id) {
                 # code...
                 $result = DhammaTerm::where('id', $id)
-                                ->where('owner', $user_uid)
+                                ->where('owner', $user['user_uid'])
                                 ->delete();
                 if($result){
                     $count++;
@@ -299,4 +362,219 @@ class DhammaTermController extends Controller
 
 		return $this->ok($count);
     }
+
+    public function export(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+//TODO 判断是否有导出权限
+        switch ($request->get("view")) {
+            case 'channel':
+                # code...
+                $rows = DhammaTerm::where('channal',$request->get("id"))->cursor();
+                break;
+            case 'studio':
+                # code...
+                $studioId = StudioApi::getIdByName($request->get("name"));
+                $rows = DhammaTerm::where('owner',$studioId)->cursor();
+                break;
+            default:
+                $this->error('no view');
+                break;
+        }
+
+        $spreadsheet = new Spreadsheet();
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+        $activeWorksheet->setCellValue('A1', 'id');
+        $activeWorksheet->setCellValue('B1', 'word');
+        $activeWorksheet->setCellValue('C1', 'meaning');
+        $activeWorksheet->setCellValue('D1', 'other_meaning');
+        $activeWorksheet->setCellValue('E1', 'note');
+        $activeWorksheet->setCellValue('F1', 'tag');
+        $activeWorksheet->setCellValue('G1', 'language');
+        $activeWorksheet->setCellValue('H1', 'channel_id');
+
+        $currLine = 2;
+        foreach ($rows as $key => $row) {
+            # code...
+            $activeWorksheet->setCellValue("A{$currLine}", $row->guid);
+            $activeWorksheet->setCellValue("B{$currLine}", $row->word);
+            $activeWorksheet->setCellValue("C{$currLine}", $row->meaning);
+            $activeWorksheet->setCellValue("D{$currLine}", $row->other_meaning);
+            $activeWorksheet->setCellValue("E{$currLine}", $row->note);
+            $activeWorksheet->setCellValue("F{$currLine}", $row->tag);
+            $activeWorksheet->setCellValue("G{$currLine}", $row->language);
+            $activeWorksheet->setCellValue("H{$currLine}", $row->channal);
+            $currLine++;
+        }
+        $writer = new Xlsx($spreadsheet);
+        $fId = Str::uuid();
+        $filename = storage_path("app/tmp/{$fId}");
+        $writer->save($filename);
+        Cache::put("download/tmp/{$fId}",file_get_contents($filename),300);
+        unlink($filename);
+        return $this->ok(['uuid'=>$fId,'filename'=>"term.xlsx",'type'=>"application/vnd.ms-excel"]);
+    }
+
+    public function import(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        /**
+         * 判断是否有权限
+         */
+        switch ($request->get('view')) {
+            case 'channel':
+                # 向channel里面导入,忽略源数据的channel id 和 owner 都设置为这个channel 的
+                $channel = ChannelApi::getById($request->get('id'));
+                $owner_id = $channel['studio_id'];
+                if($owner_id !== $user["user_uid"]){
+                    //判断是否为协作
+                    $power = ShareApi::getResPower($user["user_uid"],$request->get('id'));
+                    if($power<30){
+                        return $this->error(__('auth.failed'),[],403);
+                    }
+                }
+                $language = $channel['lang'];
+                break;
+            case 'studio':
+                # 向 studio 里面导入,忽略源数据的 owner 但是要检测 channel id 是否有权限
+                $owner_id = StudioApi::getIdByName($request->get('name'));
+                if(!$owner_id){
+                    return $this->error('no studio name',[],403);
+                }
+
+                break;
+        }
+
+        $message = "";
+        $filename = $request->get('filename');
+        $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
+        $reader->setReadDataOnly(true);
+        $spreadsheet = $reader->load($filename);
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+        $currLine = 2;
+        $countFail = 0;
+
+        do {
+            # code...
+            $id = $activeWorksheet->getCell("A{$currLine}")->getValue();
+            $word = $activeWorksheet->getCell("B{$currLine}")->getValue();
+            $meaning = $activeWorksheet->getCell("C{$currLine}")->getValue();
+            $other_meaning = $activeWorksheet->getCell("D{$currLine}")->getValue();
+            $note = $activeWorksheet->getCell("E{$currLine}")->getValue();
+            $tag = $activeWorksheet->getCell("F{$currLine}")->getValue();
+            $language = $activeWorksheet->getCell("G{$currLine}")->getValue();
+            $channel_id = $activeWorksheet->getCell("H{$currLine}")->getValue();
+            $query = ['word'=>$word,'tag'=>$tag];
+            $channelId = null;
+            switch ($request->get('view')) {
+                case 'channel':
+                    # 向channel里面导入,忽略源数据的channel id 和 owner 都设置为这个channel 的
+                    $query['channal'] = $request->get('id');
+                    $channelId = $request->get('id');
+                    break;
+                case 'studio':
+                    # 向 studio 里面导入,忽略源数据的owner 但是要检测 channel id 是否有权限
+                    $query['owner'] = $owner_id;
+                    if(!empty($channel_id)){
+
+                        //有channel 数据,查看是否在studio中
+                        $channel = ChannelApi::getById($channel_id);
+                        if($channel === false){
+                            $message .= "没有查到版本信息:{$channel_id} - {$word}\n";
+                            $currLine++;
+                            $countFail++;
+                            continue 2;
+                        }
+                        if($owner_id != $channel['studio_id']){
+                            $message .= "版本不在studio中:{$channel_id} - {$word}\n";
+                            $currLine++;
+                            $countFail++;
+                            continue 2;
+                        }
+                        $query['channal'] = $channel_id;
+                        $channelId = $channel_id;
+                    }
+                    # code...
+                    break;
+            }
+
+            if(empty($id) && empty($word)){
+                break;
+            }
+
+            //查询此id是否有旧数据
+            if(!empty($id)){
+                $oldRow = DhammaTerm::find($id);
+                //TODO 有 id 无 word 删除数据
+                if(empty($word)){
+                    //查看权限
+                    if($oldRow->owner !== $user['user_uid']){
+                        if(!empty($oldRow->channal)){
+                            //看是否为协作
+                            $power = ShareApi::getResPower($user['user_uid'],$oldRow->channal);
+                            if($power < 20){
+                                $message .= "无删除权限:{$id} - {$word}\n";
+                                $currLine++;
+                                $countFail++;
+                                continue;
+                            }
+                        }else{
+                            $message .= "无删除权限:{$id} - {$word}\n";
+                            $currLine++;
+                            $countFail++;
+                            continue;
+                        }
+                    }
+                    //删除
+                    $oldRow->delete();
+                    $currLine++;
+                    continue;
+                }
+            }else{
+                $oldRow = null;
+            }
+            //查询是否跟已有数据重复
+            $row = DhammaTerm::where($query)->first();
+            if(!$row){
+                //不重复
+                if(isset($oldRow) && $oldRow){
+                    //找到旧的记录-修改旧数据
+                    $row = $oldRow;
+                }else{
+                    //没找到旧的记录-新建
+                    $row = new DhammaTerm();
+                    $row->id = app('snowflake')->id();
+                    $row->guid = Str::uuid();
+                    $row->word = $word;
+                    $row->create_time = time()*1000;
+                }
+            }else{
+                //重复-如果与旧的id不同,报错
+                if(isset($oldRow) && $oldRow && $row->guid !== $id){
+                    $message .= "重复的数据:{$id} - {$word}\n";
+                    $currLine++;
+                    $countFail++;
+                    continue;
+                }
+            }
+            $row->word_en = Tools::getWordEn($word);
+            $row->meaning = $meaning;
+            $row->other_meaning = $other_meaning;
+            $row->note = $note;
+            $row->tag = $tag;
+            $row->language = $language;
+            $row->channal = $channelId;
+            $row->editor_id = $user['user_id'];
+            $row->owner = $owner_id;
+            $row->modify_time = time()*1000;
+            $row->save();
+
+            $currLine++;
+        } while (true);
+        return $this->ok(["success"=>$currLine-2-$countFail,'fail'=>($countFail)],$message);
+    }
 }

+ 71 - 13
app/Http/Controllers/DictController.php

@@ -7,6 +7,7 @@ use App\Models\DictInfo;
 use Illuminate\Http\Request;
 use App\Tools\CaseMan;
 use Illuminate\Support\Facades\Log;
+use App\Http\Api\DictApi;
 
 require_once __DIR__."/../../../public/app/dict/grm_abbr.php";
 
@@ -30,6 +31,8 @@ class DictController extends Controller
         $word_base = [];
         $searched = [];
         $words[$request->get('word')] = [];
+        $userLang = $request->get('lang',"zh");
+
         for ($i=0; $i < 2; $i++) {
             # code...
             $word_base = [];
@@ -47,26 +50,82 @@ class DictController extends Controller
                     'anchor'=> $anchor,
                     'dict' => [],
                 ];
+                /**
+                 * 按照语言调整词典顺序
+                 * 算法:准备理想的词典顺序容器。
+                 * 将查询的结果放置在对应的容器中。
+                 * 最后将结果扁平化
+                 * 准备字典容器
+                * $wordDict = [
+                *    "zh"=>[
+                *        "0d79e8e8-1430-4c99-a0f1-b74f2b4b26d8"=>[];
+                *    ]
+                * ]
+                 */
+
+                foreach (DictApi::langOrder($userLang) as  $langId) {
+                    # code...
+                    $dictContainer = [];
+                    foreach (DictApi::dictOrder($langId) as $dictId) {
+                        $dictContainer[$dictId] = [];
+                    }
+                    $wordDict[$langId] = $dictContainer;
+                }
                 $dictList=[
                     'href'=> '#'.$anchor,
                     'title'=> "{$word}",
+                    'children' => [],
                 ];
                 foreach ($result as $key => $value) {
                     # code...
                     $dictInfo= DictInfo::find($value->dict_id);
-
+                    $dict_lang = explode('-',$dictInfo->dest_lang);
                     $anchor = "{$word}-{$dictInfo->shortname}";
-                    $wordData['dict'][] = [
-                        'dictname'=> $dictInfo->name,
-                        'word'=> $word,
-                        'note'=> $this->GrmAbbr($value->note,0),
-                        'anchor'=> $anchor,
-                    ];
-                    $dictList['children'][] = [
-                        'href'=> '#'.$anchor,
-                        'title'=> "{$dictInfo->shortname}",
+                    $currData = [
+                            'dictname'=> $dictInfo->name,
+                            'shortname'=> $dictInfo->shortname,
+                            'dict_id' => $value->dict_id,
+                            'lang' => $dict_lang[0],
+                            'word'=> $word,
+                            'note'=> $this->GrmAbbr($value->note,0),
+                            'anchor'=> $anchor,
                     ];
+                    if(isset($wordDict[$dict_lang[0]])){
+                        if(isset($wordDict[$dict_lang[0]][$value->dict_id])){
+                            array_push($wordDict[$dict_lang[0]][$value->dict_id],$currData);
+                        }else{
+                            array_push($wordDict[$dict_lang[0]]["others"],$currData);
+                        }
+                    }else{
+                        array_push($wordDict['others']['others'],$currData);
+                    }
                 }
+                /**
+                 * 把树状数据变为扁平数据
+                 */
+                foreach ($wordDict as $oneLang) {
+                    # code...
+                    foreach ($oneLang as $langId => $dictId) {
+                        # code...
+                        foreach ($dictId as $oneData) {
+                            # code...
+                            $wordData['dict'][] = $oneData;
+                            if(isset($dictList['children']) && count($dictList['children'])>0){
+                                $lastHref = end($dictList['children'])['href'];
+                            }else{
+                                $lastHref = '';
+                            }
+                            $currHref = '#'.$oneData['anchor'];
+                            if($lastHref !== $currHref){
+                                $dictList['children'][] = [
+                                    'href'=> $currHref,
+                                    'title'=> $oneData['shortname'],
+                                ];
+                            }
+                        }
+                    }
+                }
+
                 $wordDataOutput[]=$wordData;
                 $dictListOutput[]=$dictList;
 
@@ -77,7 +136,6 @@ class DictController extends Controller
                     # code...
                     if(!in_array($base,$searched)){
                         $word_base[$base] = $case;
-                        Log::info($case);
                     }
                 }
             }
@@ -153,11 +211,11 @@ class DictController extends Controller
             # code...
             if($dictid !== 0){
                 if($value["dictid"]=== $dictid && strpos($input,$value["abbr"]."|") == false){
-                    $mean = str_ireplace($value["abbr"],"|@{$value["abbr"]}-grammar_{$value["replace"]}",$mean);
+                    $mean = str_ireplace($value["abbr"],"|@{$value["abbr"]}-{$value["replace"]}",$mean);
                 }
             }else{
                 if( strpos($mean,"|@".$value["abbr"]) == false){
-                    $mean = str_ireplace($value["abbr"],"|@{$value["abbr"]}-grammar_{$value["replace"]}|",$mean);
+                    $mean = str_ireplace($value["abbr"],"|@{$value["abbr"]}-{$value["replace"]}|",$mean);
                 }
             }
 

+ 127 - 0
app/Http/Controllers/DictMeaningController.php

@@ -0,0 +1,127 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\UserDict;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+
+class DictMeaningController extends Controller
+{
+    protected $langOrder = [
+        "zh-Hans"=>[
+            "zh-Hans","zh-Hant","jp","en","my","vi"
+        ],
+        "zh-Hant"=>[
+            "zh-Hant","zh-Hans","jp","en","my","vi"
+        ],
+        "en"=>[
+            "en","my","zh-Hant","zh-Hans","jp","vi"
+        ],
+        "jp"=>[
+            "jp","en","my","zh-Hant","zh-Hans","vi"
+        ],
+    ];
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        $words = explode("-",$request->get('word'));
+        $lang = $request->get('lang');
+        $key = "dict_first_mean/";
+        $meaning = [];
+        foreach ($words as $key => $word) {
+            # code...
+            $meaning[] = ['word'=>$word,'meaning'=>$this->get($word,$lang)];
+        }
+
+        return $this->ok($meaning);
+    }
+
+    public function get(string $word,string $lang){
+        $currMeaning = "";
+        if(isset($this->langOrder[$lang])){
+            foreach ($this->langOrder[$lang] as $key => $value) {
+                # 遍历每种语言。找到返回
+                $cacheKey = "dict_first_mean/{$value}/{$word}";
+                $meaning = Cache::get($cacheKey);
+                if(!empty($meaning)){
+                    $currMeaning = $meaning;
+                    break;
+                }
+            }
+        }
+        return $currMeaning;
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\UserDict  $userDict
+     * @return \Illuminate\Http\Response
+     */
+    public function show(UserDict $userDict)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  \App\Models\UserDict  $userDict
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(UserDict $userDict)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\UserDict  $userDict
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, UserDict $userDict)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\UserDict  $userDict
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(UserDict $userDict)
+    {
+        //
+    }
+}

+ 54 - 10
app/Http/Controllers/DiscussionController.php

@@ -6,6 +6,7 @@ use App\Models\Discussion;
 use App\Models\Wbw;
 use App\Models\WbwBlock;
 use App\Models\PaliSentence;
+use App\Models\Sentence;
 use Illuminate\Http\Request;
 use App\Http\Resources\DiscussionResource;
 use App\Http\Api\MdRender;
@@ -62,6 +63,39 @@ class DiscussionController extends Controller
 		}
     }
 
+    public function discussion_tree(Request $request){
+        $output = [];
+        $sentences = $request->get("data");
+        foreach ($sentences as $key => $sentence) {
+            # 先查句子信息
+            $sentInfo = Sentence::where('book_id',$sentence['book'])
+                                ->where('paragraph',$sentence['paragraph'])
+                                ->where('word_start',$sentence['word_start'])
+                                ->where('word_end',$sentence['word_end'])
+                                ->where('channel_uid',$sentence['channel_id'])
+                                ->first();
+            if($sentInfo){
+                $sentPr = Discussion::where('res_id',$sentInfo['uid'])
+                                ->whereNull('parent')
+                                ->select('title','children_count','editor_uid')
+                                ->orderBy('created_at','desc')->get();
+                $output[] = [
+                    'sentence' => [
+                        'book' => $sentInfo->book_id,
+                        'paragraph' => $sentInfo->paragraph,
+                        'word_start' => $sentInfo->word_start,
+                        'word_end' => $sentInfo->word_end,
+                        'channel_id' => $sentInfo->channel_uid,
+                        'content' => $sentInfo->content,
+                        'pr_count' => count($sentPr),
+                    ],
+                    'pr' => $sentPr,
+                ];
+            }
+
+        }
+        return $this->ok(['rows'=>$output,'count'=>count($output)]);
+    }
     /**
      * Store a newly created resource in storage.
      *
@@ -77,31 +111,41 @@ class DiscussionController extends Controller
         //
         // validate
         // read more on validation at http://laravel.com/docs/validation
-        $rules = array(
+
+        if($request->has('parent')){
+            $rules = [];
+            $parentInfo = Discussion::find($request->get('parent'));
+            if(!$parentInfo){
+                return $this->error('no record');
+            }
+        }else{
+            $rules = array(
             'res_id' => 'required',
             'res_type' => 'required',
+            'title' => 'required',
         );
-        if(!$request->has('parent')){
-            $rules['title'] = 'required';
         }
 
         $validated = $request->validate($rules);
 
         $discussion = new Discussion;
-        $discussion->res_id = $request->get('res_id');
-        $discussion->res_type = $request->get('res_type');
+        if($request->has('parent')){
+            $discussion->res_id = $parentInfo->res_id;
+            $discussion->res_type = $parentInfo->res_type;
+        }else{
+            $discussion->res_id = $request->get('res_id');
+            $discussion->res_type = $request->get('res_type');
+        }
         $discussion->title = $request->get('title',null);
         $discussion->content = $request->get('content',null);
+        $discussion->content_type = $request->get('content_type',"markdown");
         $discussion->parent = $request->get('parent',null);
         $discussion->editor_uid = $user['user_uid'];
         $discussion->save();
         //更新parent children_count
         if($request->has('parent')){
-            $parent = Discussion::find($request->get('parent'));
-            if($parent){
-                $parent->increment('children_count',1);
-                $parent->save();
-            }
+            $parentInfo->increment('children_count',1);
+            $parentInfo->save();
         }
 
         return $this->ok(new DiscussionResource($discussion));

+ 83 - 0
app/Http/Controllers/GrammarGuideController.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\DhammaTerm;
+use App\Http\Api\ChannelApi;
+use Illuminate\Http\Request;
+
+class GrammarGuideController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\DhammaTerm  $dhammaTerm
+     * @return \Illuminate\Http\Response
+     */
+    public function show(string $id)
+    {
+        //
+        $param = explode('_',$id);
+
+        $localTermChannel = ChannelApi::getSysChannel(
+            "_System_Grammar_Term_".strtolower($param[1])."_",
+            "_System_Grammar_Term_en_"
+        );
+        if(!$localTermChannel){
+            return $this->error('no term channel');
+        }
+        $result = DhammaTerm::where('word',$param[0])
+                    ->where('channal',$localTermChannel)->first();
+
+        if($result){
+            return $this->ok("# {$result->meaning}\n {$result->note}");
+        }else{
+            return $this->ok("# {$id}\n no record");
+        }
+
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\DhammaTerm  $dhammaTerm
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, DhammaTerm $dhammaTerm)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\DhammaTerm  $dhammaTerm
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(DhammaTerm $dhammaTerm)
+    {
+        //
+    }
+}

+ 73 - 44
app/Http/Controllers/GroupController.php

@@ -10,9 +10,9 @@ use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\DB;
 use App\Http\Api\AuthApi;
 use App\Http\Api\StudioApi;
+use App\Http\Resources\GroupResource;
 
 
-require_once __DIR__.'/../../../public/app/ucenter/function.php';
 class GroupController extends Controller
 {
     /**
@@ -23,27 +23,40 @@ class GroupController extends Controller
     public function index(Request $request)
     {
         //
-        $userinfo = new \UserInfo();
 		$result=false;
 		$indexCol = ['uid','name','description','owner','updated_at','created_at'];
 		switch ($request->get('view')) {
             case 'studio':
-	            # 获取studio内所有channel
+	            # 获取studio内所有group
                 $user = AuthApi::current($request);
-                if($user){
-                    //判断当前用户是否有指定的studio的权限
-                    if($user['user_uid'] === StudioApi::getIdByName($request->get('name'))){
-                        $table = GroupInfo::select($indexCol)->where('owner', $user["user_uid"]);
-                    }else{
-                        return $this->error(__('auth.failed'));
-                    }
-                }else{
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                //判断当前用户是否有指定的studio的权限
+                $studioId = StudioApi::getIdByName($request->get('name'));
+                if($user['user_uid'] !== $studioId){
                     return $this->error(__('auth.failed'));
                 }
+
+                $table = GroupInfo::select($indexCol);
+                if($request->get('view2','my')==='my'){
+                    $table = $table->where('owner', $studioId);
+                }else{
+                    //我参加的group
+                    $groupId = GroupMember::where('user_id',$studioId)
+                                          ->groupBy('group_id')
+                                          ->select('group_id')
+                                          ->get();
+                    $table = $table->whereIn('uid', $groupId);
+                    $table = $table->where('owner','<>', $studioId);
+                }
 				break;
+            case 'key':
+                $table = GroupInfo::select($indexCol)->where('name','like', $request->get('key')."%");
+                break;
         }
-        if(isset($_GET["search"])){
-            $table = $table->where('title', 'like', $_GET["search"]."%");
+        if($request->has("search")){
+            $table = $table->where('name', 'like', "%" . $request->get("search")."%");
         }
         $count = $table->count();
         if(isset($_GET["order"]) && isset($_GET["dir"])){
@@ -65,29 +78,34 @@ class GroupController extends Controller
         }
         $result = $table->get();
 		if($result){
-            foreach ($result as $key => $value) {
-                # code...
-                $value->role = 'owner';
-                $value->studio = [
-                    'id'=>$value->owner,
-                    'nickName'=>$userinfo->getName($value->owner)['nickname'],
-                    'studioName'=>$userinfo->getName($value->owner)['username'],
-                    'avastar'=>'',
-                    'owner' => [
-                        'id'=>$value->owner,
-                        'nickName'=>$userinfo->getName($value->owner)['nickname'],
-                        'userName'=>$userinfo->getName($value->owner)['username'],
-                        'avastar'=>'',
-                    ]
-                ];
-            }
-			return $this->ok(["rows"=>$result,"count"=>$count]);
+			return $this->ok(["rows"=>GroupResource::collection($result),"count"=>$count]);
 		}else{
 			return $this->error("没有查询到数据");
 		}
 
     }
+    /**
+     * 获取我的,和协作channel数量
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function showMyNumber(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //判断当前用户是否有指定的studio的权限
+        $studioId = StudioApi::getIdByName($request->get('studio'));
+        if($user['user_uid'] !== $studioId){
+            return $this->error(__('auth.failed'));
+        }
+        //我的
+        $my = GroupMember::where('user_id', $studioId)->where('power',0)->count();
+        //协作
+        $collaboration = GroupMember::where('user_id', $studioId)->where('power','<>',0)->count();
 
+        return $this->ok(['my'=>$my,'collaboration'=>$collaboration]);
+    }
     /**
      * Store a newly created resource in storage.
      *
@@ -109,15 +127,26 @@ class GroupController extends Controller
         if(GroupInfo::where('name',$request->get('name'))->where('owner',$user['user_uid'])->exists()){
             return $this->error(__('validation.exists',['name']));
         }
-
+        $studioId = StudioApi::getIdByName($request->get('studio_name'));
         $group = new GroupInfo;
-        $group->id = app('snowflake')->id();
-        $group->uid = Str::uuid();
-        $group->name = $request->get('name');
-        $group->owner = $user['user_uid'];
-        $group->create_time = time()*1000;
-        $group->modify_time = time()*1000;
-        $group->save();
+        DB::transaction(function() use($group,$request,$user,$studioId){
+            $group->id = app('snowflake')->id();
+            $group->uid = Str::uuid();
+            $group->name = $request->get('name');
+            $group->owner = $studioId;
+            $group->create_time = time()*1000;
+            $group->modify_time = time()*1000;
+            $group->save();
+
+            $newMember = new GroupMember();
+            $newMember->id=app('snowflake')->id();
+            $newMember->user_id = $studioId;
+            $newMember->group_id = $group->uid;
+            $newMember->power = 0;
+            $newMember->group_name = $request->get('name');
+            $newMember->save();
+        });
+
         return $this->ok($group);
     }
 
@@ -149,30 +178,30 @@ class GroupController extends Controller
                 return $this->error(__('auth.failed'));
             }
         }
-        return $this->ok($result);
+        return $this->ok(new GroupResource($result));
     }
 
     /**
      * Update the specified resource in storage.
      *
      * @param  \Illuminate\Http\Request  $request
-     * @param  \App\Models\Group  $group
+     * @param  \App\Models\GroupInfo  $group
      * @return \Illuminate\Http\Response
      */
-    public function update(Request $request, Group $group)
+    public function update(Request $request, GroupInfo $group)
     {
         //
         $user = AuthApi::current($request);
         if(!$user){
             return $this->error(__('auth.failed'));
         }
-        //判断当前用户是否有指定的studio的权限
-        if($user['user_uid'] !== StudioApi::getIdByName($request->get('studio'))){
+        //判断当前用户是否有修改权限
+        if($user['user_uid'] !== $group->owner){
             return $this->error(__('auth.failed'));
         }
         $group->name = $request->get('name');
         $group->description = $request->get('description');
-        $group->status = $request->get('status');
+        if($request->has('status')) { $group->status = $request->get('status'); }
         $group->create_time = time()*1000;
         $group->modify_time = time()*1000;
         $group->save();

+ 9 - 9
app/Http/Controllers/GroupMemberController.php

@@ -24,17 +24,17 @@ class GroupMemberController extends Controller
             case 'group':
 	            # 获取 group 内所有 成员
                 $user = AuthApi::current($request);
-                if($user){
-                    //TODO 判断当前用户是否有指定的 group 的权限
-
-                    if(GroupInfo::where('uid',$request->get('id'))->where('owner',$user['user_uid'])->exists()){
-                        $table = GroupMember::where('group_id', $request->get('id'));
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                    //判断当前用户是否有指定的 group 的权限
+                    if(GroupMember::where('group_id', $request->get('id'))
+                            ->where('user_id',$user['user_uid'])
+                            ->exists()){
+                                $table = GroupMember::where('group_id', $request->get('id'));
                     }else{
                         return $this->error(__('auth.failed'));
                     }
-                }else{
-                    return $this->error(__('auth.failed'));
-                }
 				break;
         }
         if(isset($_GET["search"])){
@@ -78,7 +78,7 @@ class GroupMemberController extends Controller
 		if($result){
 			return $this->ok(["rows"=>GroupMemberResource::collection($result),"count"=>$count,'role'=>$role]);
 		}else{
-			return $this->error("没有查询到数据");
+			return $this->error("没有查询到数据",[],200);
 		}
     }
 

+ 340 - 0
app/Http/Controllers/NissayaEndingController.php

@@ -0,0 +1,340 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\NissayaEnding;
+use App\Models\Relation;
+use App\Models\DhammaTerm;
+use Illuminate\Http\Request;
+use App\Http\Resources\NissayaEndingResource;
+use App\Http\Api\AuthApi;
+use App\Http\Api\ChannelApi;
+use Illuminate\Support\Facades\App;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+use mustache\mustache;
+
+class NissayaEndingController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        $table = NissayaEnding::select(['id','ending','lang','relation','case','count','editor_id','updated_at']);
+
+        if(($request->has('case'))){
+            $table->whereIn('case', explode(",",$request->get('case')) );
+        }
+
+        if(($request->has('lang'))){
+            $table->whereIn('lang', explode(",",$request->get('lang')) );
+        }
+
+        if(($request->has('relation'))){
+            $table->where('relation', $request->get('relation'));
+        }
+
+        if(($request->has('search'))){
+            $table->where('ending', 'like', $request->get('search')."%");
+        }
+        if(!empty($request->get('order')) && !empty($request->get('dir'))){
+            $table->orderBy($request->get('order'),$request->get('dir'));
+        }else{
+            $table->orderBy('updated_at','desc');
+        }
+        $count = $table->count();
+        if(!empty($request->get('limit'))){
+            $offset = 0;
+            if(!empty($request->get("offset"))){
+                $offset = $request->get("offset");
+            }
+            $table->skip($offset)->take($request->get('limit'));
+        }
+        $result = $table->get();
+
+		if($result){
+			return $this->ok(["rows"=>NissayaEndingResource::collection($result),"count"=>$count]);
+		}else{
+			return $this->error("没有查询到数据");
+		}
+    }
+
+    public function vocabulary(Request $request){
+        $result = NissayaEnding::select(['ending'])
+                              ->where('lang', $request->get('lang') )
+                              ->groupBy('ending')
+                              ->get();
+        return $this->ok(["rows"=>$result,"count"=>count($result)]);
+    }
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //TODO 判断权限
+        $validated = $request->validate([
+            'ending' => 'required',
+            'lang' => 'required',
+        ]);
+        $new = new NissayaEnding;
+        $new->ending = $validated['ending'];
+        $new->strlen = mb_strlen($validated['ending'],"UTF-8") ;
+        $new->lang = $validated['lang'];
+        $new->relation = $request->get('relation');
+        $new->case = $request->get('case');
+        $new->editor_id = $user['user_uid'];
+        $new->save();
+        return $this->ok(new NissayaEndingResource($new));
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\NissayaEnding  $nissayaEnding
+     * @return \Illuminate\Http\Response
+     */
+    public function show(NissayaEnding $nissayaEnding)
+    {
+        //
+        return $this->ok(new NissayaEndingResource($nissayaEnding));
+
+    }
+
+    public function nissaya_card(Request $request)
+    {
+        //
+        $cardData = [];
+        App::setLocale($request->get('lang'));
+        $localTerm = ChannelApi::getSysChannel(
+                                "_System_Grammar_Term_".strtolower($request->get('lang'))."_",
+                                "_System_Grammar_Term_en_"
+                            );
+        if(!$localTerm){
+            return $this->error('no term channel');
+        }
+        $termTable = DhammaTerm::where('channal',$localTerm);
+        $cardData['ending'] = $request->get('ending');
+        $endingTerm = $termTable->where('word',$request->get('ending'))->first();
+        if($endingTerm){
+            $cardData['ending_tag'] = $endingTerm->tag;
+            $cardData['ending_meaning'] = $endingTerm->meaning;
+            $cardData['ending_note'] = $endingTerm->note;
+        }
+
+        $myEnding = NissayaEnding::where('ending',$request->get('ending'))
+                                 ->groupBy('relation')
+                                 ->select('relation')->get();
+        if(count($myEnding) === 0){
+            if(!isset($cardData['ending_note'])){
+                $cardData['ending_note'] = "no record\n";
+            }
+        }
+
+        $relations = Relation::whereIn('name',$myEnding)->get();
+        if(count($relations) > 0){
+            $cardData['title_case'] = "格位";
+            $cardData['title_content'] = "含义";
+            $cardData['title_local_ending'] = "翻译建议";
+            $cardData['title_local_relation'] = "关系";
+            $cardData['title_relation'] = "关系";
+            foreach ($relations as $key => $relation) {
+                $relationInTerm = DhammaTerm::where('channal',$localTerm)->where('word',$relation['name'])->first();
+                if(empty($relation->case)){
+                    $cardData['row'][] = ["relation"=>$relation->name];
+                    continue;
+                }
+                $case = $relation->case;
+                # 格位
+                $newLine['case'] = __("grammar.".$case);
+                //含义
+                if($relationInTerm){
+                    $newLine['other_meaning'] = $relationInTerm->other_meaning;
+                    $newLine['note'] = $relationInTerm->note;
+                    if(!empty($relationInTerm->note)){
+                        $newLine['summary'] = explode("\n",$relationInTerm->note)[0];
+                    }
+                }
+                //翻译建议
+                $localEnding = '';
+                $localEndingRecord = NissayaEnding::where('relation',$relation['name'])
+                                                ->where('lang',$request->get('lang'));
+                if(!empty($case)){
+                    $localEndingRecord = $localEndingRecord->where('case',$case);
+                }
+                $localLangs = $localEndingRecord->get();
+                foreach ($localLangs as $localLang) {
+                    # code...
+                    $localEnding .= $localLang->ending.",";
+                }
+                $newLine['local_ending'] = $localEnding;
+
+                //本地语言 关系名称
+                if($relationInTerm){
+                    $newLine['local_relation'] =  $relationInTerm->meaning;
+                }
+                //关系名称
+                $newLine['relation'] =  strtoupper($relation['name']);
+                $cardData['row'][] = $newLine;
+            }
+        }
+
+
+        $m = new \Mustache_Engine(array('entity_flags'=>ENT_QUOTES));
+        $tpl = file_get_contents(resource_path("mustache/nissaya_ending_card.tpl"));
+        $md = $m->render($tpl,$cardData);
+        return $this->ok($md);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\NissayaEnding  $nissayaEnding
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, NissayaEnding $nissayaEnding)
+    {
+        //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //查询是否重复
+        if(NissayaEnding::where('ending',$request->get('ending'))
+                 ->where('lang',$request->get('lang'))
+                 ->where('relation',$request->get('relation'))
+                 ->where('case',$request->get('case'))
+                 ->exists()){
+            return $this->error(__('validation.exists',['name']));
+        }
+        $nissayaEnding->ending = $request->get('ending');
+        $nissayaEnding->strlen = mb_strlen($request->get('ending'),"UTF-8") ;
+        $nissayaEnding->lang = $request->get('lang');
+        $nissayaEnding->relation = $request->get('relation');
+        $nissayaEnding->case = $request->get('case');
+        $nissayaEnding->editor_id = $user['user_uid'];
+        $nissayaEnding->save();
+        return $this->ok(new NissayaEndingResource($nissayaEnding));
+
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\NissayaEnding  $nissayaEnding
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request,NissayaEnding $nissayaEnding)
+    {
+        //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //TODO 判断当前用户是否有权限
+        $delete = 0;
+        $delete = $nissayaEnding->delete();
+
+        return $this->ok($delete);
+    }
+
+    public function export(){
+        $spreadsheet = new Spreadsheet();
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+        $activeWorksheet->setCellValue('A1', 'id');
+        $activeWorksheet->setCellValue('B1', 'ending');
+        $activeWorksheet->setCellValue('C1', 'lang');
+        $activeWorksheet->setCellValue('D1', 'relation');
+
+        $nissaya = NissayaEnding::cursor();
+        $currLine = 2;
+        foreach ($nissaya as $key => $row) {
+            # code...
+            $activeWorksheet->setCellValue("A{$currLine}", $row->id);
+            $activeWorksheet->setCellValue("B{$currLine}", $row->ending);
+            $activeWorksheet->setCellValue("C{$currLine}", $row->lang);
+            $activeWorksheet->setCellValue("D{$currLine}", $row->relation);
+            $activeWorksheet->setCellValue("E{$currLine}", $row->case);
+            $currLine++;
+        }
+        $writer = new Xlsx($spreadsheet);
+        header('Content-Type: application/vnd.ms-excel');
+        header('Content-Disposition: attachment; filename="nissaya-ending.xlsx"');
+        $writer->save("php://output");
+    }
+
+    public function import(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+
+        $filename = $request->get('filename');
+        $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
+        $reader->setReadDataOnly(true);
+        $spreadsheet = $reader->load($filename);
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+        $currLine = 2;
+        $countFail = 0;
+        $error = "";
+        do {
+            # code...
+            $id = $activeWorksheet->getCell("A{$currLine}")->getValue();
+            $ending = $activeWorksheet->getCell("B{$currLine}")->getValue();
+            $lang = $activeWorksheet->getCell("C{$currLine}")->getValue();
+            $relation = $activeWorksheet->getCell("D{$currLine}")->getValue();
+            $case = $activeWorksheet->getCell("E{$currLine}")->getValue();
+            if(!empty($ending)){
+                //查询是否有冲突数据
+                //查询此id是否有旧数据
+                if(!empty($id)){
+                    $oldRow = NissayaEnding::find($id);
+                }
+                //查询是否跟已有数据重复
+                $row = NissayaEnding::where(['ending'=>$ending,'relation'=>$relation,'case'=>$case])->first();
+                if(!$row){
+                    //不重复
+                    if(isset($oldRow) && $oldRow){
+                        //有旧的记录-修改旧数据
+                        $row = $oldRow;
+                    }else{
+                        //没找到旧的记录-新建
+                        $row = new NissayaEnding();
+                    }
+                }else{
+                    //重复-如果与旧的id不同旧报错
+                    if(isset($oldRow) && $oldRow && $row->id !== $id){
+                        $error .= "重复的数据:{$id} - {$word}\n";
+                        $currLine++;
+                        $countFail++;
+                        continue;
+                    }
+                }
+                $row->ending = $ending;
+                $row->strlen = mb_strlen($ending,"UTF-8") ;
+                $row->lang = $lang;
+                $row->relation = $relation;
+                $row->case = $case;
+                $row->editor_id = $user['user_uid'];
+                $row->save();
+            }else{
+                break;
+            }
+            $currLine++;
+        } while (true);
+        return $this->ok(["success"=>$currLine-2-$countFail,'fail'=>($countFail)],$error);
+    }
+}

+ 11 - 1
app/Http/Controllers/PaliTextController.php

@@ -8,6 +8,8 @@ use App\Models\BookTitle;
 use App\Models\Tag;
 use App\Models\TagMap;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
 
 class PaliTextController extends Controller
 {
@@ -131,7 +133,9 @@ class PaliTextController extends Controller
                 $chapters = $table->orderBy('paragraph')->get();
                 break;
             case 'paragraph':
-                $result = PaliText::where('book',$request->get('book'))->where('paragraph',$request->get('para'))->first();
+                $result = PaliText::where('book',$request->get('book'))
+                                  ->where('paragraph',$request->get('para'))
+                                  ->first();
                 if($result){
                     return $this->ok($result);
                 }else{
@@ -196,6 +200,12 @@ class PaliTextController extends Controller
                 break;
             }
         if($chapters){
+            if($request->get('view') !== 'book-toc'){
+                foreach ($chapters as $key => $value) {
+                    $progress_key="/chapter_dynamic/{$value->book}/{$value->paragraph}/global";
+                    $chapters[$key]->progress_line = Cache::get($progress_key);
+                }
+            }
             return $this->ok(["rows"=>$chapters,"count"=>$all_count]);
         }else{
             return $this->error("no data");

+ 66 - 20
app/Http/Controllers/ProgressChapterController.php

@@ -15,6 +15,7 @@ use App\Models\View;
 use App\Models\Like;
 use Illuminate\Http\Request;
 use App\Http\Api\StudioApi;
+use Illuminate\Support\Facades\Cache;
 
 class ProgressChapterController extends Controller
 {
@@ -26,21 +27,12 @@ class ProgressChapterController extends Controller
     public function index(Request $request)
     {
 
-        if($request->get('progress')){
-            $minProgress = (float)$request->get('progress');
-        }else{
-            $minProgress = 0.8;
-        }
-        if($request->get('offset')){
-            $offset = (int)$request->get('offset');
-        }else{
-            $offset = 0;
-        }
-        if($request->has('limit')){
-            $limit = (int)$request->get('limit');
-        }else{
-            $limit = 20;
-        }
+        $minProgress = (float)$request->get('progress',0.8);
+
+        $offset = (int)$request->get('offset',0);
+
+        $limit = (int)$request->get('limit',20);
+
         $channel_id = $request->get('channel');
 
         //
@@ -234,6 +226,8 @@ class ProgressChapterController extends Controller
                     }
                     $chapters[$key]->likes = $likes;
                     $chapters[$key]->studio = StudioApi::getById($value->channel->owner_uid);
+                    $progress_key="/chapter_dynamic/{$value->book}/{$value->para}/ch_{$value->channel_id}";
+                    $chapters[$key]->progress_line = Cache::get($progress_key);
                 }
 
                 $all_count = count($chapters);
@@ -243,6 +237,8 @@ class ProgressChapterController extends Controller
                 $pc =(new ProgressChapter)->getTable();
                 $tg = (new Tag)->getTable();
                 $pt = (new PaliText)->getTable();
+
+                //标签过滤
                 if($request->get('tags') && $request->get('tags')!==''){
                     $tags = explode(',',$request->get('tags'));
                     foreach ($tags as $tag) {
@@ -266,19 +262,17 @@ class ProgressChapterController extends Controller
                 }else{
                     $channel = "";
                 }
-
-
-
-
+                //完成度过滤
                 $param[] = $minProgress;
 
+                //语言过滤
                 if(!empty($request->get('lang'))){
                     $whereLang = " and pc.lang = ? ";
                     $param[] = $request->get('lang');
                 }else{
                     $whereLang = "   ";
                 }
-
+                //channel type过滤
 				if($request->has('channel_type') && !empty($request->get('channel_type'))){
 					$channel_type = "and ch.type = ? ";
 					$param[] = $request->get('channel_type');
@@ -361,6 +355,58 @@ class ProgressChapterController extends Controller
                 break;
             case 'top':
             break;
+            case 'search':
+                $key = $request->get('key');
+                $table = ProgressChapter::where('title','like',"%{$key}%");
+                //获取记录总条数
+                $all_count = $table->count();
+                //处理排序
+                if($request->has("order") && $request->has("dir")){
+                    $table = $table->orderBy($request->get("order"),$request->get("dir"));
+                }else{
+                    //默认排序
+                    $table = $table->orderBy('updated_at','desc');
+                }
+                //处理分页
+                if($request->has("limit")){
+                    if($request->has("offset")){
+                        $offset = $request->get("offset");
+                    }else{
+                        $offset = 0;
+                    }
+                    $table = $table->skip($offset)->take($request->get("limit"));
+                }
+                //获取数据
+                $chapters = $table->get();
+                //TODO 移到resource
+                foreach ($chapters as $key => $chapter) {
+                    # code...
+                    $chapter->toc = PaliText::where('book',$chapter->book)->where('paragraph',$chapter->para)->value('toc');
+                    $chapter->path = PaliText::where('book',$chapter->book)->where('paragraph',$chapter->para)->value('path');
+                    $chapter->channel = Channel::where('uid',$chapter->channel_id)->select(['name','owner_uid'])->first();
+                    if($chapter->channel){
+                        $chapter->studio = StudioApi::getById($chapter->channel["owner_uid"]);
+                    }else{
+                        $chapter->channel = [
+                            'name'=>"unknown",
+                            'owner_uid'=>"unknown",
+                        ];
+                        $chapter->studio = [
+                            'id'=>"",
+                            'nickName'=>"unknown",
+                            'realName'=>"unknown",
+                            'avatar'=>'',
+                        ];
+                    }
+
+                    $chapter->views = View::where("target_id",$chapter->uid)->count();
+                    $chapter->likes = Like::where(["type"=>"like","target_id"=>$chapter->uid])->count();
+                    $chapter->tags = TagMap::where("anchor_id",$chapter->uid)
+                                                ->leftJoin('tags','tag_maps.tag_id', '=', 'tags.id')
+                                                ->select(['tags.id','tags.name','tags.description'])
+                                                ->get();
+                }
+                break;
         }
 
         if($chapters){

+ 113 - 0
app/Http/Controllers/RelatedParagraphController.php

@@ -0,0 +1,113 @@
+<?php
+/*
+ *查询相关联的书
+ *mula->attakhata->tika
+ *算法:
+ *在原始的html 文件里 如 s0404m1.mul.htm 有 <a name="para2_an8"></a>
+ * 在 so404a.att.htm 里也有 </a><a name="para2_an8"></a>
+ * 这说明这两个段落是关联段落,para2是段落编号 an8是书名只要书名一样,段落编号一样。
+ * 两个就是关联段落
+ *
+ * 表名:cs6_para
+ * 所以数据库结构是
+ * book 书号 1-217
+ * para 段落号
+ * bookid
+ * cspara 上述段落号
+ * book_name 上述书名
+ *
+ * 输入 book para
+ * 查询书名和段落号
+ * 输入这个书名和段落号
+ * 查询有多少段落有一样的书名和段落号
+ * 有些book 里面有两本书。所以又加了一个bookid
+ * 每个bookid代表一本真正的书。所以bookid 要比 book 多
+ * bookid 是为了输出书名用的。不是为了查询相关段落
+ *
+ * 数据要求:
+ * 制作时包含全部段落。做好后把没有相关段落的段落删掉??
+ *
+ */
+namespace App\Http\Controllers;
+
+use App\Models\RelatedParagraph;
+use Illuminate\Http\Request;
+use App\Http\Resources\RelatedParagraphResource;
+
+class RelatedParagraphController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        $first = RelatedParagraph::where('book',$request->get('book'))
+                                    ->where('para',$request->get('para'))
+                                    ->where('cs_para','>',0)
+                                    ->first();
+        $result = RelatedParagraph::where('book_name',$first->book_name)
+                                    ->where('cs_para',$first->cs_para)
+                                    ->orderBy('book_id')
+                                    ->orderBy('para')
+                                    ->get();
+        $books=[];
+        foreach ($result as $value) {
+            # 把段落整合成书。有几本书就有几条输出纪录
+            if(!isset($books[$value->book_id])){
+                $books[$value->book_id]['book'] = $value->book;
+                $books[$value->book_id]['book_id'] = $value->book_id;
+                $books[$value->book_id]['cs6_para'] = $value->cs_para;
+            }
+            $books[$value->book_id]['para'][]=$value->para;
+        }
+        return $this->ok(["rows"=>RelatedParagraphResource::collection($books),"count"=>count($books)]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\RelatedParagraph  $relatedParagraph
+     * @return \Illuminate\Http\Response
+     */
+    public function show(RelatedParagraph $relatedParagraph)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\RelatedParagraph  $relatedParagraph
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, RelatedParagraph $relatedParagraph)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\RelatedParagraph  $relatedParagraph
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(RelatedParagraph $relatedParagraph)
+    {
+        //
+    }
+}

+ 237 - 0
app/Http/Controllers/RelationController.php

@@ -0,0 +1,237 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\Relation;
+use Illuminate\Http\Request;
+use App\Http\Resources\RelationResource;
+use App\Http\Api\AuthApi;
+use Illuminate\Support\Facades\App;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+class RelationController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        $table = Relation::select(['id','name','case','to','editor_id','updated_at','created_at']);
+        if(($request->has('case'))){
+            $table->whereIn('case', explode(",",$request->get('case')) );
+        }
+        if(($request->has('search'))){
+            $table->where('name', 'like', $request->get('search')."%");
+        }
+        if(!empty($request->get('order')) && !empty($request->get('dir'))){
+            $table->orderBy($request->get('order'),$request->get('dir'));
+        }else{
+            $table->orderBy('updated_at','desc');
+        }
+        $count = $table->count();
+        if(!empty($request->get('limit'))){
+            $offset = 0;
+            if(!empty($request->get("offset"))){
+                $offset = $request->get("offset");
+            }
+            $table->skip($offset)->take($request->get('limit'));
+        }
+        $result = $table->get();
+
+		if($result){
+			return $this->ok(["rows"=>RelationResource::collection($result),"count"=>$count]);
+		}else{
+			return $this->error("没有查询到数据");
+		}
+    }
+
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //TODO 判断权限
+        $validated = $request->validate([
+            'name' => 'required',
+        ]);
+        $case = $request->get('case','');
+        $new = new Relation;
+        $new->name = $validated['name'];
+        if($request->has('case')){
+            $new->case = $request->get('case');
+        }else{
+            $new->case = null;
+        }
+        if($request->has('to')){
+            $new->to = json_encode($request->get('to'),JSON_UNESCAPED_UNICODE);
+        }else{
+            $new->to = null;
+        }
+        $new->editor_id = $user['user_uid'];
+        $new->save();
+        return $this->ok(new RelationResource($new));
+
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\Relation  $relation
+     * @return \Illuminate\Http\Response
+     */
+    public function show(Relation $relation)
+    {
+        //
+        return $this->ok(new RelationResource($relation));
+
+    }
+
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\Relation  $relation
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, Relation $relation)
+    {
+        //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+
+        $relation->name = $request->get('name');
+        if($request->has('case')){
+            $relation->case = $request->get('case');
+        }else{
+            $relation->case = null;
+        }
+        if($request->has('to')){
+            $relation->to = json_encode($request->get('to'),JSON_UNESCAPED_UNICODE);
+        }else{
+            $relation->to = null;
+        }
+        $relation->editor_id = $user['user_uid'];
+        $relation->save();
+        return $this->ok(new RelationResource($relation));
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\Relation  $relation
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Request $request,Relation $relation)
+    {
+        //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        //TODO 判断当前用户是否有权限
+        $delete = 0;
+        $delete = $relation->delete();
+
+        return $this->ok($delete);
+    }
+
+    public function export(){
+        $spreadsheet = new Spreadsheet();
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+        $activeWorksheet->setCellValue('A1', 'id');
+        $activeWorksheet->setCellValue('B1', 'name');
+        $activeWorksheet->setCellValue('C1', 'case');
+        $activeWorksheet->setCellValue('D1', 'to');
+
+        $nissaya = Relation::cursor();
+        $currLine = 2;
+        foreach ($nissaya as $key => $row) {
+            # code...
+            $activeWorksheet->setCellValue("A{$currLine}", $row->id);
+            $activeWorksheet->setCellValue("B{$currLine}", $row->name);
+            $activeWorksheet->setCellValue("C{$currLine}", $row->case);
+            $activeWorksheet->setCellValue("D{$currLine}", $row->to);
+            $currLine++;
+        }
+        $writer = new Xlsx($spreadsheet);
+        header('Content-Type: application/vnd.ms-excel');
+        header('Content-Disposition: attachment; filename="relation.xlsx"');
+        $writer->save("php://output");
+    }
+
+    public function import(Request $request){
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+
+        $filename = $request->get('filename');
+        $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
+        $reader->setReadDataOnly(true);
+        $spreadsheet = $reader->load($filename);
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+        $currLine = 2;
+        $countFail = 0;
+        $error = "";
+        do {
+            # code...
+            $id = $activeWorksheet->getCell("A{$currLine}")->getValue();
+            $name = $activeWorksheet->getCell("B{$currLine}")->getValue();
+            $case = $activeWorksheet->getCell("C{$currLine}")->getValue();
+            $to = $activeWorksheet->getCell("D{$currLine}")->getValue();
+            if(!empty($name)){
+                                //查询是否有冲突数据
+                //查询此id是否有旧数据
+                if(!empty($id)){
+                    $oldRow = Relation::find($id);
+                }
+                //查询是否跟已有数据重复
+                $row = Relation::where(['name'=>$name,'case'=>$case])->first();
+                if(!$row){
+                    //不重复
+                    if(isset($oldRow) && $oldRow){
+                        //有旧的记录-修改旧数据
+                        $row = $oldRow;
+                    }else{
+                        //没找到旧的记录-新建
+                        $row = new Relation();
+                    }
+                }else{
+                    //重复-如果与旧的id不同旧报错
+                    if(isset($oldRow) && $oldRow && $row->id !== $id){
+                        $error .= "重复的数据:{$id} - {$word}\n";
+                        $currLine++;
+                        $countFail++;
+                        continue;
+                    }
+                }
+                $row->name = $name;
+                $row->case = $case;
+                $row->to = $to;
+                $row->editor_id = $user['user_uid'];
+                $row->save();
+            }else{
+                break;
+            }
+            $currLine++;
+        } while (true);
+        return $this->ok(["success"=>$currLine-2-$countFail,'fail'=>($countFail)],$error);
+    }
+}

+ 341 - 0
app/Http/Controllers/SearchController.php

@@ -0,0 +1,341 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Models\BookTitle;
+use App\Models\FtsText;
+use App\Models\Tag;
+use App\Models\TagMap;
+use App\Models\PaliText;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\DB;
+use App\Http\Resources\SearchResource;
+use App\Http\Resources\SearchBookResource;
+use Illuminate\Support\Facades\Log;
+use App\Tools\Tools;
+use App\Models\WbwTemplate;
+
+
+class SearchController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request){
+        switch ($request->get('view','pali')) {
+            case 'pali':
+                $pageHead = ['M','P','T','V','O'];
+                $key = $request->get('key');
+                if(substr($key,0,4) === 'para' || in_array(substr($key,0,1),$pageHead)){
+                    return $this->page($request);
+                }else{
+                    return $this->pali($request);
+                }
+                break;
+            case 'page':
+                return $this->page($request);
+                break;
+            default:
+                # code...
+                break;
+        }
+    }
+    public function pali(Request $request)
+    {
+        //
+        $searchChapters = [];
+        $searchBooks = [];
+        $searchBookId = [];
+        $queryBookId = '';
+
+        if($request->has('book')){
+            $queryBookId = ' AND pcd_book_id = ' . (int)$request->get('book');
+        }else if($request->has('tags')){
+            //查询搜索范围
+            //查询搜索范围
+            $tagItems = explode(';',$request->get('tags'));
+            $bookId = [];
+            foreach ($tagItems as $tagItem) {
+                # code...
+                $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+            }
+            $queryBookId = ' AND pcd_book_id in ('.implode(',',$bookId).') ';
+        }
+
+        $key = explode(';',$request->get('key')) ;
+        $param = [];
+        $countParam = [];
+        switch ($request->get('match','case')) {
+            case 'complete':
+            case 'case':
+                # code...
+                $querySelect_rank_base = " ts_rank('{0.1, 0.2, 0.4, 1}',
+                                                full_text_search_weighted,
+                                                websearch_to_tsquery('pali', ?)) ";
+                $querySelect_rank_head = implode('+', array_fill(0, count($key), $querySelect_rank_base));
+                $param = array_merge($param,$key);
+                $querySelect_rank = " {$querySelect_rank_head} AS rank, ";
+                $querySelect_highlight = " ts_headline('pali', content,
+                                            websearch_to_tsquery('pali', ?),
+                                            'StartSel = ~~, StopSel = ~~,MaxWords=3500, MinWords=3500,HighlightAll=TRUE')
+                                            AS highlight,";
+                array_push($param,implode(' ',$key));
+                break;
+            case 'similar':
+                # 形似,去掉变音符号
+                $key = Tools::getWordEn($key[0]);
+                $querySelect_rank = "
+                    ts_rank('{0.1, 0.2, 0.4, 1}',
+                        full_text_search_weighted_unaccent,
+                        websearch_to_tsquery('pali_unaccent', ?))
+                    AS rank, ";
+                    $param[] = $key;
+                $querySelect_highlight = " ts_headline('pali_unaccent', content,
+                        websearch_to_tsquery('pali_unaccent', ?),
+                        'StartSel = ~~, StopSel = ~~,MaxWords=3500, MinWords=3500,HighlightAll=TRUE')
+                        AS highlight,";
+                $param[] = $key;
+                break;
+        }
+        $_queryWhere = $this->getQueryWhere($request->get('key'),$request->get('match','case'));
+        $queryWhere = $_queryWhere['query'];
+        $param = array_merge($param,$_queryWhere['param']);
+
+        $querySelect_2 = "  book,paragraph,content ";
+
+        $queryCount = "SELECT count(*) as co FROM fts_texts WHERE {$queryWhere} {$queryBookId};";
+        $resultCount = DB::select($queryCount, $_queryWhere['param']);
+
+        $limit = $request->get('limit',10);
+        $offset = $request->get('offset',0);
+        switch ( $request->get('orderby',"rank")) {
+            case 'rank':
+                $orderby = " ORDER BY rank DESC ";
+                break;
+            case 'paragraph':
+                $orderby = " ORDER BY book,paragraph ";
+                break;
+            default:
+                $orderby = "";
+                break;
+        };
+        $query = "SELECT
+            {$querySelect_rank}
+            {$querySelect_highlight}
+            {$querySelect_2}
+            FROM fts_texts
+            WHERE
+                {$queryWhere}
+                {$queryBookId}
+                {$orderby}
+            LIMIT ? OFFSET ? ;";
+        $param[] = $limit;
+        $param[] = $offset;
+
+        $result = DB::select($query, $param);
+
+        //待查询单词列表
+        //$caseMan = new CaseMan();
+        //$wordSpell = $caseMan->BaseToWord($key);
+
+        return $this->ok(["rows"=>SearchResource::collection($result),"count"=>$resultCount[0]->co]);
+    }
+    public function page(Request $request)
+    {
+        //
+        $searchChapters = [];
+        $searchBooks = [];
+        $searchBookId = [];
+        $queryBookId = '';
+        $bookId = [];
+        if($request->has('book')){
+            $bookId[] = $request->get('book');
+        }else if($request->has('tags')){
+            //查询搜索范围
+            //查询搜索范围
+            $tagItems = explode(';',$request->get('tags'));
+            foreach ($tagItems as $tagItem) {
+                # code...
+                $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+            }
+        }
+
+//type='.ctl.' and word like 'P%038'
+        $key = $request->get('key');
+        $searchKey = '';
+        $table = WbwTemplate::where('type','.ctl.');
+        if(is_numeric($key)){
+            $table = $table->where('word','like',$request->get('type')."%0".$key);
+        }else{
+            $table = $table->where('word',$key);
+        }
+
+        if(count($bookId)>0){
+            $table = $table->whereIn('pcd_book_id',$bookId);
+        }
+        $count = $table->count();
+        $table = $table->select(['book','paragraph']);
+        $table->skip($request->get("offset",0))->take($request->get('limit',10));
+        $result = $table->get();
+
+        return $this->ok(["rows"=>SearchResource::collection($result),"count"=>$count]);
+    }
+
+    public function book_list(Request $request){
+        $searchChapters = [];
+        $searchBooks = [];
+        $queryBookId = '';
+
+        if($request->has('tags')){
+            //查询搜索范围
+            $tagItems = explode(';',$request->get('tags'));
+            $bookId = [];
+            foreach ($tagItems as $tagItem) {
+                # code...
+                $bookId = array_merge($bookId,$this->getBookIdByTags(explode(',',$tagItem)));
+            }
+            $queryBookId = ' AND pcd_book_id in ('.implode(',',$bookId).') ';
+        }
+        $key = $request->get('key');
+        switch ($request->get('view','pali')) {
+            case 'pali':
+                # code...
+                $pageHead = ['M','P','T','V','O'];
+                if(substr($key,0,4) === 'para' || in_array(substr($key,0,1),$pageHead)){
+                    $queryWhere = "type='.ctl.' AND word = ?";
+                    $query = "SELECT pcd_book_id, count(*) as co FROM wbw_templates WHERE {$queryWhere} {$queryBookId} GROUP BY pcd_book_id ORDER BY co DESC;";
+                    $result = DB::select($query, [$key]);
+                }else{
+                    $queryWhere = $this->getQueryWhere($key,$request->get('match','case'));
+                    $query = "SELECT pcd_book_id, count(*) as co FROM fts_texts WHERE {$queryWhere['query']} {$queryBookId} GROUP BY pcd_book_id ORDER BY co DESC;";
+                    $result = DB::select($query, $queryWhere['param']);
+                }
+                break;
+            case 'page';
+                $type = $request->get('type','P');
+                $word = "{$type}%0{$key}";
+                $queryWhere = "type='.ctl.' AND word like ?";
+                $query = "SELECT pcd_book_id, count(*) as co FROM wbw_templates WHERE {$queryWhere} {$queryBookId} GROUP BY pcd_book_id ORDER BY co DESC;";
+                $result = DB::select($query, [$word]);
+                break;
+            default:
+                # code...
+                return $this->error('unknown view');
+                break;
+        }
+
+
+        return $this->ok(["rows"=>SearchBookResource::collection($result),"count"=>count($result)]);
+    }
+
+    private function getQueryWhere($key,$match){
+        $key = explode(';',$key) ;
+        $param = [];
+        $queryWhere = '';
+        switch ($match) {
+            case 'complete':
+            case 'case':
+                # code...
+                $queryWhereBase = " full_text_search_weighted @@ websearch_to_tsquery('pali', ?) ";
+                $queryWhereBody = implode(' or ', array_fill(0, count($key), $queryWhereBase));
+                $queryWhere = " ({$queryWhereBody}) ";
+                $param = array_merge($param,$key);
+                break;
+            case 'similar':
+                # 形似,去掉变音符号
+                $queryWhere = " full_text_search_weighted_unaccent @@ websearch_to_tsquery('pali_unaccent', ?) ";
+                $key = Tools::getWordEn($key[0]);
+                $param = [$key];
+                break;
+        };
+        return (['query'=>$queryWhere,'param'=>$param]);
+    }
+
+    private function getBookIdByTags($tags){
+        $searchBookId = [];
+        if(empty($tags)){
+            return $searchBookId;
+        }
+
+        //查询搜索范围
+        $tagIds = Tag::whereIn('name',$tags)->select('id')->get();
+        $paliTextIds = TagMap::where('table_name','pali_texts')->whereIn('tag_id',$tagIds)->select('anchor_id')->get();
+        $paliPara=[];
+        foreach ($paliTextIds as $key => $value) {
+            # code...
+            if(isset($paliPara[$value->anchor_id])){
+                $paliPara[$value->anchor_id]++;
+            }else{
+                $paliPara[$value->anchor_id]=1;
+            }
+        }
+        $paliId=[];
+        foreach ($paliPara as $key => $value) {
+            # code...
+            if($value===count($tags)){
+                $paliId[] = $key;
+            }
+        }
+        $para = PaliText::where('level',1)->whereIn('uid',$paliId)->get();
+
+        if(count($para)>0){
+            foreach ($para as $key => $value) {
+                # code...
+                $book_id = BookTitle::where('book',$value['book'])->where('paragraph',$value['paragraph'])->value('id');
+                if(!empty($book_id)){
+                    $searchBookId[] = $book_id;
+                }
+            }
+        }
+        return $searchBookId;
+
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}

+ 49 - 11
app/Http/Controllers/SentPrController.php

@@ -6,6 +6,7 @@ namespace App\Http\Controllers;
 use App\Models\SentPr;
 use App\Models\Channel;
 use App\Models\PaliSentence;
+use App\Models\Sentence;
 use App\Http\Resources\SentPrResource;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Http;
@@ -31,7 +32,8 @@ class SentPrController extends Controller
                                 ->where('word_end',$request->get('end'))
                                 ->where('channel_uid',$request->get('channel'));
                 $all_count = $table->count();
-                $chapters = $table->orderBy('paragraph')->get();
+                $chapters = $table->orderBy('created_at','desc')->get();
+
                 break;
         }
         if($chapters){
@@ -41,6 +43,47 @@ class SentPrController extends Controller
         }
     }
 
+    public function pr_tree(Request $request){
+        $output = [];
+        $sentences = $request->get("data");
+        foreach ($sentences as $key => $sentence) {
+            # 先查句子信息
+            $sentInfo = Sentence::where('book_id',$sentence['book'])
+                                ->where('paragraph',$sentence['paragraph'])
+                                ->where('word_start',$sentence['word_start'])
+                                ->where('word_end',$sentence['word_end'])
+                                ->where('channel_uid',$sentence['channel_id'])
+                                ->first();
+            $sentPr = SentPr::where('book_id',$sentence['book'])
+                            ->where('paragraph',$sentence['paragraph'])
+                            ->where('word_start',$sentence['word_start'])
+                            ->where('word_end',$sentence['word_end'])
+                            ->where('channel_uid',$sentence['channel_id'])
+                            ->select('content','editor_uid')
+                            ->orderBy('created_at','desc')->get();
+            if(count($sentPr)>0){
+                if($sentInfo){
+                    $content = $sentInfo->content;
+                }else{
+                    $content = "null";
+                }
+                $output[] = [
+                    'sentence' => [
+                        'book' => $sentence['book'],
+                        'paragraph' => $sentence['paragraph'],
+                        'word_start' => $sentence['word_start'],
+                        'word_end' => $sentence['word_end'],
+                        'channel_id' => $sentence['channel_id'],
+                        'content' => $content,
+                        'pr_count' => count($sentPr),
+                    ],
+                    'pr' => $sentPr,
+                ];
+            }
+
+        }
+        return $this->ok(['rows'=>$output,'count'=>count($output)]);
+    }
     /**
      * Store a newly created resource in storage.
      *
@@ -50,11 +93,11 @@ class SentPrController extends Controller
     public function store(Request $request)
     {
         //
-        if(!isset($_COOKIE['user_uid'])){
-            return $this->error('not login');
-        }else{
-			$user_uid = $_COOKIE['user_uid'];
-		}
+        $user = \App\Http\Api\AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        $user_uid = $user['user_uid'];
 
         $data = $request->all();
 
@@ -121,7 +164,6 @@ class SentPrController extends Controller
 				$palitext = mb_substr($palitext,0,20,"UTF-8");
 				$prtext = mb_substr($data['text'],0,140,"UTF-8");
 				$link = "https://www-hk.wikipali.org/app/article/index.php?view=para&book={$data['book']}&par={$data['para']}&begin={$data['begin']}&end={$data['end']}&channel={$data['channel']}&mode=edit";
-				Log::info("palitext:{$palitext} prtext = {$prtext} link={$link}");
 				switch ($data['channel']) {
 					//测试
 					//case '3b0cb0aa-ea88-4ce5-b67d-00a3e76220cc':
@@ -155,7 +197,6 @@ class SentPrController extends Controller
 							"content"=> $strMessage,
 						],
 					];
-				Log::info("message:{$strMessage}");
 				if(!empty($strMessage)){
 					$response = Http::post($url, $param);
 					if($response->successful()){
@@ -186,7 +227,6 @@ class SentPrController extends Controller
 						->where('word_end' , $data['end'])
 						->where('channel_uid' , $data['channel'])
 						->count();
-		Log::info("count:{$count} webhook-ok={$robotMessageOk}");
 		return $this->ok(["new"=>$info,"count"=>$count,"webhook"=>["message"=>$webHookMessage,"ok"=>$robotMessageOk]]);
 
     }
@@ -246,12 +286,10 @@ class SentPrController extends Controller
     public function destroy($id)
     {
         //
-		Log::info("user_uid=" .$_COOKIE['user_uid']);
 		$old = SentPr::where('id', $id)->first();
 		$result = SentPr::where('id', $id)
 							->where('editor_uid', $_COOKIE["user_uid"])
 							->delete();
-		Log::info("delete=" .$result);
 		if($result>0){
 					#同时返回此句子pr数量
 		$count = SentPr::where('book_id' , $old->book_id)

+ 116 - 0
app/Http/Controllers/SentSimController.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\SentSim;
+use App\Models\PaliSentence;
+use Illuminate\Http\Request;
+use App\Http\Resources\SentSimResource;
+
+class SentSimController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        switch ($request->get('view')) {
+            case 'sentence':
+                $sentId = PaliSentence::where('book',$request->get('book'))
+                                ->where('paragraph',$request->get('paragraph'))
+                                ->where('word_begin',$request->get('start'))
+                                ->where('word_end',$request->get('end'))
+                                ->value('id');
+                if(!$sentId){
+                    return $this->error("no sent");
+                }
+                $table = SentSim::where('sent1',$sentId)
+                                ->where('sim',">",0.7)
+                                ->orderBy('sim','desc');
+                break;
+        }
+        $count = $table->count();
+        if(!empty($request->get('limit'))){
+            $offset = 0;
+            if(!empty($request->get("offset"))){
+                $offset = $request->get("offset");
+            }
+            $table->skip($offset)->take($request->get('limit'));
+        }
+        $result = $table->get();
+        if($result){
+            return $this->ok(["rows"=>SentSimResource::collection($result),"count"=>$count]);
+        }else{
+            return $this->error("no data");
+        }
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\SentSim  $sentSim
+     * @return \Illuminate\Http\Response
+     */
+    public function show(SentSim $sentSim)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  \App\Models\SentSim  $sentSim
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(SentSim $sentSim)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\SentSim  $sentSim
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, SentSim $sentSim)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\SentSim  $sentSim
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(SentSim $sentSim)
+    {
+        //
+    }
+}

+ 147 - 56
app/Http/Controllers/SentenceController.php

@@ -7,6 +7,9 @@ use App\Models\Channel;
 use Illuminate\Http\Request;
 use Illuminate\Support\Str;
 use App\Http\Resources\SentResource;
+use App\Http\Api\AuthApi;
+use App\Http\Api\ShareApi;
+use App\Http\Api\ChannelApi;
 
 class SentenceController extends Controller
 {
@@ -18,9 +21,32 @@ class SentenceController extends Controller
     public function index(Request $request)
     {
         $result=false;
-		$indexCol = ['id','book_id','paragraph','word_start','word_end','content','channel_uid','updated_at'];
+		$indexCol = ['id','book_id','paragraph','word_start','word_end','content','content_type','channel_uid','editor_uid','acceptor_uid','pr_edit_at','updated_at'];
 
 		switch ($request->get('view')) {
+            case 'public':
+                //获取全部公开的译文
+                //首先获取某个类型的 channel 列表
+                $channels = [];
+                $channel_type = $request->get('channel_type','translation');
+                if($channel_type === "original"){
+                    $pali_channel = ChannelApi::getSysChannel("_System_Pali_VRI_");
+                    if($pali_channel !== false){
+                        $channels[] = $pali_channel;
+                    }
+                }else{
+                    $channelList = Channel::where('type',$channel_type)
+                                              ->where('status',30)
+                                              ->select('uid')->get();
+                    foreach ($channelList as $channel) {
+                        # code...
+                        $channels[] = $channel->uid;
+                    }
+                }
+                $table = Sentence::select($indexCol)
+                                  ->whereIn('channel_uid',$channels)
+                                  ->where('updated_at','>',$request->get('updated_after','1970-1-1'));
+                break;
             case 'fulltext':
                 if(isset($_COOKIE['user_uid'])){
                     $userUid = $_COOKIE['user_uid'];
@@ -46,26 +72,86 @@ class SentenceController extends Controller
                                 ->where('channel_uid', $request->get('channel'))
                                 ->whereIns(['book_id','paragraph','word_start','word_end'],$query);
                 break;
+            case 'sent-can-read':
+                /**
+                 * 某句的全部译文
+                 */
+                //获取用户有阅读权限的所有channel
+                //全网公开
+                $type = $request->get('type','translation');
+                $channelTable = Channel::where("type",$type)->select(['uid','name']);
+                $channelPub = $channelTable->where('status',30)->get();
+
+                $user = AuthApi::current($request);
+                if($user){
+                    //自己的
+                    $channelMy = $channelTable->where('owner_uid',$user['user_uid'])->get();
+                    //协作
+                    $channelShare = ShareApi::getResList($user['user_uid'],2);
+                }
+                $channelCanRead = [];
+                foreach ($channelPub as $key => $value) {
+                    $channelCanRead[$value->uid] = [
+                        'id' => $value->uid,
+                        'role' => 'member',
+                        'name' => $value->name,
+                    ];
+                }
+                foreach ($channelShare as $key => $value) {
+                    if($value['type'] === $type){
+                        $channelCanRead[$value['res_id']] = [
+                            'id' => $value['res_id'],
+                            'role' => 'member',
+                            'name' => $value['res_title'],
+                        ];
+                        if($value['power']>=20){
+                            $channelCanRead[$value['res_id']]['role'] = "editor";
+                        }
+                    }
+                }
+                foreach ($channelMy as $key => $value) {
+                    $channelCanRead[$value->uid] = [
+                        'id' => $value->uid,
+                        'role' => 'owner',
+                        'name' => $value->name,
+                    ];
+                }
+                $channels = [];
+                foreach ($channelCanRead as $key => $value) {
+                    # code...
+                    $channels[] = $key;
+                }
+                $sent = explode('-',$request->get('sentence')) ;
+                $table = Sentence::select($indexCol)
+                                ->whereIn('channel_uid', $channels)
+                                ->where('book_id',$sent[0])
+                                ->where('paragraph',$sent[1])
+                                ->where('word_start',$sent[2])
+                                ->where('word_end',$sent[3]);
 			default:
 				# code...
 				break;
 		}
-        if(!empty($request->get('order')) && !empty($request->get('dir'))){
-            $table->orderBy($request->get('order'),$request->get('dir'));
-        }else{
-            $table->orderBy('updated_at','desc');
-        }
         $count = $table->count();
-        if(!empty($request->get('limit'))){
-            $offset = 0;
-            if(!empty($request->get("offset"))){
-                $offset = $request->get("offset");
-            }
-            $table->skip($offset)->take($request->get('limit'));
+        if($request->get('strlen',false)){
+            $totalStrLen = $table->sum('strlen');
         }
+        $table = $table->orderBy($request->get('order','updated_at'),$request->get('dir','desc'));
+        $table = $table->skip($request->get("offset",0))
+                       ->take($request->get('limit',1000));
         $result = $table->get();
+
 		if($result){
-			return $this->ok(["rows"=>$result,"count"=>$count]);
+            if($request->get('view') === 'sent-can-read'){
+                $output = ["rows"=>SentResource::collection($result),"count"=>$count];
+            }else{
+                $output = ["rows"=>$result,"count"=>$count];
+            }
+            if(isset($totalStrLen)){
+                $output['total_strlen'] = $totalStrLen;
+            }
+            return $this->ok($output);
+
 		}else{
 			return $this->error("没有查询到数据");
 		}
@@ -79,7 +165,9 @@ class SentenceController extends Controller
         foreach ($sent as $value) {
             # code...
             $ids = explode('-',$value);
-            $query[] = $ids;
+            if(count($ids)===4){
+                $query[] = $ids;
+            }
         }
         $table = Sentence::select(['id','book_id','paragraph','word_start','word_end','content','channel_uid','updated_at'])
                         ->where('channel_uid', $request->get('channel'))
@@ -102,18 +190,18 @@ class SentenceController extends Controller
     }
 
     /**
-     * Store a newly created resource in storage.
-     *
+     * 新建多个句子
+     * 如果句子存在,修改
      * @param  \Illuminate\Http\Request  $request
      * @return \Illuminate\Http\Response
      */
     public function store(Request $request)
     {
         //鉴权
-        $user = \App\Http\Api\AuthApi::current($request);
+        $user = AuthApi::current($request);
         if(!$user ){
             //未登录用户
-            return $this->error(__('auth.failed'));
+            return $this->error(__('auth.failed'),[],401);
         }
         $channel = Channel::where('uid',$request->get('channel'))->first();
         if(!$channel){
@@ -161,19 +249,9 @@ class SentenceController extends Controller
         //
     }
 
-    /**
-     * Show the form for editing the specified resource.
-     *
-     * @param  \App\Models\Sentence  $sentence
-     * @return \Illuminate\Http\Response
-     */
-    public function edit(Sentence $sentence)
-    {
-        //
-    }
 
     /**
-     * Update the specified resource in storage.
+     * 修改单个句子
      *
      * @param  \Illuminate\Http\Request  $request
      * @param  string  $id book_para_start_end_channel
@@ -185,35 +263,48 @@ class SentenceController extends Controller
         $param = \explode('_',$id);
 
         //鉴权
-        $user = \App\Http\Api\AuthApi::current($request);
-        if($user ){
-            $channel = Channel::where('uid',$param[4])->first();
-            if($channel && $channel->owner_uid === $user["user_uid"]){
-                $sent = Sentence::firstOrNew([
-                    "book_id"=>$param[0],
-                    "paragraph"=>$param[1],
-                    "word_start"=>$param[2],
-                    "word_end"=>$param[3],
-                    "channel_uid"=>$param[4],
-                ],[
-                    "id"=>app('snowflake')->id(),
-                    "uid"=>Str::orderedUuid(),
-                ]);
-                $sent->content = $request->get('content');
-                $sent->language = $channel->lang;
-                $sent->status = $channel->status;
-                $sent->editor_uid = $user["user_uid"];
-                $sent->save();
-                return $this->ok(new SentResource($sent));
-            }else{
-                //TODO 判断是否为协作
-                return $this->error(__('auth.failed'));
-            }
-        }else{
-            //非所有者鉴权失败
+        $user = AuthApi::current($request);
+        if(!$user){
+            //未登录鉴权失败
+            return $this->error(__('auth.failed'),[],403);
+        }
+        $channel = Channel::where('uid',$param[4])->first();
+        if(!$channel){
+            return $this->error("not found channel");
+        }
+        if($channel->owner_uid !== $user["user_uid"]){
+            //TODO 判断是否为协作
+            return $this->error(__('auth.failed'),[],403);
+        }
 
-            return $this->error(__('auth.failed'));
+        $sent = Sentence::firstOrNew([
+            "book_id"=>$param[0],
+            "paragraph"=>$param[1],
+            "word_start"=>$param[2],
+            "word_end"=>$param[3],
+            "channel_uid"=>$param[4],
+        ],[
+            "id"=>app('snowflake')->id(),
+            "uid"=>Str::orderedUuid(),
+            "create_time"=>time()*1000,
+        ]);
+        $sent->content = $request->get('content');
+        if($request->has('contentType')){
+            $sent->content_type = $request->get('contentType');
+        }
+        $sent->language = $channel->lang;
+        $sent->status = $channel->status;
+        $sent->editor_uid = $user["user_uid"];
+        $sent->strlen = mb_strlen($request->get('content'),"UTF-8");
+        $sent->modify_time = time()*1000;
+        if($request->has('prEditor')){
+            $sent->acceptor_uid = $user["user_uid"];
+            $sent->pr_edit_at = $request->get('prEditAt');
+            $sent->editor_uid = $request->get('prEditor');
+            $sent->pr_id = $request->get('prId');
         }
+        $sent->save();
+        return $this->ok(new SentResource($sent));
     }
 
     /**

+ 97 - 15
app/Http/Controllers/ShareController.php

@@ -4,8 +4,12 @@ namespace App\Http\Controllers;
 
 use App\Models\Share;
 use App\Models\GroupInfo;
+use App\Models\Article;
+use App\Models\Collection;
 use Illuminate\Http\Request;
 use App\Http\Resources\ShareResource;
+use App\Http\Api\AuthApi;
+use App\Http\Api\ShareApi;
 
 class ShareController extends Controller
 {
@@ -17,23 +21,38 @@ class ShareController extends Controller
     public function index(Request $request)
     {
         //
+        $user = AuthApi::current($request);
         $result=false;
+        $role = "member";
 		$indexCol = ['id','res_id','res_type','power','updated_at','created_at'];
 		switch ($request->get('view')) {
+            case 'res':
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                $table = Share::where('res_id',$request->get('id'));
+                $power = ShareApi::getResPower($user['user_uid'],$request->get('id'),$table->value('res_type'));
+                switch ($power) {
+                    case 10:
+                        $role = "member";
+                        break;
+                    case 20:
+                        $role = "editor";
+                        break;
+                    case 30:
+                        $role = "owner";
+                        break;
+                }
+                break;
             case 'group':
-	            # 获取 group 内所有 成员
-                $user = \App\Http\Api\AuthApi::current($request);
-                if($user){
-                    //TODO 判断当前用户是否有指定的 group 的权限
-
-                    if(GroupInfo::where('uid',$request->get('id'))->where('owner',$user['user_uid'])->exists()){
-                        $table = Share::where('cooperator_id', $request->get('id'));
-                    }else{
-                        return $this->error(__('auth.failed'));
-                    }
-                }else{
+                if(!$user){
                     return $this->error(__('auth.failed'));
                 }
+                //TODO 判断当前用户是否有指定的 group 的权限
+                if(GroupInfo::where('uid',$request->get('id'))->where('owner',$user['user_uid'])->exists()){
+                    $role = "owner";
+                }
+                $table = Share::where('cooperator_id', $request->get('id'));
 				break;
         }
         if(isset($_GET["search"])){
@@ -56,7 +75,7 @@ class ShareController extends Controller
         }
         $result = $table->get();
         //TODO 获取当前用户的身份
-        $role = "member";
+
 
 		if($result){
 			return $this->ok(["rows"=>ShareResource::collection($result),"count"=>$count,'role'=>$role]);
@@ -76,6 +95,33 @@ class ShareController extends Controller
     public function store(Request $request)
     {
         //
+        foreach ($request->get('user_id') as $key => $value) {
+            # code...
+            $row = Share::where('cooperator_id',$value)
+                        ->where('res_id',$request->get('res_id'))->first();
+            if(!$row){
+                $row = new Share();
+                $row->id = app('snowflake')->id();
+                $row->cooperator_id = $value;
+                $row->res_id = $request->get('res_id');
+                $row->res_type = $request->get('res_type');
+                $row->create_time = time()*1000;
+            }
+            $c_type=['user'=>0,'group'=>1];
+            $row->cooperator_type = $c_type[$request->get('user_type')];
+            switch ($request->get('role')) {
+                case 'manager':
+                case 'editor':
+                    $row->power = 20;
+                    break;
+                case 'reader':
+                    $row->power = 10;
+                    break;
+            }
+            $row->modify_time = time()*1000;
+            $row->save();
+        }
+        return $this->ok(count($request->get('user_id')));
     }
 
     /**
@@ -98,17 +144,53 @@ class ShareController extends Controller
      */
     public function update(Request $request, Share $share)
     {
-        //
+        //查询权限
+        $currUser = AuthApi::current($request);
+        if(!$currUser){
+            return $this->error(__('auth.failed'));
+        }
+
+        $power = ShareApi::getResPower($currUser['user_uid'],$share->res_id,$share->res_type);
+        if(!$power || $power <= 20){
+            //普通成员没有删除权限
+            return $this->error(__('auth.failed'));
+        }
+        switch ($request->get('role')) {
+            case 'manager':
+            case 'editor':
+                $share->power = 20;
+                break;
+            case 'reader':
+                $share->power = 10;
+                break;
+        }
+        $share->modify_time = time()*1000;
+        $share->save();
+        return $this->ok($share);
     }
 
     /**
      * Remove the specified resource from storage.
      *
+     * @param  \Illuminate\Http\Request  $request
      * @param  \App\Models\Share  $share
      * @return \Illuminate\Http\Response
      */
-    public function destroy(Share $share)
+    public function destroy(Request $request, Share $share)
     {
-        //
+        //查询权限
+        $currUser = AuthApi::current($request);
+        if(!$currUser){
+            return $this->error(__('auth.failed'));
+        }
+
+        $power = ShareApi::getResPower($currUser['user_uid'],$share->res_id,$share->res_type);
+        if(!$power || $power <= 20){
+            //普通成员没有删除权限
+            return $this->error(__('auth.failed'));
+        }
+
+        $delete = $share->delete();
+        return $this->ok($delete);
     }
 }

+ 88 - 0
app/Http/Controllers/StudioController.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Http\Api\AuthApi;
+use App\Http\Api\StudioApi;
+use App\Http\Api\ShareApi;
+use App\Models\Channel;
+
+class StudioController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        switch ($request->get('view')) {
+            case 'collaboration-channel':
+                //协作channel 拥有者列表
+                $studioId = StudioApi::getIdByName($request->get('studio_name'));
+                $resList = ShareApi::getResList($studioId,2);
+                $resId=[];
+                foreach ($resList as $res) {
+                    $resId[] = $res['res_id'];
+                }
+                $owners = Channel::whereIn('uid', $resId)
+                                ->where('owner_uid','<>', $studioId)
+                                ->select('owner_uid')
+                                ->groupBy('owner_uid')->get();
+                $output = [];
+                foreach ($owners as $key => $owner) {
+                    # code...
+                    $output[] = StudioApi::getById($owner->owner_uid);
+                }
+                return $this->ok(['rows'=>$output,'count'=>count($output)]);
+                break;
+        }
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}

+ 94 - 1
app/Http/Controllers/TagController.php

@@ -2,7 +2,11 @@
 
 namespace App\Http\Controllers;
 
+use Illuminate\Support\Str;
+use Illuminate\Support\Facades\DB;
 use App\Models\Tag;
+use App\Models\TagMap;
+use App\Models\ProgressChapter;
 use Illuminate\Http\Request;
 
 class TagController extends Controller
@@ -12,9 +16,98 @@ class TagController extends Controller
      *
      * @return \Illuminate\Http\Response
      */
-    public function index()
+    public function index(Request $request)
     {
         //
+        switch ($request->get('view')) {
+            case "chapter":
+                $progress = $request->get('progress',0.8);
+                $lang = $request->get('lang');
+                $channelType = $request->get('type','translation');
+
+                $tm = (new TagMap)->getTable();
+                $pc =(new ProgressChapter)->getTable();
+                $tg = (new Tag)->getTable();
+
+                //标签过滤
+                if($request->get('tags') && $request->get('tags')!==''){
+                    $tags = explode(',',$request->get('tags'));
+                    foreach ($tags as $tag) {
+                        # code...
+                        if(!empty($tag)){
+                            $tagNames[] = $tag;
+                        }
+                    }
+                }
+                if(isset($tagNames)){
+                    $where1 = " where co = ".count($tagNames);
+                    $a = implode(",",array_fill(0, count($tagNames), '?')) ;
+                    $in1 = "and t.name in ({$a})";
+                    $param = $tagNames;
+                }else{
+                    $where1 = " ";
+                    $in1 = " ";
+                }
+                if(Str::isUuid($request->get('channel'))){
+                    $channel = "and channel_id = '".$request->get('channel')."' ";
+                }else{
+                    $channel = "";
+                }
+                //完成度过滤
+                $param[] = $progress;
+
+                //语言过滤
+                if(!empty($request->get('lang'))){
+                    $whereLang = " and pc.lang = ? ";
+                    $param[] = $request->get('lang');
+                }else{
+                    $whereLang = "   ";
+                }
+                //channel type过滤
+				if($request->has('channel_type') && !empty($request->get('channel_type'))){
+					$channel_type = "and ch.type = ? ";
+					$param[] = $request->get('channel_type');
+				}else{
+					$channel_type = "";
+				}
+
+                $param_count = $param;
+
+                $query = "
+                select TID.tag_id as id,name, TID.count from(
+                    select tm2.tag_id, count(*)      from(
+						select pcd.uid as pc_uid
+							from (
+								select uid, book,para,lang,progress,channel_id,title,summary ,created_at ,updated_at
+									from (
+										select anchor_id as cid
+											from (
+												select tm.anchor_id , count(*) as co
+													from $tm as  tm
+													left join $tg as t on tm.tag_id = t.id
+													where tm.table_name  = 'progress_chapters'
+													$in1
+													group by tm.anchor_id
+											) T
+											$where1
+									) CID
+								left join $pc as pc on CID.cid = pc.uid
+								where pc.progress > ?
+								$channel  $whereLang
+							) pcd
+						left join channels as ch on pcd.channel_id = ch.uid
+						where ch.status >= 30 $channel_type
+                    ) CUID
+                    left join tag_maps tm2 on CUID.pc_uid = tm2.anchor_id
+				group by tm2.tag_id
+				) TID
+				left join tags t2 on t2.id = TID.tag_id
+				order by count desc";
+                $result = DB::select($query,$param);
+                return $this->ok(['rows'=>$result,'count'=>count($result)]);
+                break;
+        }
+
     }
 
     /**

+ 95 - 0
app/Http/Controllers/TermVocabularyController.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\DhammaTerm;
+use Illuminate\Http\Request;
+use App\Http\Resources\TermVocabularyResource;
+use App\Http\Api\ChannelApi;
+
+class TermVocabularyController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        $table = DhammaTerm::select(['word','meaning']);
+        switch ($request->get('view')) {
+            case "grammar":
+                $localTerm = ChannelApi::getSysChannel(
+                    "_System_Grammar_Term_".strtolower($request->get('lang'))."_",
+                    "_System_Grammar_Term_en_"
+                );
+                if(!$localTerm){
+                    return $this->error('no term channel');
+                }
+                $table = $table->where('channal',$localTerm);
+                break;
+            case "studio":
+                break;
+            case "user":
+                break;
+            case "community":
+                $localTerm = ChannelApi::getSysChannel(
+                    "_community_term_".strtolower($request->get('lang'))."_",
+                    "_community_term_en_"
+                );
+                if(!$localTerm){
+                    return $this->error('no term channel');
+                }
+                $table = $table->where('channal',$localTerm);
+                break;
+        }
+        $result = $table->get();
+        return $this->ok(["rows"=>TermVocabularyResource::collection($result),'count'=>count($result)]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\DhammaTerm  $dhammaTerm
+     * @return \Illuminate\Http\Response
+     */
+    public function show(DhammaTerm $dhammaTerm)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\DhammaTerm  $dhammaTerm
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, DhammaTerm $dhammaTerm)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\DhammaTerm  $dhammaTerm
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(DhammaTerm $dhammaTerm)
+    {
+        //
+    }
+}

+ 97 - 83
app/Http/Controllers/UserDictController.php

@@ -8,6 +8,8 @@ use Illuminate\Support\Facades\Redis;
 use Illuminate\Support\Facades\Log;
 use App\Http\Api;
 use App\Http\Api\AuthApi;
+use App\Http\Api\DictApi;
+use App\Http\Resources\UserDictResource;
 
 class UserDictController extends Controller
 {
@@ -24,31 +26,40 @@ class UserDictController extends Controller
 		switch ($request->get('view')) {
             case 'studio':
 				# 获取studio内所有channel
-                $user = \App\Http\Api\AuthApi::current($request);
-                if($user){
-                    //判断当前用户是否有指定的studio的权限
-                    if($user['user_uid'] === \App\Http\Api\StudioApi::getIdByName($request->get('name'))){
-                        $table = UserDict::select($indexCol)
-                                    ->where('creator_id', $user["user_id"])
-                                    ->where('source', "_USER_WBW_");
-                    }else{
-                        return $this->error(__('auth.failed'));
-                    }
-                }else{
+                $user = AuthApi::current($request);
+                if(!$user){
                     return $this->error(__('auth.failed'));
                 }
+                //判断当前用户是否有指定的studio的权限
+                if($user['user_uid'] !== \App\Http\Api\StudioApi::getIdByName($request->get('name'))){
+                    return $this->error(__('auth.failed'));
+                }
+                $table = UserDict::select($indexCol)
+                            ->where('creator_id', $user["user_id"])
+                            ->whereIn('source', ["_USER_WBW_","_USER_DICT_"]);
 				break;
 			case 'user':
 				# code...
 				$table = UserDict::select($indexCol)
 									->where('creator_id', $_COOKIE["user_id"])
 									->where('source', '<>', "_SYS_USER_WBW_");
-
 				break;
 			case 'word':
 				$table = UserDict::select($indexCol)
-									->where('word', $_GET["word"]);
+								 ->where('word', $request->get("word"));
 				break;
+            case 'community':
+                $table = UserDict::select($indexCol)
+                                ->where('word', $request->get("word"))
+                                ->where('source', "_USER_WBW_");;
+                break;
+            case 'compound':
+                $dict_id = DictApi::getSysDict('robot_compound');
+                if($dict_id===false){
+                    $this->error('no robot_compound');
+                }
+                $table = UserDict::where("dict_id",$dict_id)->where("word",$request->get('word'));
+                break;
 			default:
 				# code...
 				break;
@@ -59,7 +70,11 @@ class UserDictController extends Controller
         if(isset($_GET["order"]) && isset($_GET["dir"])){
             $table->orderBy($_GET["order"],$_GET["dir"]);
         }else{
-            $table->orderBy('updated_at','desc');
+            if($request->get('view') === "compound"){
+                $table->orderBy('confidence','desc');
+            }else{
+                $table->orderBy('updated_at','desc');
+            }
         }
         $count = $table->count();
         if(isset($_GET["limit"])){
@@ -71,7 +86,7 @@ class UserDictController extends Controller
         }
         $result = $table->get();
 		if($result){
-			return $this->ok(["rows"=>$result,"count"=>$count]);
+			return $this->ok(["rows"=>UserDictResource::collection($result),"count"=>$count]);
 		}else{
 			return $this->error("没有查询到数据");
 		}
@@ -86,46 +101,51 @@ class UserDictController extends Controller
     public function store(Request $request)
     {
         //
-		if(!isset($_COOKIE["user_id"])){
+        $user  = AuthApi::current($request);
+		if(!$user){
 			$this->error("not login");
 		}
 
-		$_data = json_decode($_POST["data"],true);
+		$_data = json_decode($request->get("data"),true);
+
 		switch($request->get('view')){
+            case "dict":
+                $src = "_USER_DICT_";
+                break;
 			case "wbw":
-				#查询用户重复的数据
-				$iOk = 0;
-				$updateOk=0;
-				foreach ($_data as $key => $word) {
-					# code...
-					$isDoesntExist = UserDict::where('creator_id', $_COOKIE["user_id"])
-										->where('word',$word["word"])
-										->where('type',$word["type"])
-										->where('grammar',$word["grammar"])
-										->where('parent',$word["parent"])
-										->where('mean',$word["mean"])
-										->where('factors',$word["factors"])
-										->where('factormean',$word["factormean"])
-										->where('source','_USER_WBW_')
-										->doesntExist();
+                $src = "_USER_WBW_";
+                break;
+            default:
+                $this->error("not view");
+                break;
+        }
+        #查询用户重复的数据
+        $iOk = 0;
+        $updateOk=0;
+        foreach ($_data as $key => $word) {
+            # code...
+            $table = UserDict::where('creator_id', $user["user_id"])
+                                ->where('word',$word["word"]);
+            if(isset($word["type"])){$table = $table->where('type',$word["type"]);}
+            if(isset($word["grammar"])){$table = $table->where('grammar',$word["grammar"]);}
+            if(isset($word["parent"])){$table = $table->where('parent',$word["parent"]);}
+            if(isset($word["mean"])){$table = $table->where('mean',$word["mean"]);}
+            if(isset($word["factors"])){$table = $table->where('factors',$word["factors"]);}
+            $isDoesntExist = $table->doesntExist();
+            if($isDoesntExist){
+                #不存在插入数据
+                $word["id"]=app('snowflake')->id();
+                $word["source"] = $src;
+                $word["create_time"] = time()*1000;
+                $word["creator_id"]=$user["user_id"];
+                $id = UserDict::insert($word);
+                $updateOk = $this->update_sys_wbw($word);
+                $this->update_redis($word);
+                $iOk++;
+            }
+        }
 
-					if($isDoesntExist){
-						#不存在插入数据
-						$word["id"]=app('snowflake')->id();
-						$word["source"]='_USER_WBW_';
-						$word["create_time"]=mTime();
-						$word["creator_id"]=$_COOKIE["user_id"];
-						$id = UserDict::insert($word);
-						$updateOk = $this->update_sys_wbw($word);
-						$this->update_redis($word);
-						$iOk++;
-					}
-				}
-				$this->ok([$iOk,$updateOk]);
-				break;
-			case "dict":
-				break;
-		}
+        return $this->ok([$iOk,$updateOk]);
     }
 
     /**
@@ -156,8 +176,6 @@ class UserDictController extends Controller
     {
         //
 		$newData = $request->all();
-        Log::info("id={$id}");
-        Log::info($newData);
 		$result = UserDict::where('id', $id)
 				->update($newData);
 		if($result){
@@ -178,21 +196,16 @@ class UserDictController extends Controller
     public function destroy(Request $request,$id)
     {
         //
-		Log::info("userDictController->destroy start");
-		Log::info("userDictController->destroy id= {$id}");
-
-        if(isset($_COOKIE["user_id"])){
-            $user_id = $_COOKIE["user_id"];
-        }else{
-            $user = AuthApi::current($request);
-            if(!$user){
-                return $this->error(__('auth.failed'));
-            }
-            $user_id = $user['user_id'];
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'),[],403);
         }
+        $user_id = $user['user_id'];
+
         if($request->has("id")){
             $arrId = json_decode($request->get("id"),true) ;
             $count = 0;
+            $updateOk = false;
             foreach ($arrId as $key => $id) {
                 # 找到对应数据
                 $data = UserDict::find($id);
@@ -219,28 +232,23 @@ class UserDictController extends Controller
 
     }
 	public function delete(Request $request){
-		Log::info("userDictController->delete start");
 		$arrId = json_decode($request->get("id"),true) ;
-		Log::info("id=".$request->get("id"));
 		$count = 0;
 		$updateOk = false;
 		foreach ($arrId as $key => $id) {
 			$data = UserDict::where('id',$id)->first();
 			if($data){
 				# 找到对应数据
-				Log::info('creator_id:'.$data->creator_id);
 				$param = [
 					"id"=>$id,
 					'creator_id'=>$_COOKIE["user_id"]
 				];
-				Log::info($param);
 				$del = UserDict::where($param)->delete();
 				$count += $del;
 				$updateOk = $this->update_sys_wbw($data);
 				$this->update_redis($data);
 			}
 		}
-		Log::info("delete:".$count);
 		return $this->ok(['deleted'=>$count]);
 	}
 
@@ -250,17 +258,24 @@ class UserDictController extends Controller
 	private function update_sys_wbw($data){
 
 		#查询用户重复的数据
+        if(!isset($data["type"])){$data["type"]=null;}
+        if(!isset($data["grammar"])){$data["grammar"]=null;}
+        if(!isset($data["parent"])){$data["parent"]=null;}
+        if(!isset($data["mean"])){$data["mean"]=null;}
+        if(!isset($data["factors"])){$data["factors"]=null;}
+        if(!isset($data["factormean"])){$data["factormean"]=null;}
+
 		$count = UserDict::where('word',$data["word"])
-		->where('type',$data["type"])
-		->where('grammar',$data["grammar"])
-		->where('parent',$data["parent"])
-		->where('mean',$data["mean"])
-		->where('factors',$data["factors"])
-		->where('factormean',$data["factormean"])
-		->where('source','_USER_WBW_')
-		->count();
+                        ->where('type',$data["type"])
+                        ->where('grammar',$data["grammar"])
+                        ->where('parent',$data["parent"])
+                        ->where('mean',$data["mean"])
+                        ->where('factors',$data["factors"])
+                        ->where('factormean',$data["factormean"])
+                        ->where('source',$data["source"])
+                        ->count();
 
-		if($count==0){
+		if($count === 0){
             # 没有任何用户有这个数据
 			#删除数据
 			$result = UserDict::where('word',$data["word"])
@@ -284,7 +299,7 @@ class UserDictController extends Controller
 							->where('mean',$data["mean"])
 							->where('factors',$data["factors"])
 							->where('factormean',$data["factormean"])
-							->where('source','_USER_WBW_')
+							->whereIn('source',['_USER_WBW_','_USER_DICT_'])
 							->orderby("created_at",'asc')
 							->value("creator_id");
 
@@ -297,11 +312,11 @@ class UserDictController extends Controller
                         ->where('factormean',$data["factormean"])
                         ->where('source','_SYS_USER_WBW_')
                         ->count();
-            if($count==0){
-                #系统字典没有 新增
+            if($count === 0){
+                #社区字典没有 新增
                 $result = UserDict::insert(
 				[
-                    'id' =>$snowflake->id(),
+                    'id' =>app('snowflake')->id(),
 					'word'=>$data["word"],
 					'type'=>$data["type"],
 					'grammar'=>$data["grammar"],
@@ -310,8 +325,9 @@ class UserDictController extends Controller
 					'factors'=>$data["factors"],
 					'factormean'=>$data["factormean"],
 					'source'=>"_SYS_USER_WBW_",
-                    'creator_id' => $creator_id,
+                    'creator_id' => $data["creator_id"],
 					'ref_counter' => 1,
+                    'dict_id' => DictApi::getSysDict('community_extract'),
                     "create_time"=>time()*1000
                     ]);
             }else{
@@ -358,10 +374,8 @@ class UserDictController extends Controller
 							);
 		}
 		$redisData = json_encode($redisWord,JSON_UNESCAPED_UNICODE);
-		Log::info("word={$word['word']} redis-data={$redisData}");
 		Redis::hSet("dict/user",$word['word'],$redisData);
 		$redisData1 = Redis::hGet("dict/user",$word['word']);
-		Log::info("word={$word['word']} redis-data1={$redisData1}");
 
 		#更新redis结束
 	}

+ 113 - 0
app/Http/Controllers/UserOperationDailyController.php

@@ -0,0 +1,113 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\UserOperationDaily;
+use Illuminate\Http\Request;
+use App\Http\Api\AuthApi;
+use App\Http\Api\UserApi;
+
+class UserOperationDailyController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        switch ($request->get('view')) {
+            case "user-all":
+                $queryUserUuid = UserApi::getIdByName($request->get('studio_name'));
+                $user = AuthApi::current($request);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                //TODO 判断是否有查看权限
+                if($queryUserUuid !== $user["user_uid"]){
+                    return $this->error(__('auth.failed'));
+                }
+                $result = UserOperationDaily::where('user_id',$user["user_id"])
+                                  ->select(['date_int','duration','hit'])
+                                  ->orderBy("date_int")
+                                  ->get();
+                break;
+            case "user-year":
+                $queryUserId = UserApi::getIntIdByName($request->get('studio_name'));
+                //TODO 判断是否有查看权限
+                $result = UserOperationDaily::where('user_id',$queryUserId)
+                                  ->select(['date_int','duration'])
+                                  ->orderBy("date_int")
+                                  ->get();
+                break;
+        }
+        return $this->ok(["rows"=>$result,"count"=>count($result)]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function show(UserOperationDaily $userOperationDaily)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(UserOperationDaily $userOperationDaily)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, UserOperationDaily $userOperationDaily)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(UserOperationDaily $userOperationDaily)
+    {
+        //
+    }
+}

+ 150 - 0
app/Http/Controllers/UserStatisticController.php

@@ -0,0 +1,150 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\UserOperationDaily;
+use App\Models\UserOperationLog;
+use App\Models\Wbw;
+use App\Models\Sentence;
+use App\Models\DhammaTerm;
+use App\Models\UserDict;
+use Illuminate\Http\Request;
+use App\Http\Api\AuthApi;
+use App\Http\Api\UserApi;
+use Illuminate\Support\Facades\Cache;
+
+class UserStatisticController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        //
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function show(string $userName)
+    {
+        //
+        $queryUserId = UserApi::getIntIdByName($userName);
+        $queryUserUuid = UserApi::getIdByName($userName);
+        $cacheExpiry = 600;
+        //总经验值
+        $expSum = Cache::remember("user/{$userName}/exp/sum",$cacheExpiry,function() use($queryUserId){
+			return UserOperationDaily::where('user_id',$queryUserId)
+                                     ->sum('duration');
+		});
+
+        //逐词解析
+        $wbwCount = Cache::remember("user/{$userName}/wbw/count",$cacheExpiry,function() use($queryUserId){
+                    return Wbw::where('editor_id',$queryUserId)
+                        ->count();
+                        });
+        //查字典次数
+        $lookupCount = Cache::remember("user/{$userName}/lookup/count",$cacheExpiry,function() use($queryUserId){
+                            return UserOperationLog::where('user_id',$queryUserId)
+                                                    ->where('op_type','dict_lookup')
+                                                    ->count();
+                                });
+        //译文
+        //TODO 判断是否是译文channel
+        $translationCount = Cache::remember("user/{$userName}/translation/count",$cacheExpiry,function() use($queryUserUuid){
+                            return Sentence::where('editor_uid',$queryUserUuid)
+                                           ->count();
+                            });
+        $translationCountPub = Cache::remember("user/{$userName}/translation/count-pub",$cacheExpiry,function() use($queryUserUuid){
+                                    return Sentence::where('editor_uid',$queryUserUuid)
+                                    ->where('status',30)
+                                    ->count();
+                                });
+        //术语
+        $termCount = Cache::remember("user/{$userName}/term/count",$cacheExpiry,function() use($queryUserId){
+                        return DhammaTerm::where('editor_id',$queryUserId)
+                                    ->count();
+                    });
+        $termCountWithNote = Cache::remember("user/{$userName}/term/count-note",$cacheExpiry,function() use($queryUserId){
+                                return DhammaTerm::where('editor_id',$queryUserId)
+                                                    ->where('note',"<>","")
+                                                    ->count();
+                                });
+        //单词本
+        $myDictCount = Cache::remember("user/{$userName}/dict/count",$cacheExpiry,function() use($queryUserId){
+                            return UserDict::where('creator_id',$queryUserId)
+                                        ->count();
+                        });
+
+        return $this->ok([
+            "exp" => ["sum"=>(int)$expSum],
+            "wbw" => ["count"=>(int)$wbwCount],
+            "lookup" => ["count"=>(int)$lookupCount],
+            "translation" =>["count"=>(int)$translationCount,
+                             "count_pub"=>(int)$translationCountPub],
+            "term" => ["count"=>(int)$termCount,
+                      "count_with_note"=>(int)$termCountWithNote],
+            "dict" => ["count"=>(int)$myDictCount],
+        ]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(UserOperationDaily $userOperationDaily)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, UserOperationDaily $userOperationDaily)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\UserOperationDaily  $userOperationDaily
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(UserOperationDaily $userOperationDaily)
+    {
+        //
+    }
+}

+ 57 - 30
app/Http/Controllers/ViewController.php

@@ -8,6 +8,9 @@ use App\Models\PaliText;
 use Illuminate\Http\Request;
 use Illuminate\Support\Str;
 use Illuminate\Support\Facades\Log;
+use App\Http\Resources\ViewResource;
+use App\Http\Api\AuthApi;
+use App\Http\Api\StudioApi;
 
 class ViewController extends Controller
 {
@@ -23,9 +26,6 @@ class ViewController extends Controller
                 break;
             case 'chapter':
                 # code...
-                $channel = $request->get("channel");
-                $book = $request->get("book");
-                $para = $request->get("para");
                 $target_id = ProgressChapter::where("channel_id",$request->get("channel"))
                                             ->where("book",$request->get("book"))
                                             ->where("para",$request->get("para"))
@@ -47,7 +47,7 @@ class ViewController extends Controller
         }else{
             return false;
         }
-        
+
     }
     /**
      * Display a listing of the resource.
@@ -69,25 +69,55 @@ class ViewController extends Controller
                 return $this->ok($count);
                 break;
             case 'user-recent':
-                if(!isset($_COOKIE["user_uid"])){
-                    return $this->error("no login");
+                $user = AuthApi::current($request);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
                 }
-                $user_id = $_COOKIE["user_uid"];
+                $user_id = $user["user_uid"];
 				$views =  View::where("user_id",$user_id)->orderBy('created_at','desc');
-				if($request->has("take")){
-					$views = $views->take($request->get("take"));
-				}else{
-					$views = $views->take(10);
-				}
+				$views = $views->take($request->get("take",10));
                 $items = $views->get();
-                
                 return $this->ok($items);
                 break;
+            case 'user':
+                $user = AuthApi::current($request);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                $user_id = $user["user_uid"];
+                $table =  View::where("user_id",$user_id);
+                break;
+            case 'studio':
+                # 获取studio内所有 数据
+                $user = AuthApi::current($request);
+                if(!$user){
+                    return $this->error(__('auth.failed'));
+                }
+                //判断当前用户是否有指定的studio的权限
+                $studioId = StudioApi::getIdByName($request->get('name'));
+                if($user['user_uid'] !== $studioId){
+                    return $this->error(__('auth.failed'));
+                }
+                $table = View::where('user_id',$studioId);
+                break;
             default:
                 # code...
                 break;
         }
-        
+        //处理搜索
+        if($request->has("search")){
+            $table = $table->where('name', 'like', "%".$request->get("search")."%");
+        }
+        //获取记录总条数
+        $count = $table->count();
+        //处理排序
+        $table = $table->orderBy($request->get("order",'updated_at'),$request->get("dir",'desc'));
+        //处理分页
+        $table = $table->skip($request->get("offset",0))
+                       ->take($request->get("limit",20));
+        //获取数据
+        $result = $table->get();
+        return $this->ok(["rows"=>ViewResource::collection($result),"count"=>$count]);
     }
 
     /**
@@ -111,27 +141,23 @@ class ViewController extends Controller
 */
         //根据target type 获取 target id
         $target_id = $this->getTargetId($request);
+        if(!$target_id){
+            return $this->error('no id');
+        }
         $clientIp = request()->ip();
         $param = [
             'target_id' => $target_id,
             'target_type' => $request->get("target_type"),
         ];
-        if(isset($_COOKIE['user_uid'])){
+        $user = AuthApi::current($request);
+        if($user){
             //已经登陆
-			Log::info('已经登陆');
-            $user_id = $_COOKIE['user_uid'];
+            $user_id = $user['user_uid'];
             $param['user_id'] = $user_id;
-        }else{
-			Log::info('没有登陆');
-            $param['user_ip'] = $clientIp;
         }
-		
+        $param['user_ip'] = $clientIp;
         $new = View::firstOrNew($param);
-		Log::info('获取记录或新建');
-		Log::info(print_r($new, true));
         $new->user_ip = $clientIp;
-		//获取标题 和 meta数据
-		Log::info('获取标题 和 meta数据');
 
 		switch($request->get("target_type")){
 			case "chapter":
@@ -142,20 +168,21 @@ class ViewController extends Controller
 				$new->org_title = PaliText::where("book",$request->get("book"))
 										->where("paragraph",$request->get("para"))
 										->value("toc");
-				Log::info('获取标题 成功');
-
+				//获取标题 成功
 				$new->meta = \json_encode([
 					"book"=>$request->get("book"),
 					"para"=>$request->get("para"),
 					"channel"=>$request->get("channel"),
+                    "mode"=>$request->get("mode","read"),
 				]);
-				Log::info('获取meta数据成功');
-
 				break;
+            default:
+                return $this->error('未知的数据类型');
+                break;
 		}
 		$new->count = $new->count+1;
         $new->save();
-		Log::info('保存成功');
+		//保存成功
 
         $count = View::where("target_id",$new->target_id)->count();
         return $this->ok($count);

+ 11 - 6
app/Http/Controllers/VocabularyController.php

@@ -5,6 +5,8 @@ namespace App\Http\Controllers;
 use App\Models\Vocabulary;
 use Illuminate\Http\Request;
 use App\Http\Resources\VocabularyResource;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
 
 class VocabularyController extends Controller
 {
@@ -18,12 +20,15 @@ class VocabularyController extends Controller
         //
         switch ($request->get("view")) {
             case 'key':
-                $result = Vocabulary::where('word_en','like',$request->get("key")."%")->take(20)->get();
-                if($result){
-                    return $this->ok(['rows'=>VocabularyResource::collection($result),'count'=>count($result)]);
-                }else{
-                    return $this->error();
-                }
+                $key = $request->get("key");
+                $result = Cache::remember("/dict_vocabulary/{$key}",10,function() use($key){
+                        return Vocabulary::whereRaw('word like ? or word_en like ?',[$key."%",$key."%"])
+                                    ->whereOr('word_en','like',$key."%")
+                                    ->orderBy('strlen')
+                                    ->orderBy('word')
+                                    ->take(10)->get();
+                });
+                return $this->ok(['rows'=>VocabularyResource::collection($result),'count'=>count($result)]);
                 break;
         }
     }

+ 189 - 0
app/Http/Controllers/WbwController.php

@@ -0,0 +1,189 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\Wbw;
+use App\Models\WbwBlock;
+use App\Models\Channel;
+use App\Models\PaliSentence;
+use App\Models\Sentence;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Str;
+use App\Tools\Tools;
+use App\Http\Api\AuthApi;
+use App\Http\Api\ShareApi;
+use App\Http\Api\ChannelApi;
+
+class WbwController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     * 新建多个
+     * 如果存在,修改
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+        //鉴权
+        $user = AuthApi::current($request);
+        if(!$user ){
+            //未登录用户
+            return $this->error(__('auth.failed'),[],401);
+        }
+        $channel = Channel::where('uid',$request->get('channel_id'))->first();
+        if(!$channel){
+            return $this->error(__('auth.failed'));
+        }
+        if($channel->owner_uid !== $user["user_uid"]){
+            //判断是否为协作
+            $power = ShareApi::getResPower($user["user_uid"],$channel->uid);
+            if($power<30){
+                return $this->error(__('auth.failed'));
+            }
+        }
+        //查看WbwBlock是否已经建立
+        $wbwBlockId = WbwBlock::where('book_id',$request->get('book'))
+                            ->where('paragraph',$request->get('para'))
+                            ->where('channel_uid',$request->get('channel_id'))
+                            ->value('uid');
+        if(!Str::isUuid($wbwBlockId)){
+            $wbwBlock = new WbwBlock();
+            $wbwBlockId = Str::uuid();
+            $wbwBlock->id = app('snowflake')->id();
+			$wbwBlock->uid = $wbwBlockId;
+            $wbwBlock->creator_uid = $user["user_uid"];
+            $wbwBlock->editor_id = $user["user_id"];
+            $wbwBlock->book_id = $request->get('book');
+            $wbwBlock->paragraph = $request->get('para');
+            $wbwBlock->channel_uid = $request->get('channel_id');
+            $wbwBlock->lang = $channel->lang;
+            $wbwBlock->status = $channel->status;
+            $wbwBlock->create_time = time()*1000;
+            $wbwBlock->modify_time = time()*1000;
+            $wbwBlock->save();
+        }
+        $wbw = Wbw::where('block_uid',$wbwBlockId)
+                        ->where('wid',$request->get('sn'))
+                        ->first();
+        if(!$wbw){
+            //建立一个句子的逐词解析数据
+            //找到句子
+            $sent = PaliSentence::where('book',$request->get('book'))
+                                 ->where('paragraph',$request->get('para'))
+                                 ->where('word_begin',"<=",$request->get('sn'))
+                                 ->where('word_end',">=",$request->get('sn'))
+                                 ->first();
+            $channelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
+            $wbwContent = Sentence::where('book_id',$sent->book)
+							->where('paragraph',$sent->paragraph)
+							->where('word_start',$sent->word_begin)
+							->where('word_end',$sent->word_end)
+							->where('channel_uid',$channelId)
+							->value('content');
+            $words = json_decode($wbwContent);
+            foreach ($words as $word) {
+                # code...
+                $xmlObj = simplexml_load_string("<word></word>");
+                $xmlObj->addChild('id',"{$sent->book}-{$sent->paragraph}-{$word->sn[0]}");
+                $xmlObj->addChild('pali',$word->word->value)->addAttribute('status',0);
+                $xmlObj->addChild('real',$word->real->value)->addAttribute('status',0);
+                $xmlObj->addChild('type',$word->type->value)->addAttribute('status',0);
+                $xmlObj->addChild('gramma',$word->grammar->value)->addAttribute('status',0);
+                $xmlObj->addChild('case',$word->case->value)->addAttribute('status',0);
+                $xmlObj->addChild('style',$word->style->value)->addAttribute('status',0);
+                $xmlObj->addChild('org',$word->factors->value)->addAttribute('status',0);
+                $xmlObj->addChild('om',$word->factorMeaning->value)->addAttribute('status',0);
+                $xmlObj->addChild('status',1);
+                $xml = $xmlObj->asXml();
+                $xml = str_replace('<?xml version="1.0"?>','',$xml);
+
+                $newWbw = new Wbw();
+                $newWbw->id = app('snowflake')->id();
+                $newWbw->uid = Str::uuid();
+                $newWbw->creator_uid = $channel->owner_uid;
+                $newWbw->editor_id = $user["user_id"];
+                $newWbw->book_id = $request->get('book');
+                $newWbw->paragraph = $request->get('para');
+                $newWbw->wid = $word->sn[0];
+                $newWbw->block_uid = $wbwBlockId;
+                $newWbw->data = $xml;
+                $newWbw->word = $word->real->value;
+                $newWbw->status = 0;
+                $newWbw->create_time = time()*1000;
+                $newWbw->modify_time = time()*1000;
+                $newWbw->save();
+                if($word->sn[0] === $request->get('sn')){
+                    $wbw = $newWbw;
+                }
+            }
+        }
+
+        $count=0;
+        foreach ($request->get('data') as $row) {
+            $wbw = Wbw::where('block_uid',$wbwBlockId)
+                        ->where('wid',$row['sn'])
+                        ->first();
+            if($wbw){
+                $wbwData = "";
+                foreach ($row['words'] as $word) {
+                    $xml = Tools::JsonToXml($word);
+                    $xml = str_replace('<?xml version="1.0"?>','',$xml);
+                    $wbwData .= $xml;
+                }
+                $wbw->data = $wbwData;
+                $wbw->status = 5;
+                $wbw->save();
+                $count++;
+            }
+        }
+
+        return $this->ok(['rows'=>[],"count"=>$count]);
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\Wbw  $wbw
+     * @return \Illuminate\Http\Response
+     */
+    public function show(Wbw $wbw)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\Wbw  $wbw
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, Wbw $wbw)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\Wbw  $wbw
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Wbw $wbw)
+    {
+        //
+    }
+}

+ 351 - 73
app/Http/Controllers/WbwLookupController.php

@@ -5,10 +5,13 @@ namespace App\Http\Controllers;
 use App\Models\UserDict;
 use App\Models\DictInfo;
 use App\Models\WbwTemplate;
+use App\Models\Channel;
+use App\Models\WbwAnalysis;
 use Illuminate\Http\Request;
 use App\Tools\CaseMan;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
+use App\Http\Api\DictApi;
 
 
 
@@ -17,17 +20,29 @@ class WbwLookupController extends Controller
 	private $dictList = [
 		'85dcc61c-c9e1-4ae0-9b44-cd6d9d9f0d01',//社区汇总
 		'4d3a0d92-0adc-4052-80f5-512a2603d0e8',// system irregular
-		'57afac99-0887-455c-b18e-67c8682158b0',// system regular
-		'ef620a93-a55d-4756-89c5-e188ab009e45',//社区字典
 		'8359757e-9575-455b-a772-cc6f036caea0',// system sandhi
-		'c42980f0-5967-4833-b695-84183344f68f',// robot compound
 		'61f23efb-b526-4a8e-999e-076965034e60',// pali myanmar grammar
 		'eae9fd6f-7bac-4940-b80d-ad6cd6f433bf',// Concise P-E Dict
 		'2f93d0fe-3d68-46ee-a80b-11fa445a29c6',// unity
 		'beb45062-7c20-4047-bcd4-1f636ba443d1',// U Hau Sein
 		'8833de18-0978-434c-b281-a2e7387f69be',// 巴汉增订
 		'3acf0c0f-59a7-4d25-a3d9-bf394a266ebd',// 汉译パーリ语辞典-黃秉榮
+        '9ce6a53b-e28f-4fb7-b69d-b35fd5d76a24',//缅英字典
 	];
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    private function initSysDict()
+    {
+        // system regular
+        $this->dictList[] = DictApi::getSysDict('system_regular');
+        $this->dictList[] = DictApi::getSysDict('robot_compound');
+        $this->dictList[] = DictApi::getSysDict('community');
+        $this->dictList[] = DictApi::getSysDict('community_extract');
+    }
+
     /**
      * Display a listing of the resource.
      * @param  \Illuminate\Http\Request  $request
@@ -36,9 +51,34 @@ class WbwLookupController extends Controller
      */
     public function index(Request $request)
     {
+
         //
-		$startAt = microtime(true);
+		$startAt = microtime(true)*1000;
 
+        $this->initSysDict();
+
+		$words = \explode(',',$request->get("word"));
+        $bases = \explode(',',$request->get("base"));
+        # 查询深度
+		$deep = $request->get("deep",2);
+        $result = $this->lookup($words,$bases,$deep);
+        $endAt = microtime(true)*1000;
+
+
+		return $this->ok(["rows"=>$result,
+                          "count"=>count($result),
+                          "time"=>(int)($endAt-$startAt)]);
+    }
+
+    public function lookup($words,$bases,$deep){
+		$wordPool = array();
+		$output  = array();
+        foreach ($words as $word) {
+			$wordPool[$word] = ['base' => false,'done' => false,'apply' => false];
+		}
+		foreach ($bases as $base) {
+			$wordPool[$base] = ['base' => true,'done' => false,'apply' => false];
+		}
         /**
          * 先查询字典名称
          */
@@ -48,66 +88,33 @@ class WbwLookupController extends Controller
             # code...
             $dict_name[$value->id] = $value->shortname;
         }
-		$caseman = new CaseMan();
-		$output  = array();
-		$wordPool = array();
-		$input = \explode(',',$request->get("word"));
-		foreach ($input as $word) {
-			$wordPool[$word] = ['base' => false,'done' => false,'apply' => false];
-		}
-		Log::info("query start ".$request->get("word"));
-		if(empty($request->get("deep"))){
-			$deep = 2;
-		}else{
-			$deep = $request->get("deep");
-		}
+        $caseman = new CaseMan();
 		for ($i=0; $i < $deep; $i++) {
-			# 查询深度
-			foreach ($wordPool as $word => $info) {
-				# code...
-				if($info['done'] === false){
-					$wordPool[$word]['done'] = true;
-					$count = 0;
-					foreach ($this->dictList as  $dictId) {
-						# code...
-						$result = Cache::remember("dict/{$dictId}/".$word,10,function() use($word,$dictId,$dict_name){
-                            $data = UserDict::where('word',$word)->where('dict_id',$dictId)->orderBy('confidence','desc')->get();
-                            foreach ($data as $key => $value) {
-                                # code...
-                                $value->dict_shortname  = $dict_name[$dictId];
-                            }
-							return $data;
-						});
-						$count += count($result);
-						if(count($result)>0){
-							foreach ($result as  $dictword) {
-								# code...
-								array_push($output,$dictword);
-								if(!isset($wordPool[$word]['factors']) && !empty($dictword->factors)){
-									//将第一个拆分作为最佳拆分存储
-									$wordPool[$word]['factors'] = $dictword->factors;
-									Log::info("best factor:{$dictword->factors}");
-								}
-							}
-						}
-					}
+            $newBase = array();
 
-					Log::info("query {$word} ".((microtime(true)-$startAt)*1000)."s.");
-					if($count == 0){
-						//没查到 去尾查
-						Log::info("没查到 去尾查");
-						$newBase = array();
-						$parents = $caseman->WordToBase($word);
-						foreach ($parents as $base => $rows) {
-							Log::info("found:{$base}");
-							array_push($output,$rows);
-						}
-						Log::info("去尾查结束");
-					}
-				}
-			}
+            $newWords = [];
+            foreach ($wordPool as $word => $info) {
+                # code...
+                if($info['done'] === false){
+                    $newWords[] = $word;
+                    $wordPool[$word]['done'] = true;
+                }
+            }
+            $data = UserDict::whereIn('word',$newWords)
+                            ->whereIn('dict_id',$this->dictList)
+                            ->leftJoin('dict_infos', 'user_dicts.dict_id', '=', 'dict_infos.id')
+                            ->orderBy('confidence','desc')
+                            ->get();
+            foreach ($data as $row) {
+                # code...
+                array_push($output,$row);
+                if(!empty($row->parent) && !isset($wordPool[$row->parent]) ){
+                    //将parent 插入待查询列表
+                    $wordPool[$row->parent] = ['base' => true,'done' => false,'apply' => false];
+                }
+            }
 
-			//查询结果中的拆分信息
+			//处理查询结果中的拆分信息
 			$newWordPart = array();
 			foreach ($wordPool as $word => $info) {
 				if(!empty($info['factors'])){
@@ -115,23 +122,24 @@ class WbwLookupController extends Controller
 					foreach ($factors as $factor) {
 						# 将没有的拆分放入单词查询列表
 						if(!isset($wordPool[$factor])){
-							$newWordPart[$factor] = 0;
+							$wordPool[$factor] = ['base' => true,'done' => false,'apply' => false];
 						}
 					}
 				}
 			}
-			foreach ($newWordPart as $part => $value) {
-				# 将拆分放入池中
-				$wordPool[$part] = ['base' => false,'done' => false,'apply' => false];
-			}
-			Log::info("loop {$i} ".((microtime(true)-$startAt)*1000)."s.");
 		}
 
-		return $this->ok(["rows"=>$output,'count'=>count($output)]);
+        return $output;
+    }
+    private function langCheck($query,$lang){
+        if($query===[]){
+            return true;
+        }else{
+            return in_array($lang,$query);
+        }
     }
-
     /**
-     * Store a newly created resource in storage.
+     * 自动查词
      *
      * @param  \Illuminate\Http\Request  $request
      * @return \Illuminate\Http\Response
@@ -139,27 +147,297 @@ class WbwLookupController extends Controller
     public function store(Request $request)
     {
         //
+        $startAt = microtime(true)*1000;
+
+        // system regular
+        $this->initSysDict();
+
+        $channel = Channel::find($request->get('channel_id'));
+        $orgData = $request->get('data');
+        $lang = $request->get('lang',[]);
+        //句子中的单词
+        $words = [];
+        foreach ($orgData as  $word) {
+            # code...
+            if( isset($word['type']) && $word['type']['value'] === '.ctl.'){
+                continue;
+            }
+            if(!empty($word['real']['value'])){
+                $words[] = $word['real']['value'];
+            }
+        }
+
+        $result = $this->lookup($words,[],2);
+        $indexed = $this->toIndexed($result);
+
+        foreach ($orgData as  $key => $word) {
+            if( isset($word['type']) && $word['type']['value'] === '.ctl.'){
+                continue;
+            }
+            if(empty($word['real']['value'])){
+                continue;
+            }
+            {
+                $data = $word;
+                if(isset($indexed[$word['real']['value']])){
+                    //parent
+                    $case = [];
+                    $parent = [];
+                    $factors = [];
+                    $factorMeaning = [];
+                    $meaning = [];
+                    $parent2 = [];
+                    $case2 = [];
+                    foreach ($indexed[$word['real']['value']] as $value) {
+                        //非base优先
+                        if(strstr($value->type,'base') === FALSE){
+                            $increment = 10;
+                        }else{
+                            $increment = 1;
+                        }
+                        //将全部结果加上得分放入数组
+                        $parent = $this->insertValue([$value->parent],$parent,$increment);
+                        if(!empty($value->type) && $value->type !== ".cp."){
+                            $case = $this->insertValue([$value->type."#".$value->grammar],$case,$increment);
+                        }
+                        $factors = $this->insertValue([$value->factors],$factors,$increment);
+                        $factorMeaning = $this->insertValue([$value->factormean],$factorMeaning,$increment);
+                        if($this->langCheck($lang,$value->language)){
+                            $meaning = $this->insertValue(explode('$',$value->mean),$meaning,$increment,false);
+                        }
+                    }
+                    if(count($case)>0){
+                        arsort($case);
+                        $first = array_keys($case)[0];
+                        $data['case'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    if(count($parent)>0){
+                        arsort($parent);
+                        $first = array_keys($parent)[0];
+                        $data['parent'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    if(count($factors)>0){
+                        arsort($factors);
+                        $first = array_keys($factors)[0];
+                        $data['factors'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    //拆分意思
+                    if(count($factorMeaning)>0){
+                        arsort($factorMeaning);
+                        $first = array_keys($factorMeaning)[0];
+                        $data['factorMeaning'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    $wbwFactorMeaning = [];
+                    if(!empty($data['factors']['value'])){
+                        foreach (explode("+",$data['factors']['value']) as  $factor) {
+                            # code...
+                            $wbwAnalyses = WbwAnalysis::where('wbw_word',$factor)
+                                                      ->where('type',7)
+                                                      ->selectRaw('data,count(*)')
+                                                      ->groupBy("data")
+                                                      ->orderBy("count", "desc")
+                                                      ->first();
+                            if($wbwAnalyses){
+                                $wbwFactorMeaning[]=$wbwAnalyses->data;
+                            }else{
+                                $wbwFactorMeaning[]="";
+                            }
+                        }
+                    }
+                    $data['factorMeaning'] = ['value'=>implode('+',$wbwFactorMeaning),'status'=>3];
+
+                    if(!empty($data['parent'])){
+                        if(isset($indexed[$data['parent']['value']])){
+                            foreach ($indexed[$data['parent']['value']] as $value) {
+                                //根据base 查找词意
+                                //非base优先
+                                $increment = 10;
+                                if($this->langCheck($lang,$value->language)){
+                                    $meaning = $this->insertValue(explode('$',$value->mean),$meaning,$increment,false);
+                                }
+                                //查找词源
+                                if(!empty($value->parent) && $value->parent !== $value->word && strstr($value->type,"base") !== FALSE ){
+                                    $parent2 = $this->insertValue([$value->grammar."$".$value->parent],$parent2,1,false);
+                                }
+                            }
+                        }
+                    }
+                    if(count($meaning)>0){
+                        arsort($meaning);
+                        $first = array_keys($meaning)[0];
+                        $data['meaning'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    if(count($parent2)>0){
+                        arsort($parent2);
+                        $first = explode("$",array_keys($parent2)[0]);
+                        $data['parent2'] = ['value'=>$first[1],'status'=>3];
+                        $data['grammar2'] = ['value'=>$first[0],'status'=>3];
+                    }
+                }
+                $orgData[$key] = $data;
+            }
+        }
+        return $this->ok($orgData);
     }
 
     /**
-     * Display the words best match in specified sentence .
+     * 自动查词
      *
      * @param  string  $sentId
      * @return \Illuminate\Http\Response
      */
-    public function show(string $sentId)
+    public function show(Request $request,string $sentId)
     {
+        $startAt = microtime(true)*1000;
+
+        $channel = Channel::find($request->get('channel_id'));
+
         //查询句子中的单词
         $sent = \explode('-',$sentId);
-        WbwTemplate::where('book',$sent[0])
+        $wbw = WbwTemplate::where('book',$sent[0])
                 ->where('paragraph',$sent[1])
                 ->whereBetween('wid',[$sent[2],$sent[3]])
                 ->orderBy('wid')
                 ->get();
+        $words = [];
+        foreach ($wbw as  $row) {
+            if($row->type !== '.ctl.' && !empty($row->real)){
+                $words[] = $row->real;
+            }
+        }
+        $result = $this->lookup($words,[],2);
+        $indexed = $this->toIndexed($result);
+
+        //生成自动填充结果
+        $wbwContent = [];
+        foreach ($wbw as  $row) {
+            $type = $row->type=='?'? '':$row->type;
+            $grammar = $row->gramma=='?'? '':$row->gramma;
+            $part = $row->part=='?'? '':$row->part;
+            if(!empty($type) || !empty($grammar)){
+                $case = "{$type}#$grammar";
+            }else{
+                $case = "";
+            }
+            $data = [
+                    'sn'=>[$row->wid],
+                    'word'=>['value'=>$row->word,'status'=>3],
+                    'real'=> ['value'=>$row->real,'status'=>3],
+                    'meaning'=> ['value'=>[],'status'=>3],
+                    'type'=> ['value'=>$type,'status'=>3],
+                    'grammar'=> ['value'=>$grammar,'status'=>3],
+                    'case'=> ['value'=>$case,'status'=>3],
+                    'style'=> ['value'=>$row->style,'status'=>3],
+                    'factors'=> ['value'=>$part,'status'=>3],
+                    'factorMeaning'=> ['value'=>'','status'=>3],
+                    'confidence'=> 0.5
+                ];
+            if($row->type !== '.ctl.' && !empty($row->real)){
+                if(isset($indexed[$row->real])){
+                    //parent
+                    $case = [];
+                    $parent = [];
+                    $factors = [];
+                    $factorMeaning = [];
+                    $meaning = [];
+                    $parent2 = [];
+                    $case2 = [];
+                    foreach ($indexed[$row->real] as $value) {
+                        //非base优先
+                        if(strstr($value->type,'base') === FALSE){
+                            $increment = 10;
+                        }else{
+                            $increment = 1;
+                        }
+                        //将全部结果加上得分放入数组
+                        $parent = $this->insertValue([$value->parent],$parent,$increment);
+                        $case = $this->insertValue([$value->type."#".$value->grammar],$case,$increment);
+                        $factors = $this->insertValue([$value->factors],$factors,$increment);
+                        $factorMeaning = $this->insertValue([$value->factormean],$factorMeaning,$increment);
+                        $meaning = $this->insertValue(explode('$',$value->mean),$meaning,$increment,false);
+                    }
+                    if(count($case)>0){
+                        arsort($case);
+                        $first = array_keys($case)[0];
+                        $data['case'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    if(count($parent)>0){
+                        arsort($parent);
+                        $first = array_keys($parent)[0];
+                        $data['parent'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    if(count($factors)>0){
+                        arsort($factors);
+                        $first = array_keys($factors)[0];
+                        $data['factors'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+                    if(count($factorMeaning)>0){
+                        arsort($factorMeaning);
+                        $first = array_keys($factorMeaning)[0];
+                        $data['factorMeaning'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
+
+                    //根据base 查找词意
+                    if(!empty($data['parent'])){
+                        if(isset($indexed[$data['parent']['value']])){
+                            Log::info($data['parent']['value']."=".count($indexed[$data['parent']['value']]));
+                            foreach ($indexed[$data['parent']['value']] as $value) {
+                                //非base优先
+                                $increment = 10;
+                                $meaning = $this->insertValue(explode('$',$value->mean),$meaning,$increment,false);
+                            }
+                        }else{
+                            Log::error("no set parent".$data['parent']['value']);
+                        }
+                    }
+                    if(count($meaning)>0){
+                        arsort($meaning);
+                        Log::info('meanings=');
+                        Log::info(array_keys($meaning));
+                        $first = array_keys($meaning)[0];
+                        $data['meaning'] = ['value'=>$first==="_null"?"":$first,'status'=>3];
+                    }
 
+                }
+            }
+            $wbwContent[]  = $data;
+        }
+        $endAt = microtime(true)*1000;
+        return $this->ok(["rows"=>$wbwContent,
+                        "count"=>count($wbwContent),
+                        "time"=>(int)($endAt-$startAt)]);
+    }
 
+    private function toIndexed($words){
+        //转成索引数组
+        $indexed = [];
+        foreach ($words as $key => $value) {
+            # code...
+            $indexed[$value->word][] = $value;
+        }
+        return $indexed;
     }
 
+    private function insertValue($value,$container,$increment,$empty=true){
+        foreach ($value as $one) {
+            if($empty === false){
+                if(empty($one)){
+                    break;
+                }
+            }
+            $one=trim($one);
+            $key = $one;
+            if(empty($key)){
+                $key = '_null';
+            }
+            if(isset($container[$key])){
+                $container[$key] += $increment;
+            }else{
+                $container[$key] = $increment;
+            }
+        }
+        return $container;
+    }
     /**
      * Update the specified resource in storage.
      *

+ 94 - 0
app/Http/Controllers/WordIndexController.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\WordIndex;
+use Illuminate\Http\Request;
+use App\Http\Resources\WordIndexResource;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\DB;
+
+class WordIndexController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //
+        switch ($request->get("view")) {
+            case 'key':
+                $key = $request->get("key");
+                /*
+                $result = Cache::remember("/word_index/{$key}",10,function() use($key){
+                    return WordIndex::where('word','like',$key."%")
+                                    ->whereOr('word_en','like',$key."%")
+                                    ->orderBy('word_en')
+                                    ->take(10)->get();
+                });
+                $table = WordIndex::where('word','like',$key."%")
+                                   ->whereOr('word_en','like',$key."%")
+                                   ->orderBy('len')
+                                   ->orderBy('word_en')
+                                   ->take(10);
+                Log::info($table->toSql());
+                $result = $table->get();
+*/
+                $result = DB::select("SELECT * from  word_indices where word like ? or word_en like ? order by len, word_en limit 10",[$key."%",$key."%"]);
+
+                return $this->ok(['rows'=>$result,'count'=>count($result)]);
+                break;
+            default:
+                return $this->error('view error');
+                break;
+        }
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\WordIndex  $wordIndex
+     * @return \Illuminate\Http\Response
+     */
+    public function show(WordIndex $wordIndex)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\WordIndex  $wordIndex
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, WordIndex $wordIndex)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\WordIndex  $wordIndex
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(WordIndex $wordIndex)
+    {
+        //
+    }
+}

+ 1 - 0
app/Http/Kernel.php

@@ -22,6 +22,7 @@ class Kernel extends HttpKernel
         \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
         \App\Http\Middleware\TrimStrings::class,
         \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
+        \App\Http\Middleware\UserOperation::class,
     ];
 
     /**

+ 0 - 40
app/Http/Middleware/EnableCrossRequestMiddleware.php

@@ -1,40 +0,0 @@
-<?php
-
-namespace App\Http\Middleware;
-
-use Closure;
-use Illuminate\Http\Request;
-
-class EnableCrossRequestMiddleware
-{
-    /**
-     * Handle an incoming request.
-     *
-     * @param  \Illuminate\Http\Request  $request
-     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
-     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
-     */
-    public function handle(Request $request, Closure $next)
-    {
-        $response = $next($request);
-        $origin = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
-        $allow_origin = [
-            env("CROSS_REQUEST_ALLOW_ORIGIN",'http://localhost:8001'),
-        ];
-        if (in_array($origin, $allow_origin)) {
-            $response->header('Access-Control-Allow-Origin', $origin);
-            $response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
-            $response->header('Access-Control-Expose-Headers', 'Authorization, authenticated');
-            $response->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, OPTIONS');
-            $response->header('Access-Control-Allow-Credentials', 'true');
-        }
-        /*
-        ————————————————
-        原文作者:qbhy
-        转自链接:https://learnku.com/articles/6504/laravel-cross-domain-solution
-        版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。
-        */
-        //return $next($request);
-		return $response;
-    }
-}

+ 256 - 0
app/Http/Middleware/UserOperation.php

@@ -0,0 +1,256 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Storage;
+use App\Models\UserOperationLog;
+use App\Models\UserOperationFrame;
+use App\Models\UserOperationDaily;
+use App\Http\Api\AuthApi;
+
+define("MAX_INTERVAL", 600000);
+define("MIN_INTERVAL", 60000);
+
+/**
+ * 	$active_type[10] = "channel_update";
+ * 	$active_type[11] = "channel_create";
+ * 	$active_type[20] = "article_update";
+ * 	$active_type[21] = "article_create";
+ * 	$active_type[30] = "dict_lookup";
+ * 	$active_type[40] = "term_update";
+ * 	$active_type[42] = "term_create";
+ * 	$active_type[41] = "term_lookup";
+ * 	$active_type[60] = "wbw_update";
+ * 	$active_type[61] = "wbw_create";
+ * 	$active_type[70] = "sent_update";
+ * 	$active_type[71] = "sent_create";
+ * 	$active_type[80] = "collection_update";
+ * 	$active_type[81] = "collection_create";
+ * 	$active_type[90] = "nissaya_open";
+ */
+class UserOperation
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
+     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
+     */
+    public function handle(Request $request, Closure $next)
+    {
+        $response = $next($request);
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $response;
+        }
+
+
+        $api = explode('/',$request->path());
+        if(count($api)<3){
+            return $response;
+        }if($api[0] !== 'api' || $api[1] !=='v2'){
+            return $response;
+        }
+        $method = $request->method();
+        switch ($api[2]) {
+            case 'channel':
+                switch ($method) {
+                    case 'POST':
+                        $newLog = [
+                            "op_type_id"=>11,
+                            "op_type"=>"channel_create",
+                            "content"=>$request->get('studio').'/'.$request->get('name'),
+                        ];
+                        break;
+                    case 'PUT':
+                        $newLog = [
+                            "op_type_id"=>10,
+                            "op_type"=>"channel_update",
+                            "content"=>$request->get('name'),
+                        ];
+                        break;
+                }
+                break;
+            case 'article':
+                switch ($method) {
+                    case 'POST':
+                        $newLog = [
+                            "op_type_id"=>21,
+                            "op_type"=>"article_create",
+                            "content"=>$request->get('studio').'/'.$request->get('title'),
+                        ];
+                        break;
+                    case 'PUT':
+                        $newLog = [
+                            "op_type_id"=>20,
+                            "op_type"=>"article_update",
+                            "content"=>$request->get('title'),
+                        ];
+                        break;
+                }
+                break;
+            case 'dict':
+                $newLog = [
+                    "op_type_id"=>30,
+                    "op_type"=>"dict_lookup",
+                    "content"=>$request->get("word")
+                ];
+                break;
+            case 'terms':
+                switch ($method) {
+                    case 'POST':
+                        $newLog = [
+                            "op_type_id"=>42,
+                            "op_type"=>"term_create",
+                            "content"=>$request->get('word'),
+                        ];
+                        break;
+                    case 'PUT':
+                        $newLog = [
+                            "op_type_id"=>40,
+                            "op_type"=>"term_update",
+                            "content"=>$request->get('word'),
+                        ];
+                        break;
+                }
+                break;
+            case 'sentence':
+                switch ($method) {
+                    case 'POST':
+                        $newLog = [
+                            "op_type_id"=>71,
+                            "op_type"=>"sent_create",
+                            "content"=>$request->get('channel'),
+                        ];
+                        break;
+                    case 'PUT':
+                        $newLog = [
+                            "op_type_id"=>70,
+                            "op_type"=>"sent_update",
+                            "content"=>$request->get('channel'),
+                        ];
+                        break;
+                }
+                break;
+            case 'anthology':
+                switch ($method) {
+                    case 'POST':
+                        $newLog = [
+                            "op_type_id"=>81,
+                            "op_type"=>"collection_create",
+                            "content"=>$request->get('title'),
+                        ];
+                        break;
+                    case 'PUT':
+                        $newLog = [
+                            "op_type_id"=>80,
+                            "op_type"=>"collection_update",
+                            "content"=>$request->get('title'),
+                        ];
+                        break;
+                }
+                break;
+            case 'wbw':
+                switch ($method) {
+                    case 'POST':
+                        $newLog = [
+                            "op_type_id"=>60,
+                            "op_type"=>"wbw_update",
+                            "content"=>$request->get('book')."_".$request->get('para')."_".$request->get('channel_id'),
+                        ];
+                        break;
+                }
+                break;
+        }
+        if(isset($newLog)){
+            $currTime = round((microtime(true))*1000,0);
+            #获取客户端时区偏移 beijing = +8
+            if (isset($_COOKIE["timezone"])) {
+                $client_timezone = (0 - (int) $_COOKIE["timezone"]) * 60 * 1000;
+            } else {
+                $client_timezone = 0;
+            }
+
+            $log = new UserOperationLog();
+            $log->id = app('snowflake')->id();
+            $log->user_id = $user['user_id'];
+            $log->op_type_id = $newLog["op_type_id"];
+            $log->op_type = $newLog["op_type"];
+            $log->content = $newLog["content"];
+            $log->timezone = $client_timezone;
+            $log->create_time = $currTime;
+            $log->save();
+
+            //frame
+            // 查询上次编辑活跃结束时间
+            $last = UserOperationFrame::where("user_id",$user['user_id'])->orderBy("updated_at","desc")->first();
+            if($last){
+                //找到,判断是否超时,超时新建,未超时修改
+                $id = (int) $last["id"];
+                $start_time = (int) $last["op_start"];
+                $endtime = (int) $last["op_end"];
+                $hit = (int) $last["hit"];
+                if ($currTime - $endtime > MAX_INTERVAL) {
+                    //超时新建
+                    $new_record = true;
+                } else {
+                    //未超时修改
+                    $new_record = false;
+                }
+            }else{
+                //没找到,新建
+                $new_record = true;
+            }
+            $this_active_time = 0; //时间增量
+            if ($new_record) {
+                #新建
+                $newFrame = new UserOperationFrame();
+                #最小思考时间 MIN_INTERVAL
+                $newFrame->id = app('snowflake')->id();
+                $newFrame->user_id = $user['user_id'];
+                $newFrame->op_start = $currTime - MIN_INTERVAL;
+                $newFrame->op_end = $currTime;
+                $newFrame->duration = MIN_INTERVAL;
+                $newFrame->hit = 1;
+                $newFrame->timezone = $client_timezone;
+                $newFrame->save();
+                $this_active_time = MIN_INTERVAL;
+            } else {
+                #修改
+                $last->op_end = $currTime;
+                $last->duration = $currTime - $start_time;
+                $last->hit = $last->hit + 1;
+                $last->save();
+            }
+
+            #更新经验总量表
+            #计算客户端日期 unix时间戳 以毫秒计
+            $client_currtime = $currTime + $client_timezone;
+            $client_date = strtotime(gmdate("Y-m-d", $client_currtime / 1000)) * 1000;
+
+            #查询是否存在
+            $daily = UserOperationDaily::where("user_id",$user['user_id'])->where("date_int",$client_date)->first();
+            if ($daily) {
+                #更新
+                $daily->duration = $daily->duration + $this_active_time;
+                $daily->hit = $daily->hit + 1;
+                $daily->save();
+            } else {
+                #新建
+                $daily = new UserOperationDaily();
+                $daily->id = app('snowflake')->id();
+                $daily->user_id = $user['user_id'];
+                $daily->date_int = $client_date;
+                $daily->duration = MIN_INTERVAL;
+                $daily->hit = 1;
+                $daily->save();
+            }
+            #更新经验总量表结束
+        }
+
+        return $response;
+    }
+}

+ 23 - 6
app/Http/Resources/ArticleResource.php

@@ -4,10 +4,15 @@ namespace App\Http\Resources;
 
 use Illuminate\Http\Resources\Json\JsonResource;
 use App\Http\Api\MdRender;
-use App\Http\Api\UserApi;
 use App\Models\CourseMember;
 use App\Models\Course;
+use App\Models\Collection;
+use App\Models\ArticleCollection;
 use Illuminate\Support\Facades\Log;
+use App\Http\Api\UserApi;
+use App\Http\Api\StudioApi;
+use App\Http\Api\AuthApi;
+use App\Http\Controllers\ArticleController;
 
 class ArticleResource extends JsonResource
 {
@@ -24,13 +29,28 @@ class ArticleResource extends JsonResource
             "title" => $this->title,
             "subtitle" => $this->subtitle,
             "summary" => $this->summary,
-            "studio"=> \App\Http\Api\StudioApi::getById($this->owner),
-            "editor"=> \App\Http\Api\UserApi::getById($this->editor_id),
+            "studio"=> StudioApi::getById($this->owner),
+            "editor"=> UserApi::getById($this->editor_id),
             "status" => $this->status,
             "lang" => $this->lang,
             "created_at" => $this->created_at,
             "updated_at" => $this->updated_at,
         ];
+        $user = AuthApi::current($request);
+        if($user){
+            $canEdit = ArticleController::userCanEdit($user['user_uid'],$this);
+            if($canEdit){
+                $data['role'] = 'editor';
+            }
+        }
+
+        //查询文集
+        $collectionCount = ArticleCollection::where('article_id',$this->uid)->count();
+        if($collectionCount>0){
+            $data['anthology_count'] = $collectionCount;
+            $collection = ArticleCollection::where('article_id',$this->uid)->first();
+            $data['anthology_first'] = Collection::find($collection->collect_id);
+        }
         if(isset($this->content) && !empty($this->content)){
             if($request->has('channel')){
                 $channel = $request->get('channel');
@@ -49,7 +69,6 @@ class ArticleResource extends JsonResource
                          * 查询用户在课程中的channel
                          */
                         $userId = UserApi::getIdByName($request->get('user'));
-                        Log::info("userId:{$userId}");
 
                         $userInCourse = CourseMember::where('course_id',$request->get('course'))
                                     ->where('user_id',$userId)
@@ -71,8 +90,6 @@ class ArticleResource extends JsonResource
                     $channel = Course::where('id',$request->get('course'))->value('channel_id');
                 }
             }
-            Log::info("channel:{$channel}");
-            Log::info("query_id:{$query_id}");
             if($request->has('mode')){
                 $mode = $request->get('mode');
             }else{

+ 34 - 0
app/Http/Resources/ChannelResource.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\StudioApi;
+
+class ChannelResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+            "uid" => $this->uid,
+            "name" => $this->name,
+            "summary" => $this->summary,
+            "type" => $this->type,
+            "studio" => StudioApi::getById($this->owner_uid),
+            "lang" => $this->lang,
+            "status" => $this->status,
+            "created_at" => $this->created_at,
+            "updated_at" => $this->updated_at,
+        ];
+        if(isset($this->role)){
+            $data["role"] = $this->role;
+        }
+        return $data;
+    }
+}

+ 39 - 0
app/Http/Resources/CollectionResource.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\StudioApi;
+use App\Models\ArticleCollection;
+
+
+class CollectionResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+            "uid" => $this->uid,
+            "title" => $this->title,
+            "subtitle" => $this->subtitle,
+            "summary" => $this->summary,
+            "studio" => StudioApi::getById($this->owner),
+            "childrenNumber" => ArticleCollection::where('collect_id',$this->uid)->count(),
+            "status" => $this->status,
+            "created_at" => $this->created_at,
+            "updated_at" => $this->updated_at,
+        ];
+        if(isset($this->article_list) && !empty($this->article_list) ){
+            $arrList = \json_decode($this->article_list);
+            if(is_array($arrList)){
+                $data["article_list"] = array_slice($arrList,0,4);
+            }
+        }
+        return $data;
+    }
+}

+ 2 - 1
app/Http/Resources/CourseMemberResource.php

@@ -15,7 +15,7 @@ class CourseMemberResource extends JsonResource
      */
     public function toArray($request)
     {
-        return [
+        $data = [
             "id"=>$this->id,
             "user_id"=> $this->user_id,
             "course_id"=> $this->course_id,
@@ -25,5 +25,6 @@ class CourseMemberResource extends JsonResource
             "created_at"=> $this->created_at,
             "updated_at"=> $this->updated_at,
         ];
+        return $data;
     }
 }

+ 1 - 0
app/Http/Resources/DiscussionResource.php

@@ -19,6 +19,7 @@ class DiscussionResource extends JsonResource
             "id"=>$this->id,
             "title"=> $this->title,
             "content"=> $this->content,
+            "content_type"=> $this->content_type,
             "parent"=> $this->parent,
             "children_count"=> $this->children_count,
             "editor"=> StudioApi::getById($this->editor_uid),

+ 55 - 0
app/Http/Resources/GroupResource.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Models\GroupMember;
+use App\Http\Api\AuthApi;
+use App\Http\Api\StudioApi;
+
+class GroupResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+		$data = [
+            'uid' => $this->uid,
+            'name' => $this->name,
+            'description' => $this->description,
+            'owner' => $this->owner,
+            "studio" => StudioApi::getById($this->owner),
+            'updated_at' => $this->updated_at,
+            'created_at' => $this->created_at
+        ];
+        $user = AuthApi::current($request);
+        if($user){
+            if($this->owner === $user['user_uid']){
+                $data['role'] = 'owner';
+            }else{
+                $power = GroupMember::where('user_id',$user['user_uid'])
+                                      ->where('group_id',$this->uid)
+                                      ->value('power');
+                switch ($power) {
+                    case 0:
+                        $data['role'] = 'owner';
+                        break;
+                    case 1:
+                        $data['role'] = 'manager';
+                        break;
+                    case 2:
+                        $data['role'] = 'member';
+                        break;
+                    default:
+                        $data['role'] = 'unknown';
+                        break;
+                }
+            }
+        }
+        return $data;
+    }
+}

+ 30 - 0
app/Http/Resources/NissayaEndingResource.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\UserApi;
+
+class NissayaEndingResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        return [
+            "id"=>$this->id,
+            "ending"=> $this->ending,
+            "lang"=> $this->lang,
+            "relation"=> $this->relation,
+            "case"=> $this->case,
+            "count"=> $this->count,
+            "editor"=> UserApi::getById($this->editor_id),
+            "created_at"=> $this->created_at,
+            "updated_at"=> $this->updated_at,
+        ];
+    }
+}

+ 38 - 0
app/Http/Resources/RelatedParagraphResource.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Models\BookTitle;
+use App\Models\PaliText;
+use App\Models\TagMap;
+use App\Models\Tag;
+use Illuminate\Support\Facades\Log;
+
+class RelatedParagraphResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $bookTitle = BookTitle::where('id',$this["book_id"])->first();
+        $data = [
+            "book"=>$this['book'],
+            "para"=> $this['para'],
+            "book_title_pali"=> $bookTitle->title,
+            'cs6_para'=> $this['cs6_para'],
+        ];
+        $paliTextUuid = PaliText::where('book',$bookTitle->book)->where('paragraph',$bookTitle->paragraph)->value('uid');
+        $tagIds = TagMap::where('anchor_id',$paliTextUuid)->select('tag_id')->get();
+        $data['tags'] = Tag::whereIn('id',$tagIds)->select('id','name','color')->get();
+
+        $data['path'] = json_decode(PaliText::where('book',$this['book'])
+                                        ->where('paragraph',$this['para'][0])
+                                        ->value('path'));
+        return $data;
+    }
+}

+ 28 - 0
app/Http/Resources/RelationResource.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\UserApi;
+
+class RelationResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        return [
+            "id"=>$this->id,
+            "name"=> $this->name,
+            "case"=> $this->case,
+            "to"=> json_decode($this->to),
+            "editor"=> UserApi::getById($this->editor_id),
+            "created_at"=> $this->created_at,
+            "updated_at"=> $this->updated_at,
+        ];
+    }
+}

+ 29 - 0
app/Http/Resources/SearchBookResource.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Models\BookTitle;
+use App\Models\PaliText;
+
+class SearchBookResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $book = BookTitle::find($this->pcd_book_id);
+        $toc = PaliText::where('book',$book->book)->where("paragraph",$book->paragraph)->value('toc');
+        return [
+            "book"=>$book->book,
+            "paragraph"=> $book->paragraph,
+            "paliTitle"=> $toc,
+            'pcdBookId'=>$this->pcd_book_id,
+            "count"=>$this->co,
+        ];
+    }
+}

+ 49 - 0
app/Http/Resources/SearchResource.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Models\PaliText;
+
+class SearchResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+            "book"=>$this->book,
+            "paragraph"=> $this->paragraph,
+        ];
+        if(isset($this->rank)){
+            $data["rank"] = $this->rank;
+        }
+        $paliText = PaliText::where('book',$this->book)
+                            ->where('paragraph',$this->paragraph)
+                            ->first();
+        if(isset($this->highlight)){
+            $data["highlight"] = $this->highlight;
+        }else if(isset($this->content)){
+            $data["content"] = $this->content;
+        }else{
+            $data["content"] = $paliText->html;
+        }
+
+        if($paliText){
+            $data['path'] = json_decode($paliText->path);
+            if($paliText->level<100){
+                $data["paliTitle"] = $paliText->toc;
+            }else{
+                $data["paliTitle"] = PaliText::where('book',$this->book)
+                                            ->where('paragraph',$paliText->parent)
+                                            ->value('toc');
+            }
+
+        }
+        return $data;
+    }
+}

+ 4 - 2
app/Http/Resources/SentPrResource.php

@@ -24,6 +24,8 @@ class SentPrResource extends JsonResource
         if($user && $user["user_uid"] === $this->editor_uid ){
             $role = 'owner';
         }
+        $channel = ChannelApi::getById($this->channel_uid);
+        $mode = $request->get("mode",'read');
         return [
             "id"=>$this->id,
             "book"=> $this->book_id,
@@ -31,9 +33,9 @@ class SentPrResource extends JsonResource
             "word_start"=> $this->word_start,
             "word_end"=> $this->word_end,
             "editor"=> StudioApi::getById($this->editor_uid),
-            "channel"=> ChannelApi::getById($this->channel_uid),
+            "channel"=> $channel,
             "content"=>$this->content,
-            "html"=> MdRender::render($this->content,$this->channel_uid),
+            "html"=> MdRender::render($this->content,$this->channel_uid,null,$mode,$channel['type']),
             "role"=>$role,
             "created_at"=> $this->created_at,
             "updated_at"=> $this->updated_at,

+ 38 - 7
app/Http/Resources/SentResource.php

@@ -4,6 +4,10 @@ namespace App\Http\Resources;
 
 use App\Http\Api\MdRender;
 use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\StudioApi;
+use App\Http\Api\UserApi;
+use App\Http\Api\ChannelApi;
+use App\Http\Api\SuggestionApi;
 
 class SentResource extends JsonResource
 {
@@ -15,19 +19,46 @@ class SentResource extends JsonResource
      */
     public function toArray($request)
     {
-        return [
+        $channel = ChannelApi::getById($this->channel_uid);
+        if($request->get('mode','read')==="read"){
+            $mode = 'read';
+        }else{
+            $mode = 'edit';
+        }
+        $data = [
+                "id" => $this->uid,
                 "content"=>$this->content,
-                "html"=> MdRender::render($this->content,$this->channel_uid),
+                "content_type"=>$this->content_type,
+                "html"=> "",
                 "book"=> $this->book_id,
                 "paragraph"=> $this->paragraph,
                 "word_start"=> $this->word_start,
                 "word_end"=> $this->word_end,
-                "editor"=> \App\Http\Api\StudioApi::getById($this->editor_uid),
-                "channel"=> [
-                    "name"=>"channel",
-	                "id"=> $this->channel_uid,
-                ],
+                "editor"=> UserApi::getById($this->editor_uid),
+                "channel"=> $channel,
+                "studio" => StudioApi::getById($channel["studio_id"]),
                 "updated_at"=> $this->updated_at,
             ];
+        if($request->get('html',true)){
+            $data['html'] = MdRender::render($this->content,
+                                             $this->channel_uid,
+                                             null,
+                                             $mode,
+                                             $channel['type'],
+                                             $this->content_type);
+        }
+        if($request->get('mode')==="edit" || $request->get('mode')==="wbw"){
+            $data['suggestionCount'] = SuggestionApi::getCountBySent($this->book_id,
+                                                                   $this->paragraph,
+                                                                   $this->word_start,
+                                                                   $this->word_end,
+                                                                   $this->channel_uid
+                                                                );
+        }
+        if(isset($this->acceptor_uid) && !empty($this->acceptor_uid)){
+            $data["acceptor"]=UserApi::getById($this->acceptor_uid);
+            $data["pr_edit_at"]=$this->pr_edit_at;
+        }
+        return $data;
     }
 }

+ 52 - 0
app/Http/Resources/SentSimResource.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Models\PaliSentence;
+use App\Http\Api\StudioApi;
+use App\Http\Api\UserApi;
+use App\Http\Api\ChannelApi;
+use App\Http\Controllers\CorpusController;
+
+class SentSimResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        //获取实际句子信息
+        $sent = PaliSentence::find($this->sent2);
+        $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
+        $sentOrg = [
+            "id"=>$sent->id,
+            "book"=> $sent->book,
+            "para"=> $sent->paragraph,
+            "wordStart"=> $sent->word_begin,
+            "wordEnd"=> $sent->word_end,
+            "editor"=> StudioApi::getById(config("app.admin.root_uuid")),
+            "channel"=> ChannelApi::getById($channelId),
+            "content"=>$sent->text,
+            "html"=> "<span>{$sent->html}</span>",
+            "role"=>"member",
+            "created_at"=> $sent->created_at,
+            "updated_at"=> $sent->updated_at,
+        ];
+        $resCount = CorpusController::sentResCount($sent->book,$sent->paragraph,$sent->word_begin,$sent->word_end);
+        return [
+            "id" => "{$sent->book}-{$sent->paragraph}-{$sent->word_begin}-{$sent->word_end}",
+            "origin" =>  [$sentOrg],
+            "translation" =>  [],
+            "layout" =>   "column",
+            "tranNum" =>  $resCount['tranNum'],
+            "nissayaNum" =>  $resCount['nissayaNum'],
+            "commNum" =>  $resCount['commNum'],
+            "originNum" =>  $resCount['originNum'],
+            "simNum" =>  $resCount['simNum'],
+        ];
+    }
+}

+ 17 - 2
app/Http/Resources/ShareResource.php

@@ -5,7 +5,10 @@ namespace App\Http\Resources;
 use Illuminate\Http\Resources\Json\JsonResource;
 use App\Models\Channel;
 use App\Models\Article;
+use App\Models\Collection;
 use App\Http\Api\StudioApi;
+use App\Http\Api\GroupApi;
+use App\Http\Api\UserApi;
 
 class ShareResource extends JsonResource
 {
@@ -28,7 +31,7 @@ class ShareResource extends JsonResource
                 # Channel 版本
                 $res = Channel::where('uid',$this->res_id)->first();
                 if($res){
-                    $res_name = $channel->name;
+                    $res_name = $res->name;
                     $owner = StudioApi::getById($res->owner_uid);
                 }
                 break;
@@ -55,15 +58,27 @@ class ShareResource extends JsonResource
                 # code...
                 break;
         }
-        return [
+        $data = [
             "id"=>$this->id,
             "res_id"=> $this->res_id,
             "res_type"=> $this->res_type,
+            "collaborator_type"=> $this->cooperator_type,
             "power"=> $this->power,
             'res_name'=>$res_name,
             'owner'=>$owner,
             "created_at"=> $this->created_at,
             "updated_at"=> $this->updated_at,
         ];
+        switch ($this->cooperator_type) {
+            case 0:
+                # user
+                $data['user'] = UserApi::getById($this->cooperator_id);
+                break;
+            case 1:
+                # code...
+                $data['group'] = GroupApi::getById($this->cooperator_id);
+                break;
+            }
+        return $data;
     }
 }

+ 41 - 0
app/Http/Resources/TermResource.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\ChannelApi;
+use App\Http\Api\StudioApi;
+use App\Http\Api\UserApi;
+
+class TermResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+            "id"=>$this->id,
+            "guid"=>$this->guid,
+            "word"=> $this->word,
+            "word_en"=> $this->word_en,
+            "meaning"=> $this->meaning,
+            "other_meaning"=> $this->other_meaning,
+            "tag"=> $this->tag,
+            "note"=> $this->note,
+            "language"=> $this->language,
+            "channal"=> $this->channal,
+            "studio" => StudioApi::getById($this->owner),
+            "editor"=> UserApi::getById($this->editor_id),
+            "created_at"=> $this->created_at,
+            "updated_at"=> $this->updated_at,
+        ];
+        if(!empty($this->channal)){
+            $data["channel"] = ChannelApi::getById($this->channal);
+        }
+        return $data;
+    }
+}

+ 19 - 0
app/Http/Resources/TermVocabularyResource.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class TermVocabularyResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        return parent::toArray($request);
+    }
+}

+ 58 - 0
app/Http/Resources/TocResource.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Models\ProgressChapter;
+
+class TocResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+            "book"=>$this->book,
+            "paragraph"=> $this->paragraph,
+            "pali_title"=> $this->toc,
+            "level"=>$this->level
+        ];
+
+        $title= ProgressChapter::where('book',$this->book)
+                                        ->where('para',$this->paragraph)
+                                        ->where('lang','zh')
+                                        ->whereNotNull('title')
+                                        ->value('title');
+        if(!empty($title)){
+            $data['title'] = $title;
+        }
+        if($request->has('channels')){
+            $channels = explode(',',$request->get('channels'));
+            $title= ProgressChapter::where('book',$this->book)
+                                ->where('para',$this->paragraph)
+                                ->where('channel_id',$channels[0])
+                                ->whereNotNull('title')
+                                ->value('title');
+            if(!empty($title)){
+                $data['title'] = $title;
+            }
+            //查询完成度
+            foreach ($channels as $key => $channel) {
+                $progress= ProgressChapter::where('book',$this->book)
+                                ->where('para',$this->paragraph)
+                                ->where('channel_id',$channel)
+                                ->value('progress');
+                if($progress){
+                    $data['progress'][] = $progress;
+                }else{
+                    $data['progress'][] = 0;
+                }
+            }
+        }
+        return $data;
+    }
+}

+ 42 - 0
app/Http/Resources/UserDictResource.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\UserApi;
+use App\Models\UserOperationDaily;
+
+class UserDictResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+		 'id'=>$this->id,
+         'word'=>$this->word,
+         'type'=>$this->type,
+         'grammar'=>$this->grammar,
+         'mean'=>$this->mean,
+         'parent'=>$this->parent,
+         'note'=>$this->note,
+         'factors'=>$this->factors,
+         'confidence'=>$this->confidence,
+         'updated_at'=>$this->updated_at,
+         'creator_id'=>$this->creator_id,
+        ];
+        if($request->get('view')==='community'){
+            $data['editor'] = UserApi::getById($this->creator_id);
+            //毫秒计算的经验值
+            $exp = UserOperationDaily::where('user_id',$this->creator_id)
+                                                ->where('date_int','<=',date_timestamp_get(date_create($this->updated_at))*1000)
+                                                ->sum('duration');
+            $data['exp'] = (int)($exp/1000);
+        }
+        return $data;
+    }
+}

+ 19 - 0
app/Http/Resources/ViewResource.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ViewResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        return parent::toArray($request);
+    }
+}

+ 3 - 6
app/Http/Resources/VocabularyResource.php

@@ -4,6 +4,7 @@ namespace App\Http\Resources;
 
 use Illuminate\Http\Resources\Json\JsonResource;
 use Illuminate\Support\Facades\Cache;
+use App\Http\Controllers\DictMeaningController;
 
 class VocabularyResource extends JsonResource
 {
@@ -15,15 +16,11 @@ class VocabularyResource extends JsonResource
      */
     public function toArray($request)
     {
-        $key = "dict_first_mean/";
-        $meaning = Cache::get($key."zh-Hans/{$this['word']}");
-        if(empty($meaning)){
-            $meaning = Cache::get($key."com/{$this['word']}");
-        }
+        $dictMeaning = new DictMeaningController();
         return [
             "word"=>$this['word'],
             "count"=> $this['count'],
-            "meaning"=> $meaning,
+            "meaning"=> $dictMeaning->get($this['word'],$request->get("lang","zh-Hans")),
         ];
     }
 }

+ 19 - 0
app/Http/Resources/WbwResource.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class WbwResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        return parent::toArray($request);
+    }
+}

+ 19 - 0
app/Http/Resources/WordIndexResource.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class WordIndexResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        return parent::toArray($request);
+    }
+}

部分文件因为文件数量过多而无法显示