visuddhinanda 1 개월 전
부모
커밋
f0217084af

+ 1 - 1
api-v12/app/Http/Controllers/CategoryController.php

@@ -155,7 +155,7 @@ class CategoryController extends Controller
     }
     }
     private function loadCategories()
     private function loadCategories()
     {
     {
-        $json = file_get_contents(public_path("date/category/default.json"));
+        $json = file_get_contents(public_path("data/category/default.json"));
         $tree = json_decode($json, true);
         $tree = json_decode($json, true);
         $flat = self::flattenWithIds($tree);
         $flat = self::flattenWithIds($tree);
         return $flat;
         return $flat;

+ 17 - 0
api-v12/app/Http/Controllers/DownloadController.php

@@ -0,0 +1,17 @@
+<?php
+// api-v12/app/Http/Controllers/DownloadController.php
+namespace App\Http\Controllers;
+
+use App\Services\PacketService;
+
+class DownloadController extends Controller
+{
+    //
+    public function index()
+    {
+        $packets = app(PacketService::class)->index();
+
+
+        return view('library.download', compact('packets'));
+    }
+}

+ 5 - 46
api-v12/app/Http/Controllers/OfflineIndexController.php

@@ -3,14 +3,12 @@
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Redis;
-use Illuminate\Support\Facades\Log;
-use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Facades\App;
+
+use App\Services\PacketService;
 
 
 class OfflineIndexController extends Controller
 class OfflineIndexController extends Controller
 {
 {
+
     /**
     /**
      * Display a listing of the resource.
      * Display a listing of the resource.
      *
      *
@@ -19,49 +17,10 @@ class OfflineIndexController extends Controller
     public function index(Request $request)
     public function index(Request $request)
     {
     {
         //
         //
-        $key = '/offline/index';
+        $index = app(PacketService::class)->index($request->get('file', null));
 
 
-        if (!Cache::has($key)) {
-            return [];
-        }
-        $fileInfo = Cache::get($key);
-        $output = [];
-        foreach ($fileInfo as $key => $file) {
-            if ($request->has('file')) {
-                if ($file['id'] !== $request->get('file')) {
-                    continue;
-                }
-            }
-            $zipFile = $file['filename'];
-            $bucket = config('mint.attachments.bucket_name.temporary');
-            $tmpFile =  $bucket . '/' . $zipFile;
-            $url = array();
-            foreach (config('mint.server.cdn_urls') as $key => $cdn) {
-                $url[] = [
-                    'link' => $cdn . '/' . $zipFile,
-                    'hostname' => 'cdn-' . $key,
-                ];
-            }
-            if (App::environment('local')) {
-                $s3Link = Storage::url($tmpFile);
-            } else {
-                try {
-                    $s3Link = Storage::temporaryUrl($tmpFile, now()->addDays(2));
-                } catch (\Exception $e) {
-                    Log::error('offline-index {Exception}', ['exception' => $e]);
-                    continue;
-                }
-            }
-            $url[] = [
-                'link' => $s3Link,
-                'hostname' => 'Amazon cloud storage(Hongkong)',
-            ];
-            $file['url'] = $url;
-            Log::debug('offline-index: file info=', ['data' => $file]);
-            $output[] = $file;
-        }
         return response()->json(
         return response()->json(
-            $output,
+            $index,
             200,
             200,
             [
             [
                 'Content-Type' => 'application/json;charset=UTF-8',
                 'Content-Type' => 'application/json;charset=UTF-8',

+ 52 - 4
api-v12/app/Services/PacketService.php

@@ -3,11 +3,13 @@
 namespace App\Services;
 namespace App\Services;
 
 
 use App\Models\Channel;
 use App\Models\Channel;
-use App\Models\Sentence;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Storage;
 use ZipArchive;
 use ZipArchive;
 use App\Http\Api\ChannelApi;
 use App\Http\Api\ChannelApi;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\App;
 
 
 /**
 /**
  * PacketService
  * PacketService
@@ -42,18 +44,18 @@ class PacketService
      */
      */
     private array $tempFiles = [];
     private array $tempFiles = [];
 
 
+
     /**
     /**
-     * 构造函数
+     *
      *
      *
      * @param string $paliChannelUid 巴利原文的channel_uid
      * @param string $paliChannelUid 巴利原文的channel_uid
      * @param array $translationChannelUids 译文版本的channel_uid数组
      * @param array $translationChannelUids 译文版本的channel_uid数组
      */
      */
-    public function __construct(array $translationChannelUids)
+    public function channels(array $translationChannelUids)
     {
     {
         $this->paliChannelUid = ChannelApi::getSysChannel('_System_Pali_VRI_');
         $this->paliChannelUid = ChannelApi::getSysChannel('_System_Pali_VRI_');
         $this->translationChannelUids = $translationChannelUids;
         $this->translationChannelUids = $translationChannelUids;
     }
     }
-
     /**
     /**
      * 执行导出并打包
      * 执行导出并打包
      *
      *
@@ -304,4 +306,50 @@ class PacketService
 
 
         rmdir($dir);
         rmdir($dir);
     }
     }
+
+    public function index(?string $id = null)
+    {
+        $key = '/offline/index';
+
+        if (!Cache::has($key)) {
+            return [];
+        }
+        $fileInfo = Cache::get($key);
+        $output = [];
+        foreach ($fileInfo as $key => $file) {
+            if ($id) {
+                if ($file['id'] !== $id) {
+                    continue;
+                }
+            }
+            $zipFile = $file['filename'];
+            $bucket = config('mint.attachments.bucket_name.temporary');
+            $tmpFile =  $bucket . '/' . $zipFile;
+            $url = array();
+            foreach (config('mint.server.cdn_urls') as $key => $cdn) {
+                $url[] = [
+                    'link' => $cdn . '/' . $zipFile,
+                    'hostname' => 'cdn-' . $key,
+                ];
+            }
+            if (App::environment('local')) {
+                $s3Link = Storage::url($tmpFile);
+            } else {
+                try {
+                    $s3Link = Storage::temporaryUrl($tmpFile, now()->addDays(2));
+                } catch (\Exception $e) {
+                    Log::error('offline-index {Exception}', ['exception' => $e]);
+                    continue;
+                }
+            }
+            $url[] = [
+                'link' => $s3Link,
+                'hostname' => 'Amazon cloud storage(Hongkong)',
+            ];
+            $file['url'] = $url;
+            Log::debug('offline-index: file info=', ['data' => $file]);
+            $output[] = $file;
+        }
+        return $output;
+    }
 }
 }

+ 233 - 0
api-v12/resources/views/library/download.blade.php

@@ -0,0 +1,233 @@
+@extends('library.layouts.app')
+
+@section('title', __('labels.home'))
+
+@section('content')
+<div class="page-body">
+    <div class="container-xl">
+        <div class="page-header d-print-none">
+            <div class="row align-items-center">
+                <div class="col">
+                    <h2 class="page-title">下载</h2>
+                    <div class="text-muted mt-1">APP 离线数据包</div>
+                </div>
+                <div class="col-auto ms-auto">
+                    <span class="badge bg-blue-lt fs-6">{{ count($packets) }} 个数据包</span>
+                </div>
+            </div>
+        </div>
+
+        <div class="card">
+            <div class="card-header">
+                <h3 class="card-title">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive me-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                        <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                        <path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" />
+                        <path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" />
+                        <path d="M10 12l2 2l2 -2" />
+                    </svg>
+                    离线数据包
+                </h3>
+            </div>
+
+            @php
+            function formatFilesize(int $size): string {
+            if ($size >= 1048576) return number_format($size / 1048576, 2) . ' MB';
+            if ($size >= 1024) return number_format($size / 1024, 1) . ' KB';
+            return $size . ' B';
+            }
+            @endphp
+
+            @if(empty($packets))
+            {{-- ── Empty state ── --}}
+            <div class="card-body">
+                <div class="empty">
+                    <div class="empty-icon">
+                        <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-inbox-off" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                            <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                            <path d="M8 4h11a2 2 0 0 1 2 2v9m-1.172 2.821a2 2 0 0 1 -.828 .179h-14a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 1.172 -1.821" />
+                            <path d="M4 12h4l3 3h2l3 -3h1" />
+                            <path d="M3 3l18 18" />
+                        </svg>
+                    </div>
+                    <p class="empty-title">暂无数据包</p>
+                    <p class="empty-subtitle text-muted">当前没有可用的离线数据包。</p>
+                </div>
+            </div>
+
+            @else
+
+            {{-- ════════════════════════════════════════════
+                     ① 桌面端 Table  ≥ md (768px)
+                     ════════════════════════════════════════════ --}}
+            <div class="d-none d-md-block table-responsive">
+                <table class="table table-vcenter card-table table-hover">
+                    <thead>
+                        <tr>
+                            <th>数据包名称</th>
+                            <th class="text-center" style="width:90px">文件大小</th>
+                            <th class="text-center" style="width:90px">最低版本</th>
+                            <th class="text-center" style="width:160px">创建时间</th>
+                            <th style="min-width:240px">下载链接</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        @foreach($packets as $packet)
+                        <tr>
+                            {{-- 名称 --}}
+                            <td>
+                                <div class="d-flex align-items-center">
+                                    <span class="avatar avatar-sm me-3 bg-blue-lt text-blue">
+                                        <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-zip" width="20" height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                                            <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                                            <path d="M6 20.735a2 2 0 0 1 -1 -1.735v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-1" />
+                                            <path d="M11 17a2 2 0 0 1 2 2v2a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-2a2 2 0 0 1 2 -2z" />
+                                            <path d="M11 5l-1 0" />
+                                            <path d="M13 7l-1 0" />
+                                            <path d="M11 9l-1 0" />
+                                            <path d="M13 11l-1 0" />
+                                            <path d="M11 13l-1 0" />
+                                            <path d="M13 15l-1 0" />
+                                        </svg>
+                                    </span>
+                                    <div>
+                                        <div class="fw-semibold text-body">{{ $packet['title'] ?? $packet['id'] }}</div>
+                                        <div class="text-muted small font-monospace">{{ $packet['filename'] ?? '' }}</div>
+                                    </div>
+                                </div>
+                            </td>
+
+                            {{-- 大小 --}}
+                            <td class="text-center">
+                                <span class="badge bg-azure-lt text-azure">
+                                    {{ formatFilesize((int)($packet['filesize'] ?? 0)) }}
+                                </span>
+                            </td>
+
+                            {{-- 最低版本 --}}
+                            <td class="text-center">
+                                @if(!empty($packet['min_app_ver']))
+                                <span class="badge bg-lime-lt text-lime">v{{ $packet['min_app_ver'] }}</span>
+                                @else
+                                <span class="text-muted">—</span>
+                                @endif
+                            </td>
+
+                            {{-- 创建时间 --}}
+                            <td class="text-center text-muted small">
+                                {{ $packet['create_at'] ?? '—' }}
+                            </td>
+
+                            {{-- 下载链接 --}}
+                            <td>
+                                @if(!empty($packet['url']) && is_array($packet['url']))
+                                <div class="d-flex flex-wrap gap-2">
+                                    @foreach($packet['url'] as $index => $urlItem)
+                                    <a href="{{ $urlItem['link'] }}"
+                                        class="btn btn-sm {{ $index === 0 ? 'btn-primary' : 'btn-outline-secondary' }}"
+                                        target="_blank" rel="noopener noreferrer"
+                                        title="{{ $urlItem['hostname'] ?? '' }}">
+                                        <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-download me-1" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                                            <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                                            <path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
+                                            <path d="M7 11l5 5l5 -5" />
+                                            <path d="M12 4l0 12" />
+                                        </svg>
+                                        {{ $urlItem['hostname'] ?? '下载' }}
+                                    </a>
+                                    @endforeach
+                                </div>
+                                @else
+                                <span class="text-muted">暂无链接</span>
+                                @endif
+                            </td>
+                        </tr>
+                        @endforeach
+                    </tbody>
+                </table>
+            </div>
+
+            {{-- ════════════════════════════════════════════
+                     ② 移动端 Card list  < md (768px)
+                     ════════════════════════════════════════════ --}}
+            <div class="d-md-none">
+                <div class="list-group list-group-flush">
+                    @foreach($packets as $packet)
+                    <div class="list-group-item p-3">
+
+                        {{-- 顶部:图标 + 标题 + 大小 badge --}}
+                        <div class="d-flex align-items-start gap-3">
+                            <span class="avatar avatar-md bg-blue-lt text-blue flex-shrink-0">
+                                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-zip" width="22" height="22" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                                    <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                                    <path d="M6 20.735a2 2 0 0 1 -1 -1.735v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-1" />
+                                    <path d="M11 17a2 2 0 0 1 2 2v2a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-2a2 2 0 0 1 2 -2z" />
+                                    <path d="M11 5l-1 0" />
+                                    <path d="M13 7l-1 0" />
+                                    <path d="M11 9l-1 0" />
+                                    <path d="M13 11l-1 0" />
+                                    <path d="M11 13l-1 0" />
+                                    <path d="M13 15l-1 0" />
+                                </svg>
+                            </span>
+                            <div class="flex-fill min-w-0 overflow-hidden">
+                                <div class="fw-semibold text-body text-truncate">{{ $packet['title'] ?? $packet['id'] }}</div>
+                                <div class="text-muted small font-monospace text-truncate">
+                                    {{ $packet['filename'] ?? '' }}
+                                </div>
+                            </div>
+                            <span class="badge bg-azure-lt text-azure flex-shrink-0">
+                                {{ formatFilesize((int)($packet['filesize'] ?? 0)) }}
+                            </span>
+                        </div>
+
+                        {{-- 中部:时间 + 最低版本 --}}
+                        <div class="d-flex flex-wrap align-items-center gap-2 mt-2 ps-1">
+                            <span class="text-muted small">
+                                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar me-1" width="14" height="14" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                                    <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                                    <path d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12z" />
+                                    <path d="M16 3v4" />
+                                    <path d="M8 3v4" />
+                                    <path d="M4 11h16" />
+                                    <path d="M11 15h1" />
+                                    <path d="M12 15v3" />
+                                </svg>
+                                {{ $packet['create_at'] ?? '—' }}
+                            </span>
+                            @if(!empty($packet['min_app_ver']))
+                            <span class="badge bg-lime-lt text-lime">
+                                最低版本 v{{ $packet['min_app_ver'] }}
+                            </span>
+                            @endif
+                        </div>
+
+                        {{-- 底部:下载按钮,全宽竖排 --}}
+                        @if(!empty($packet['url']) && is_array($packet['url']))
+                        <div class="d-flex flex-column gap-2 mt-3">
+                            @foreach($packet['url'] as $index => $urlItem)
+                            <a href="{{ $urlItem['link'] }}"
+                                class="btn btn-sm {{ $index === 0 ? 'btn-primary' : 'btn-outline-secondary' }} w-100"
+                                target="_blank" rel="noopener noreferrer">
+                                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-download me-1" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                                    <path stroke="none" d="M0 0h24v24H0z" fill="none" />
+                                    <path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
+                                    <path d="M7 11l5 5l5 -5" />
+                                    <path d="M12 4l0 12" />
+                                </svg>
+                                {{ $urlItem['hostname'] ?? '下载' }}
+                            </a>
+                            @endforeach
+                        </div>
+                        @endif
+
+                    </div>{{-- /.list-group-item --}}
+                    @endforeach
+                </div>
+            </div>
+
+            @endif
+        </div>{{-- /.card --}}
+    </div>
+</div>
+@endsection

+ 2 - 1
api-v12/routes/web.php

@@ -8,6 +8,7 @@ use App\Http\Controllers\AssetsController;
 use App\Http\Controllers\BlogController;
 use App\Http\Controllers\BlogController;
 use App\Http\Controllers\CategoryController;
 use App\Http\Controllers\CategoryController;
 use App\Http\Controllers\BookController;
 use App\Http\Controllers\BookController;
+use App\Http\Controllers\DownloadController;
 
 
 /*
 /*
 |--------------------------------------------------------------------------
 |--------------------------------------------------------------------------
@@ -64,7 +65,7 @@ Route::prefix('library')->group(function () {
     Route::get('/book/{id}', [BookController::class, 'show'])->name('library.book.show');
     Route::get('/book/{id}', [BookController::class, 'show'])->name('library.book.show');
     Route::get('/book/{id}/read', [BookController::class, 'read'])->name('library.book.read');
     Route::get('/book/{id}/read', [BookController::class, 'read'])->name('library.book.read');
     Route::get('/wiki', [BookController::class, 'read'])->name('library.wiki');
     Route::get('/wiki', [BookController::class, 'read'])->name('library.wiki');
-    Route::get('/download', [BookController::class, 'read'])->name('library.download');
+    Route::get('/download', [DownloadController::class, 'index'])->name('library.download');
 });
 });
 // 博客路由
 // 博客路由
 Route::prefix('blog')->group(function () {
 Route::prefix('blog')->group(function () {

+ 83 - 110
dashboard-v6/src/components/sentence/SentRead.tsx

@@ -1,11 +1,8 @@
-import { useEffect, useMemo, useState, useCallback } from "react";
-import { Button, Dropdown, type MenuProps, Typography } from "antd";
-import { LoadingOutlined, CloseOutlined } from "@ant-design/icons";
+import { useEffect, useState, useCallback } from "react";
+import { Dropdown, Flex, type MenuProps, Typography } from "antd";
+import { LoadingOutlined } from "@ant-design/icons";
 
 
-import { useAppSelector } from "../../hooks";
-import { settingInfo } from "../../reducers/setting";
-
-import { type IWidgetSentEditInner, SentEditInner } from "./SentEdit";
+import { type IWidgetSentEditInner } from "./SentEdit";
 
 
 import store from "../../store";
 import store from "../../store";
 import { push } from "../../reducers/sentence";
 import { push } from "../../reducers/sentence";
@@ -13,13 +10,15 @@ import "./style.css";
 
 
 import type { ISentence } from "../../api/sentence";
 import type { ISentence } from "../../api/sentence";
 import { get } from "../../request";
 import { get } from "../../request";
-import { GetUserSetting } from "../setting/default";
 import { openDiscussion } from "../discussion/utils";
 import { openDiscussion } from "../discussion/utils";
 import { prOpen } from "./utils";
 import { prOpen } from "./utils";
-import MdView from "../general/MdView";
 import InteractiveButton from "./InteractiveButton";
 import InteractiveButton from "./InteractiveButton";
 import type { IEditableSentence } from "../../api/sentence";
 import type { IEditableSentence } from "../../api/sentence";
 import MdOrigin from "./components/MdOrigin";
 import MdOrigin from "./components/MdOrigin";
+import EditPad from "./components/EditPad";
+import MdTranslation from "./components/MdTranslation";
+import CommentaryPad from "../tipitaka/components/CommentaryPad";
+import { useSetting } from "../../hooks/useSetting";
 
 
 const { Text } = Typography;
 const { Text } = Typography;
 
 
@@ -38,6 +37,7 @@ export interface IWidgetSentReadFrame {
   wordEnd?: number;
   wordEnd?: number;
   origin?: ISentence[];
   origin?: ISentence[];
   translation?: ISentence[];
   translation?: ISentence[];
+  commentaries?: ISentence[];
   layout?: "row" | "column";
   layout?: "row" | "column";
   error?: string;
   error?: string;
 }
 }
@@ -45,38 +45,22 @@ export interface IWidgetSentReadFrame {
 const SentReadFrame = ({
 const SentReadFrame = ({
   origin,
   origin,
   translation,
   translation,
+  commentaries,
   book,
   book,
   para,
   para,
   wordStart,
   wordStart,
   wordEnd,
   wordEnd,
   error,
   error,
 }: IWidgetSentReadFrame) => {
 }: IWidgetSentReadFrame) => {
-  const settings = useAppSelector(settingInfo);
+  const layoutDirection = useSetting("setting.layout.direction");
+  const layoutCommentary = useSetting("setting.layout.commentary");
+  const displayOriginal = useSetting("setting.display.original");
 
 
   const [loadingId, setLoadingId] = useState<string | null>(null);
   const [loadingId, setLoadingId] = useState<string | null>(null);
   const [active, setActive] = useState(false);
   const [active, setActive] = useState(false);
   const [sentData, setSentData] = useState<IWidgetSentEditInner>();
   const [sentData, setSentData] = useState<IWidgetSentEditInner>();
   const [showEdit, setShowEdit] = useState(false);
   const [showEdit, setShowEdit] = useState(false);
 
 
-  /** 派生数据:是否显示原文 */
-  const displayOriginal = useMemo(() => {
-    return GetUserSetting("setting.display.original", settings);
-  }, [settings]);
-
-  /** 派生数据:布局方向 */
-  const layoutDirection = useMemo<React.CSSProperties["flexDirection"]>(() => {
-    const v = GetUserSetting("setting.layout.direction", settings);
-    if (
-      v === "row" ||
-      v === "column" ||
-      v === "row-reverse" ||
-      v === "column-reverse"
-    ) {
-      return v;
-    }
-    return "row";
-  }, [settings]);
-
   /** push 到 store(副作用) */
   /** push 到 store(副作用) */
   useEffect(() => {
   useEffect(() => {
     store.dispatch(
     store.dispatch(
@@ -121,101 +105,90 @@ const SentReadFrame = ({
   }, []);
   }, []);
 
 
   return (
   return (
-    <span
-      className="sent_read_shell"
-      style={{ flexDirection: layoutDirection }}
-    >
-      <Text type="danger" mark>
-        {error}
-      </Text>
-
+    <div className="sent_read_shell">
+      {error && (
+        <Text type="danger" mark>
+          {error}
+        </Text>
+      )}
       {/* anchor */}
       {/* anchor */}
-      <span
+      <div
         dangerouslySetInnerHTML={{
         dangerouslySetInnerHTML={{
           __html: `<span class="pcd_sent" id="sent_${book}-${para}-${wordStart}-${wordEnd}"></span>`,
           __html: `<span class="pcd_sent" id="sent_${book}-${para}-${wordStart}-${wordEnd}"></span>`,
         }}
         }}
       />
       />
+      <Flex vertical={layoutCommentary === "row"}>
+        <Flex
+          gap="middle"
+          vertical={layoutDirection === "row"}
+          style={{ flex: 5 }}
+        >
+          {/* 原文 */}
+          <span
+            style={{
+              flex: 5,
+              color: "#9f3a01",
+              display:
+                displayOriginal === false && translation?.length
+                  ? "none"
+                  : "block",
+            }}
+          >
+            {origin?.map((item, id) => (
+              <MdOrigin text={item.html} key={id} />
+            ))}
+          </span>
 
 
-      {/* 原文 */}
-      <span
-        style={{
-          flex: 5,
-          color: "#9f3a01",
-          display:
-            displayOriginal === false && translation?.length ? "none" : "block",
-        }}
-      >
-        {origin?.map((item, id) => (
-          <MdOrigin text={item.html} key={id} />
-        ))}
-      </span>
-
-      {/* 译文 */}
-      <span className="sent_read" style={{ flex: 5 }}>
-        {translation?.map((item, id) => (
-          <span key={id}>
-            {loadingId === item.id && <LoadingOutlined />}
-
-            <Dropdown
-              trigger={["contextMenu"]}
-              menu={{
-                items,
-                onClick: (e) => handleMenuClick(e.key, item),
-              }}
-            >
-              <Text
-                className="sent_read_translation"
-                style={{ display: showEdit ? "none" : "inline" }}
-              >
-                <MdView
-                  html={item.html}
-                  style={{ backgroundColor: active ? "beige" : undefined }}
-                />
-              </Text>
-            </Dropdown>
-
-            {/* 编辑面板 */}
-            {showEdit && (
-              <div>
-                <div style={{ textAlign: "right" }}>
-                  <Button
-                    size="small"
-                    icon={<CloseOutlined />}
-                    onClick={() => setShowEdit(false)}
-                  >
-                    返回审阅模式
-                  </Button>
-                </div>
-
-                {sentData ? (
-                  <SentEditInner
-                    mode="edit"
-                    {...sentData}
+          {/* 译文 */}
+          <span className="sent_read" style={{ flex: 5 }}>
+            {translation?.map((item, id) => (
+              <span key={id} style={{ border: active ? "1px" : "unset" }}>
+                {loadingId === item.id && <LoadingOutlined />}
+
+                <Dropdown
+                  trigger={["contextMenu"]}
+                  menu={{
+                    items,
+                    onClick: (e) => handleMenuClick(e.key, item),
+                  }}
+                >
+                  {showEdit && <MdTranslation text={item.html} />}
+                </Dropdown>
+
+                {/* 编辑面板 */}
+                {showEdit && (
+                  <EditPad
+                    data={sentData}
                     onTranslationChange={(data: ISentence) => {
                     onTranslationChange={(data: ISentence) => {
                       if (!translation) return;
                       if (!translation) return;
                       const copy = [...translation];
                       const copy = [...translation];
                       copy[id] = data;
                       copy[id] = data;
                     }}
                     }}
+                    onClose={() => setShowEdit(false)}
                   />
                   />
-                ) : (
-                  "无数据"
                 )}
                 )}
-              </div>
-            )}
-
-            <InteractiveButton
-              data={item}
-              compact
-              float
-              hideCount
-              hideInZero
-              onMouseEnter={() => setActive(true)}
-              onMouseLeave={() => setActive(false)}
-            />
+
+                <InteractiveButton
+                  data={item}
+                  compact
+                  float
+                  hideCount
+                  hideInZero
+                  onMouseEnter={() => setActive(true)}
+                  onMouseLeave={() => setActive(false)}
+                />
+              </span>
+            ))}
           </span>
           </span>
-        ))}
-      </span>
-    </span>
+        </Flex>
+        {/**注疏区 */}
+        <CommentaryPad>
+          {commentaries?.map((item, id) => {
+            return <MdTranslation text={item.html} key={id} />;
+          })}
+        </CommentaryPad>
+      </Flex>
+    </div>
   );
   );
 };
 };
 
 

+ 34 - 0
dashboard-v6/src/components/sentence/components/EditPad.tsx

@@ -0,0 +1,34 @@
+import { Button } from "antd";
+import { CloseOutlined } from "@ant-design/icons";
+
+import { SentEditInner, type IWidgetSentEditInner } from "../SentEdit";
+import type { ISentence } from "../../../api/sentence";
+
+interface IWidget {
+  data?: IWidgetSentEditInner;
+  onTranslationChange?: (data: ISentence) => void;
+  onClose?: () => void;
+}
+const EditPad = ({ data, onTranslationChange, onClose }: IWidget) => {
+  return (
+    <div>
+      <div style={{ textAlign: "right" }}>
+        <Button size="small" icon={<CloseOutlined />} onClick={onClose}>
+          返回审阅模式
+        </Button>
+      </div>
+
+      {data ? (
+        <SentEditInner
+          mode="edit"
+          {...data}
+          onTranslationChange={onTranslationChange}
+        />
+      ) : (
+        "无数据"
+      )}
+    </div>
+  );
+};
+
+export default EditPad;