吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1430|回复: 30
上一主题 下一主题
收起左侧

[其他原创] html手机拍照无确认连续上传系统

  [复制链接]
跳转到指定楼层
楼主
xyxy1111 发表于 2026-7-14 21:58 回帖奖励
本帖最后由 xyxy1111 于 2026-7-14 22:00 编辑

大神BD7IWD在精品软件区发了一个软件:拍照自动回传工具 v2.0
链接:https://www.52pojie.cn/forum.php?mod=viewthread&tid=2110717

让我想起来2020年给疫情物资清单库存时制作的一个网页版工具,稍微改了一下,给有需要的网友。

系统要求:有自己的域名(公网IP)和存储空间(服务器开启IIS),必须开启SSL(也就是要有https),否则无法启用手机的摄像头。

系统其实就2个页面:index.asp负责拍照,view.asp负责对上传的照片进行管理(请修改第7行的pwd="888888"为自己的密码)。
index.asp会自动生成一个二维码,就是你的网站的地址。可以把这个二维码保存在自己的手机里用浏览器或者微信进行识别。也可以将网站地址直接存在手机浏览器的收藏夹里(
https://域名/index.asp?mode=camera
)。
拍照时,是不需要确认上传的,只要按下拍照键就自动拍照上传了,可以连拍连传(当然,上传速度取决于你的网络情况,尤其是服务器的带宽。一般来说,1-2秒一张是没问题的)。

index.asp代码如下:
[HTML] 纯文本查看 复制代码
<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>手机拍照上传系统</title>
<script src="js/qrcode.min.js"></script>
<style>
* {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
}

a { text-decoration:none;cursor:pointer;}

body {
        background: #f1f5f9;
        margin: 0;
        padding: 10px;
        display: flex;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
}

.container {
        max-width: 500px;
        width: 100%;
        background: white;
        border-radius: 2rem;
        box-shadow: 0 20px 35px -12px rgba(0,0,0,0.2);
        padding: 1.5rem 1.8rem 2rem;
}

.title {
        font-size: 24px;
        font-weight: 700;
        margin: 0 0 1.5rem 0;
        color: #0f172a;
        text-align: center;
        display:flex;
        align-items:center;
        justify-content:center;
        gap:15px;
}

.qrcard {
        text-align: center;
        margin-bottom: 1.5rem;
}

.qrcode-area {
        display: flex;
        justify-content: center;
        margin: 15px;
}

#qrcode {
        width: 220px;
        height: 220px;
        display: inline-block;
        background: white;
        margin: 10px;
}

.qrTip {
        font-size: 0.75rem;line-height:1.75;
        color: #555;
        background: #f8fafc;
        display: inline-block;
        padding: 6px 20px;
        border-radius: 60px;border:1px solid #e2e8f0;
        word-break: break-all;
}

.footer-note {
        text-align: center;
        font-size: 12px;
        color: #5b6e8c;
        margin-top: 28px;
        border-top: 1px solid #e2e8f0;
        padding-top: 16px;
        display:flex;
        align-items:center;
        justify-content:space-between;
}
.view-link {
        border:1px solid #5b6e8c;
        color:#5b6e8c;
        padding:4px 12px;
        border-radius:20px;
        font-size:14px;
}

/* 相机全屏样式 */
.camera-fullscreen {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: #000;
        display: flex;
        flex-direction: column;
        z-index: 1000;
}
.camera-header {
        background: rgba(0,0,0,0.7);
        color: white;
        padding: 12px 16px;
        text-align: center;
        font-weight: 100;
        backdrop-filter: blur(12px);
        font-size: 1.2rem;
}
.camera-video-container {
        flex: 1;
        position: relative;
        background: #111;
        overflow: hidden;
}
.camera-video {
        width: 100%;
        height: 100%;
        object-fit: cover;
}
.camera-controls {
        padding: 20px;
        background: #0f172a;
        display: grid;
        grid-template-columns: 1fr 1fr; /* 分为两列 */
        justify-content: center;/* 整个网格容器在父元素中水平居中 */
        gap: 24px;  /* 网格行列之间的间距 */
        align-items: center;/* 网格子项垂直居中对齐 */
        justify-items: center;  /* 网格子项水平居中对齐 */
        text-align: center; /* 文本内容居中 */
}
.op-btn {
        background: none;
        border: none;
        width: 40px;
        height: 40px;
        border: none;
        font-size: 1.2rem;
        font-weight: bold;
        color: white;
        cursor: pointer;
}

.toast-msg {
        position: fixed;
        bottom: 100px;
        left: 20px;
        right: 20px;
        background: rgba(0,0,0,0.8);
        color: #ccffcc;
        text-align: center;
        padding: 8px;
        border-radius: 50px;
        font-size: 0.8rem;
        pointer-events: none;
        opacity: 0;
        transition: 0.2s;
        z-index: 2000;
}

</style>
</head>

<%
' ==================== 经典ASP无组件上传处理 ====================
' 功能: 二维码展示 + 手机拍照上传(二进制解析)
' ========================================================

Response.Expires = 0
Response.CodePage = 65001
Response.Charset = "UTF-8"

' 定义上传文件夹
upload_folder="upload/"

method = Request.ServerVariables("REQUEST_METHOD")

' 判断是否有文件上传 (POST 且 TotalBytes > 0)
If method = "POST" And Request.TotalBytes > 0 Then
Call ProcessPhotoUpload()
Response.End()
End If

Dim mode
mode = Request.QueryString("mode")
If mode = "camera" Then  ' 相机模式
%>
<body>
<div class="camera-fullscreen" id="cameraApp">
        <div class="camera-header">拍照上传</div>
        <div class="camera-video-container">
                <video id="video" class="camera-video" autoplay playsinline></video>
                <canvas id="canvas" style="display:none;"></canvas>
        </div>
<div class="camera-controls">
        <button id="captureBtn" class="op-btn"><svg  xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" viewBox="0 0 24 24" ><path d="M12 8c-2.17 0-4 1.83-4 4s1.83 4 4 4 4-1.83 4-4-1.83-4-4-4m0 6c-1.07 0-2-.93-2-2s.93-2 2-2 2 .93 2 2-.93 2-2 2"></path><path d="M20 5h-2.59L14.7 2.29a1 1 0 0 0-.71-.29h-4c-.27 0-.52.11-.71.29L6.57 5H3.98c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2Zm0 13H4V7h3c.27 0 .52-.11.71-.29L10.42 4h3.17l2.71 2.71c.19.19.44.29.71.29h3v11Z"></path></svg></button>
<button id="backBtn" class="op-btn"><svg  xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" viewBox="0 0 24 24" ><path d="M11 2h2v10h-2z"></path><path d="M15 4.53v2.16c2.36 1.13 4 3.53 4 6.32 0 3.86-3.14 7-7 7s-7-3.14-7-7c0-2.79 1.64-5.19 4-6.32V4.53C5.51 5.77 3 9.1 3 13c0 4.96 4.04 9 9 9s9-4.04 9-9c0-3.91-2.51-7.24-6-8.47"></path></svg></button>
</div>
<div id="toastMsg" class="toast-msg"></div>
</div>

<script>
var uploadUrl = '<%=GetBaseUrl()%>';
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var captureBtn = document.getElementById('captureBtn');
var backBtn = document.getElementById('backBtn');
var toastDiv = document.getElementById('toastMsg');

function showToast(msg, isError) {
toastDiv.style.opacity = '1';
toastDiv.textContent = msg;
toastDiv.style.background = isError ? 'rgba(220,38,38,0.9)' : 'rgba(0,0,0,0.8)';
setTimeout(function() { toastDiv.style.opacity = '0'; }, 1800);
}

function initCamera() {
// 请求高分辨率,从高到低降级尝试
var resolutions = [
{ width: { ideal: 3840 }, height: { ideal: 2160 } },  // 4K
{ width: { ideal: 2560 }, height: { ideal: 1440 } },  // 2K
{ width: { ideal: 1920 }, height: { ideal: 1080 } },  // 1080p
{ width: { ideal: 1280 }, height: { ideal: 720 } },   // 720p
{ width: { ideal: 640 }, height: { ideal: 480 } } // 480p 保底
];

function tryResolution(index) {
if (index >= resolutions.length) {
// 所有分辨率都失败,使用无约束
return navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
}

var constraints = {
video: {
facingMode: 'environment',
width: resolutions[index].width,
height: resolutions[index].height
}
};

return navigator.mediaDevices.getUserMedia(constraints)
.catch(function(err) {
console.warn('分辨率', resolutions[index].width.ideal, 'x', resolutions[index].height.ideal, '失败:', err);
return tryResolution(index + 1);
});
}

tryResolution(0)
.then(function(stream) {
video.srcObject = stream;
return video.play();
})
.then(function() {
showToast('&#128247; 相机就绪,当前分辨率: ' + video.videoWidth + 'x' + video.videoHeight, false);
})
.catch(function(err) {
showToast('无法获取摄像头,请使用HTTPS或授权', true);
alert('摄像头不可用:' + err.message);
});
}

function takePhotoAndUpload() {
if (!video.videoWidth || !video.videoHeight) {
showToast('相机未就绪', true);
return;
}
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
canvas.toBlob(function(blob) {
if (!blob) {
showToast('拍照失败', true);
return;
}
var now = new Date();
var year = now.getFullYear();
var month = String(now.getMonth() + 1).padStart(2, '0');
var day = String(now.getDate()).padStart(2, '0');
var hh = String(now.getHours()).padStart(2, '0');
var mm = String(now.getMinutes()).padStart(2, '0');
var ss = String(now.getSeconds()).padStart(2, '0');
var ms = String(now.getMilliseconds()).padStart(3, '0');
var filename = year + month + day + '_' + hh + mm + ss + '_' + ms + '.jpg';
var formData = new FormData();
formData.append('photo', blob, filename);
fetch(uploadUrl, { method: 'POST', body: formData })
.then(function(resp) { return resp.text(); })
.then(function(text) {
try {
var json = JSON.parse(text);
if (json.success) {
showToast('&#9989; 已保存: ' + json.filename, false);
} else {
showToast('失败: ' + json.message, true);
}
} catch(e) {
if (text.indexOf('success') > 0) {
showToast('&#9989; 已保存', false);
} else {
showToast('服务器响应异常', true);
}
}
})
.catch(function(err) {
showToast('网络错误: ' + err.message, true);
});
}, 'image/jpeg', 0.85);
}

captureBtn.addEventListener('click', takePhotoAndUpload);
backBtn.addEventListener('click', function() {
if (video.srcObject) {
video.srcObject.getTracks().forEach(function(t) { t.stop(); });
}
window.close();
});
initCamera();
window.addEventListener('beforeunload', function() {
if (video.srcObject) {
video.srcObject.getTracks().forEach(function(t) { t.stop(); });
}
});
</script>
</body>
</html>

<%
Else ' 二维码模式
%>

<body>
<div class="container">
        <div class="title">
                <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="currentColor" viewBox="0 0 24 24"><path d="M12 8c-2.17 0-4 1.83-4 4s1.83 4 4 4 4-1.83 4-4-1.83-4-4-4m0 6c-1.07 0-2-.93-2-2s.93-2 2-2 2 .93 2 2-.93 2-2 2"></path><path d="M20 5h-2.59L14.7 2.29a1 1 0 0 0-.71-.29h-4c-.27 0-.52.11-.71.29L6.57 5H3.98c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2Zm0 13H4V7h3c.27 0 .52-.11.71-.29L10.42 4h3.17l2.71 2.71c.19.19.44.29.71.29h3v11Z"></path></svg>
                <span>随身拍照助手</span>
        </div>
        <div class="qrcard">
                <div class="qrcode-area"><div id="qrcode"></div></div>
                <div class="qrTip" id="qrTip">二维码加载中...</div>
        </div>
        <div class="footer-note"><div>手机扫码进入相机 > 拍照 > 自动上传至服务器</div><a href="view.asp"><div class="view-link">管理图片</div></a></div>
</div>
<script>
var cameraUrl = '<%=GetBaseUrl()%>?mode=camera';
var qrDiv = document.getElementById('qrcode');
new QRCode(qrDiv, {
text: cameraUrl,
width: 220,
height: 220,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
});
document.getElementById('qrTip').innerHTML = '扫码访问<br>' + cameraUrl;
</script>
</body>
</html>
<%
End If

' ==================== 获取网址函数 ====================
Function GetBaseUrl()
Dim url, protocol
If Request.ServerVariables("HTTPS") = "on" Then
protocol = "https://"
Else
protocol = "http://"
End If
GetBaseUrl = protocol & Request.ServerVariables("HTTP_HOST") & Request.ServerVariables("SCRIPT_NAME")
End Function

' ==================== 照片上传处理 ====================
Sub ProcessPhotoUpload()
On Error Resume Next

totalBytes = Request.TotalBytes
binData = Request.BinaryRead(totalBytes)

Set a = CreateObject("adodb.stream")
a.Type = 1
a.Open
a.Write binData
a.Position = 0
b = a.Read
c = ChrB(13) & ChrB(10)
d = InStrB(b, c)
e = InStrB(d + 1, b, c)

Set f = CreateObject("adodb.stream")
f.Type = 1
f.Open
a.Position = d + 1
a.CopyTo f, e - d - 3
f.Position = 0
f.Type = 2
f.CharSet = "utf-8"
g = f.ReadText
f.Close

' 解析文件名
h = Mid(g, InStrRev(g, "\") + 1, e)
h = Mid(h, InStrRev(h, "=") + 2)
h = Trim(h)

' 解析文件数据位置
i = InStrB(b, c & c) + 4
j = InStrB(i + 1, b, LeftB(b, d - 1)) - i - 2
k = InStrB(i + j + 1, b, c & c) + 4
l = InStrB(k + 1, b, LeftB(b, d - 1)) - k - 2
m = InStrB(k + l + 1, b, c & c) + 4
n = InStrB(m + 1, b, LeftB(b, d - 1)) - m - 2

' 获取文件扩展名
hh = LCase(Mid(h, InStrRev(h, ".") + 1))
If hh = "" Then hh = "jpg"

' 生成时间格式的文件名
currentTime = Now()
formattedTime = Year(currentTime) & Right("0" & Month(currentTime), 2) & Right("0" & Day(currentTime), 2) & "_" & Right("0" & Hour(currentTime), 2) & Right("0" & Minute(currentTime), 2) & Right("0" & Second(currentTime), 2)

' 加上毫秒避免重复(使用Timer)
Dim msPart
msPart = Int((Timer * 1000) Mod 1000)
msPart = Right("00" & msPart, 3)
formattedTime = formattedTime & "_" & msPart

fileName = formattedTime & "." & hh

' 保存文件
f.Type = 1
f.Open
a.Position = i - 1
a.CopyTo f, j

' 确保upload目录存在
savePath = Server.MapPath(upload_folder)
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FolderExists(savePath) Then
fso.CreateFolder(savePath)
End If
Set fso = Nothing

fullPath = savePath & "\" & fileName
f.SaveToFile fullPath, 2
f.Close

' 清理
Set f = Nothing
a.Close
Set a = Nothing

' 返回成功JSON
If Err.Number = 0 Then
Response.ContentType = "application/json"
Response.Write "{""success"":true,""filename"":""" & fileName & """}"
Else
Response.ContentType = "application/json"
Response.Write "{""success"":false,""message"":""保存文件失败: " & Err.Description & """}"
End If
End Sub
%>
</body>
</html>


view.asp代码如下(包含了无组件上传代码):
[HTML] 纯文本查看 复制代码
<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%>
<%
' ==================== 图片管理页面 ====================
Response.Expires = 0
Response.CodePage = 65001
Response.Charset = "UTF-8"

pwd="888888" '引号内为管理密码

upload_folder = "upload/"
mode = Request.QueryString("mode")
method = Request.ServerVariables("REQUEST_METHOD")


if request("act")="login" then
        if request.form("password")=pwd then
        session("login")=1
        response.redirect "view.asp"
        end if
end if

if request("act")="logout" then
        session("login")=0
        response.redirect "view.asp"
end if

' 获取图片列表接口
If mode = "listImages" Then
    Response.ContentType = "application/json"
    Dim folderPath, fso, folder, file, arr(), i, j, temp, jsonFiles
    folderPath = Server.MapPath(upload_folder)
    Set fso = CreateObject("Scripting.FileSystemObject")
    If Not fso.FolderExists(folderPath) Then
        Response.Write "{""success"":true,""files"":[]}"
        Set fso = Nothing
        Response.End()
    End If
    Set folder = fso.GetFolder(folderPath)
    ReDim arr(-1)
    For Each file In folder.Files
        Dim ext
        ext = LCase(fso.GetExtensionName(file.Name))
        If ext = "jpg" Or ext = "jpeg" Or ext = "png" Or ext = "gif" Or ext = "webp" Then
            ReDim Preserve arr(UBound(arr) + 1)
            arr(UBound(arr)) = file.Name
        End If
    Next
    
    ' 按文件名倒序排列(最新的在前)
    For i = 0 To UBound(arr) - 1
        For j = i + 1 To UBound(arr)
            If arr(i) < arr(j) Then
                temp = arr(i)
                arr(i) = arr(j)
                arr(j) = temp
            End If
        Next
    Next
    
    jsonFiles = "["
    For i = 0 To UBound(arr)
        If i > 0 Then jsonFiles = jsonFiles & ","
        jsonFiles = jsonFiles & """" & Replace(arr(i), "\", "/") & """"
    Next
    jsonFiles = jsonFiles & "]"
    Response.Write "{""success"":true,""files"": " & jsonFiles & "}"
    Set folder = Nothing
    Set fso = Nothing
    Response.End()
End If

' 批量删除接口
If mode = "deleteMultiple" Then
    Response.ContentType = "application/json"
    Dim fso3, folderPath3, filesParam, fileArray, fileItem, count, ff, fullPath3
    count = 0
    filesParam = Request.Form("files")
    
    If filesParam = "" Then
        Response.Write "{""success"":false,""message"":""未指定文件列表""}"
        Response.End()
    End If
    
    ' 简单的逗号分隔解析
    fileArray = Split(filesParam, ",")
    
    Set fso3 = CreateObject("Scripting.FileSystemObject")
    folderPath3 = Server.MapPath(upload_folder)
    
    For ff = 0 To UBound(fileArray)
        fileItem = Trim(fileArray(ff))
        ' 安全过滤,防止路径遍历
        fileItem = Replace(fileItem, "..", "")
        fileItem = Replace(fileItem, "/", "")
        fileItem = Replace(fileItem, "\", "")
        If fileItem <> "" Then
            fullPath3 = folderPath3 & "\" & fileItem
            If fso3.FileExists(fullPath3) Then
                On Error Resume Next
                fso3.DeleteFile fullPath3
                If Err.Number = 0 Then
                    count = count + 1
                End If
                On Error GoTo 0
            End If
        End If
    Next
    
    Response.Write "{""success"":true,""deletedCount"":" & count & "}"
    Set fso3 = Nothing
    Response.End()
End If

' 单张删除接口
If mode = "delete" Then
    Response.ContentType = "application/json"
    Dim fileName, fullPath, fso2, folderPath2
    fileName = Request.QueryString("file")
    
    If fileName = "" Then
        Response.Write "{""success"":false,""message"":""未指定文件名""}"
        Response.End()
    End If
    
    ' 安全过滤
    fileName = Replace(fileName, "..", "")
    fileName = Replace(fileName, "/", "")
    fileName = Replace(fileName, "\", "")
    
    Set fso2 = CreateObject("Scripting.FileSystemObject")
    folderPath2 = Server.MapPath(upload_folder)
    fullPath = folderPath2 & "\" & fileName
    
    If fso2.FileExists(fullPath) Then
        On Error Resume Next
        fso2.DeleteFile fullPath
        If Err.Number = 0 Then
            Response.Write "{""success"":true,""message"":""删除成功""}"
        Else
            Response.Write "{""success"":false,""message"":""删除失败: " & Err.Description & """}"
        End If
        On Error GoTo 0
    Else
        Response.Write "{""success"":false,""message"":""文件不存在""}"
    End If
    
    Set fso2 = Nothing
    Response.End()
End If
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>图片管理 - 手机拍照上传系统</title>
<style>
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    background: #f1f5f9;
    padding: 20px;
    min-height: 100vh;
}

.login-form {
    background: white;
    border-radius: 20px;
    box-shadow: 0 20px 35px -12px rgba(0,0,0,0.1);
    width:300px;
    position:absolute;
    left:50%;top:50%;
    transform:translate(-50%, -50%);
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:15px;
    text-align:center;
    overflow:hidden;
}
.login-form .header {
    width:100%;
    background: #ff6600;
    color:#fff;
    font-size:26px;
    font-weight:700;
    text-align:center;
    padding: 20px;
    display:flex;
    align-items:center;
    justify-content:center;
    letter-spacing:5px;
}
.login-form input {
    width:80%;
    padding:14px;
    font-size:16px;
    text-align:center;
    border: 1px solid #555;
    border-radius:8px;
}
.login-form button {
    width:80%;
    text-align:center;
    border: none;
    background:none;
    margin:10px;
    cursor:pointer;
}

.container {
    max-width: 1000px;
    margin: 0 auto;
    background: white;
    border-radius: 20px;
    box-shadow: 0 20px 35px -12px rgba(0,0,0,0.1);
    padding: 30px;
}

.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 18px;
    margin-bottom: 20px;
    border-bottom: 1px solid #eef2ff;
    padding-bottom: 20px;
}

.title {
    font-size: 22px;
    font-weight: 700;
    color: #0f172a;
    display: flex;
    align-items: center;
    gap: 10px;
}

.actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

.btn {
    border: none;
    padding: 8px 16px;
    border-radius: 40px;
    font-weight: 100;
    color:#000;
    cursor: pointer;
    transition: 0.2s ease;
    font-size: 14px;
    text-decoration: none;
    display: inline-block;
}

.btn-index {
    background: #fcd4b8;
}

.btn-index:hover {
    background: #fcc39a;
}

.btn-refresh {
    background: #f1f5f9;
}

.btn-refresh:hover {
    background: #bfd5eb;
}

.btn-del {
    background: #fee2e2;
}

.btn-del:hover {
    background: #fecaca;
}

.btn-camera {
    background: #dcfce7;
}

.btn-camera:hover {
    background: #bbf7d0;
}

.stats-bar {
    background: #f8fafc;
    padding: 10px 20px;
    border-radius: 60px;
    margin-bottom: 20px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    font-size: 14px;
    color: #475569;
    font-weight:100;
}

.sum {display:flex;align-items:center;gap:4px;}

.image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 20px;
    margin-top: 20px;
}

.image-card {
    background: #f8fafc;
    border-radius: 10px;
    overflow: hidden;
    transition: 0.2s ease;
    border: 1px solid #e2e8f0;
    position: relative;
}

.image-card:hover {
    transform: translateY(-1px);
    box-shadow: 0 12px 20px -12px rgba(0,0,0,0.2);
}

.img-checkbox {
    position: absolute;
    top: 5px;
    right: 5px;
    z-index: 99;
    width: 18px;
    height: 18px;
    cursor: pointer;
}

.image-card img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
    background: #e2e8f0;
    cursor: pointer;
}

.image-info {
    padding: 10px 8px;
    font-size: 0.7rem;
    color: #334155;
    text-align: center;
    background: white;
    border-top: 1px solid #e2e8f0;
    word-break: break-all;
}

.filename {
    font-weight: 500;
    margin-bottom: 6px;
    color: #0f172a;
    font-size: 12px;
}

.file-time {
    font-size: 10px;
    color: #5b6e8c;
}

.empty-state {
    text-align: center;
    padding: 60px 20px;
    color: #64748b;
    font-weight: 100;
}

.empty-state svg {
    margin-bottom: 15px;
    opacity: 0.5;
}

.footer-bar {
    margin-top: 28px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    padding-top: 18px;
    border-top: 1px solid #e2e8f0;
}

.select-all {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    cursor: pointer;
}

.tip {font-size: 14px;}

.toast-msg {
    position: fixed;
    bottom: 30px;
    left: 20px;
    right: 20px;
    background: #0f172a;
    color: white;
    text-align: center;
    padding: 12px 20px;
    border-radius: 60px;
    font-size: 12px;
    opacity: 0;
    transition: 0.2s;
    pointer-events: none;
    z-index: 1000;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

.loading {
    text-align: center;
    padding: 40px;
    color: #64748b;
}

.preview-modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.9);
    z-index: 2000;
    justify-content: center;
    align-items: center;
    cursor: pointer;
}

.preview-modal img {
    max-width: 90%;
    max-height: 90%;
    object-fit: contain;
}

.close-preview {
    position: absolute;
    top: 20px;
    right: 30px;
    color: white;
    font-size: 40px;
    font-weight: bold;
    cursor: pointer;
}

/* 分页样式 */
.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 8px;
    margin-top: 20px;
    flex-wrap: wrap;
}

.page-btn {
    background: #f1f5f9;
    border: none;
    padding: 6px 12px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 14px;
    transition: 0.2s;
}

.page-btn:hover {
    background: #e2e8f0;
}

.page-btn.active {
    background: #0f172a;
    color: white;
}

.page-btn.disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.page-info {
    font-size: 14px;
    color: #64748b;
    margin: 0 10px;
}

@media (max-width: 640px) {
    .container { padding: 1rem; }
    .image-grid { gap: 12px; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
    .title { font-size: 18px; }
    .page-btn { padding: 4px 10px; font-size: 12px; }
}
</style>
</head>
<body>

<%
if session("login")<>1 then
%>
<form method="post" action="?act=login" class="login-form">
        <div class="header">通行证</div>
        <input type="password" name="password" placeholder="请输入密码">
        <button type="submit"><svg  xmlns="http://www.w3.org/2000/svg" width="34" height="34" fill="currentColor" viewBox="0 0 24 24" ><path d="m9 17 6-5-6-5v4H2v2h7z"></path><path d="M13 3v2c3.86 0 7 3.14 7 7s-3.14 7-7 7v2c4.96 0 9-4.04 9-9s-4.04-9-9-9"></path></svg></button>
        <div class="actions" style="margin-bottom:20px;width:100%;justify-content:space-around;">
                <a href="index.asp?mode=scanQR" class="btn btn-index">返回首页</a>
                <a href="index.asp?mode=camera" class="btn btn-camera">拍照上传</a>
        </div>
</form>
<%
response.end
else
%>

<div class="container">
    <div class="header">
        <div class="title">
            <svg  xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 24 24" ><path d="M4 21h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2M4 5h16v14H4z"></path><path d="M7.5 8a1.5 1.5 0 1 0 0 3 1.5 1.5 0 1 0 0-3m3 6L9 12l-3 4h12l-4.5-6z"></path></svg>
            图片管理
        </div>
        <div class="actions">
            <a href="index.asp?mode=scanQR" class="btn btn-index">返回首页</a>
            <a href="index.asp?mode=camera" class="btn btn-camera">拍照上传</a>
            <button id="refreshBtn" class="btn btn-refresh">刷新列表</button>
            <button id="deleteSelectedBtn" class="btn btn-del">删除选中</button>
            <a href="?act=logout" class="btn btn-index">退出登录</a>
        </div>
    </div>

    <div class="stats-bar" id="statsBar">
        <span class="sum"><svg  xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24" ><path d="M4 2H2v19c0 .55.45 1 1 1h19v-2H4z"></path><path d="M17 12h2v6h-2zm-5-8h2v14h-2zM7 9h2v9H7z"></path></svg>总计<span id="totalCount">0</span>张图片</span>
        <span class="sum"><svg  xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24" ><path d="M10.5 15.5c-.26 0-.51-.1-.71-.29l-2.5-2.5L8.7 11.3l1.79 1.79 4.79-4.79 1.41 1.41-5.5 5.5c-.2.2-.45.29-.71.29Z"></path><path d="M19 21H5c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2M5 5v14h14V5z"></path></svg>已选择<span id="selectedCount">0</span> 张</span>
    </div>

    <div id="imageListContainer" class="image-grid">
        <div class="loading">
            <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <circle cx="12" cy="12" r="10" stroke-dasharray="30" stroke-dashoffset="10">
                    <animate attributeName="stroke-dashoffset" from="30" to="0" dur="1s" repeatCount="indefinite"/>
                </circle>
            </svg>
            <p>加载中...</p>
        </div>
    </div>

    <!-- 分页控件 -->
    <div id="pagination" class="pagination"></div>

    <div class="footer-bar">
        <label class="select-all">
            <input type="checkbox" id="selectAllCheckbox"> 全选 / 取消
        </label>
        <span class="tip">点击图片可预览大图</span>
    </div>
</div>

<div id="toastMsg" class="toast-msg"></div>
<div id="previewModal" class="preview-modal">
    <span class="close-preview">×</span>
    <img id="previewImage" src="" alt="预览">
</div>

<script>
var scriptName = 'view.asp';
var IMAGE_FOLDER = '<%=upload_folder%>';
var currentImages = [];
var currentPage = 1;
var pageSize = 8;
var totalPages = 1;

function showToast(msg, isError) {
    var toast = document.getElementById('toastMsg');
    toast.textContent = msg;
    toast.style.background = isError ? '#b91c1c' : '#0f172a';
    toast.style.opacity = '1';
    setTimeout(function() { toast.style.opacity = '0'; }, 2000);
}

// 获取当前页的数据
function getCurrentPageImages() {
    var start = (currentPage - 1) * pageSize;
    var end = start + pageSize;
    return currentImages.slice(start, end);
}

// 渲染分页控件
function renderPagination() {
    if (totalPages <= 1) {
        document.getElementById('pagination').innerHTML = '';
        return;
    }
    
    var html = '';
    // 上一页
    if (currentPage > 1) {
        html += '<button class="page-btn">上一页</button>';
    } else {
        html += '<button class="page-btn disabled" disabled>上一页</button>';
    }
    
    // 页码
    var startPage = Math.max(1, currentPage - 2);
    var endPage = Math.min(totalPages, currentPage + 2);
    
    if (startPage > 1) {
        html += '<button class="page-btn">1</button>';
        if (startPage > 2) html += '<span class="page-info">...</span>';
    }
    
    for (var i = startPage; i <= endPage; i++) {
        if (i === currentPage) {
            html += '<button class="page-btn active" disabled>' + i + '</button>';
        } else {
            html += '<button class="page-btn">' + i + '</button>';
        }
    }
    
    if (endPage < totalPages) {
        if (endPage < totalPages - 1) html += '<span class="page-info">...</span>';
        html += '<button class="page-btn">' + totalPages + '</button>';
    }
    
    // 下一页
    if (currentPage < totalPages) {
        html += '<button class="page-btn">下一页</button>';
    } else {
        html += '<button class="page-btn disabled" disabled>下一页</button>';
    }
    
    //html += '<span class="page-info">第 ' + currentPage + ' / ' + totalPages + ' 页</span>';
    document.getElementById('pagination').innerHTML = html;
}

// 跳转到指定页
function goToPage(page) {
    if (page < 1 || page > totalPages) return;
    currentPage = page;
    renderImageList(getCurrentPageImages());
    renderPagination();
    // 滚动到顶部
    window.scrollTo({ top: 0, behavior: 'smooth' });
}

// 图片列表
function renderImageList(images) {
    var container = document.getElementById('imageListContainer');
    var totalSpan = document.getElementById('totalCount');
    
    if (!currentImages.length) {
        container.innerHTML = `
            <div class="empty-state">
            <svg  xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" viewBox="0 0 24 24" ><path d="M5 21h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2m0-2v-1.59l3-3 1.29 1.29c.39.39 1.02.39 1.41 0l5.29-5.29 3 3V19h-14ZM19 5v5.59L16.71 8.3a.996.996 0 0 0-1.41 0l-5.29 5.29-1.29-1.29a.996.996 0 0 0-1.41 0l-2.29 2.29V5h14Z"></path><path d="M8.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 1 0 0-3"></path></svg>
            <p>暂无图片</p>
            </div>
            `;
        totalSpan.innerText = '0';
        document.getElementById('selectedCount').innerText = '0';
        return;
    }
    
    totalSpan.innerText = currentImages.length;
    totalPages = Math.ceil(currentImages.length / pageSize);
    
    if (!images.length) {
        container.innerHTML = '<div class="empty-state"><p>暂无图片</p></div>';
    } else {
        var html = '';
        for (var i = 0; i < images.length; i++) {
            var img = images[i];
            var displayName = img.length > 28 ? img.substring(0,25) + '...' : img;
            html += '<div class="image-card" data-filename="' + img.replace(/"/g, '"') + '">'+
                '<input type="checkbox" class="img-checkbox" data-filename="' + img.replace(/"/g, '"') + '" id="chk_' + i + '">'+
                '<img src="' + IMAGE_FOLDER + img + '" alt="' + img + '" loading="lazy"\\'") + '\')">'+
                '<div class="image-info">'+
                '<div class="filename" title="' + img + '">' + displayName + '</div>'+
                '</div>'+
                '</div>';
        }
        container.innerHTML = html;
    }
    
    attachCheckboxEvents();
    updateSelectedCount();
    renderPagination();
}

function attachCheckboxEvents() {
    var checkboxes = document.querySelectorAll('.img-checkbox');
    for (var i = 0; i < checkboxes.length; i++) {
        checkboxes[i].onchange = updateSelectedCount;
    }
    var selectAllChk = document.getElementById('selectAllCheckbox');
    if (selectAllChk) {
        selectAllChk.onchange = handleSelectAll;
    }
}

function handleSelectAll(e) {
    var isChecked = e.target.checked;
    var allCheckboxes = document.querySelectorAll('.img-checkbox');
    for (var i = 0; i < allCheckboxes.length; i++) {
        allCheckboxes[i].checked = isChecked;
    }
    updateSelectedCount();
}

function updateSelectedCount() {
    var selected = document.querySelectorAll('.img-checkbox:checked').length;
    document.getElementById('selectedCount').innerText = selected;
    var selectAllChk = document.getElementById('selectAllCheckbox');
    var total = document.querySelectorAll('.img-checkbox').length;
    if (selectAllChk) {
        selectAllChk.checked = (total > 0 && selected === total);
        selectAllChk.indeterminate = (selected > 0 && selected < total);
    }
}

function fetchImageList() {
    var container = document.getElementById('imageListContainer');
    container.innerHTML = '<div class="loading"><p>加载中...</p></div>';
    
    var xhr = new XMLHttpRequest();
    xhr.open('GET', scriptName + '?mode=listImages', true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                try {
                    var data = JSON.parse(xhr.responseText);
                    if (data.success && Array.isArray(data.files)) {
                        currentImages = data.files;
                        currentPage = 1;
                        renderImageList(getCurrentPageImages());
                    } else {
                        container.innerHTML = '<div class="empty-state">&#9888;&#65039; 获取列表失败</div>';
                    }
                } catch(e) {
                    container.innerHTML = '<div class="empty-state">&#10060; 解析数据失败</div>';
                    console.error('解析错误:', e);
                }
            } else {
                container.innerHTML = '<div class="empty-state">&#10060; HTTP错误: ' + xhr.status + '</div>';
            }
        }
    };
    xhr.send();
}

function deleteSelectedImages() {
    var selectedCheckboxes = document.querySelectorAll('.img-checkbox:checked');
    if (selectedCheckboxes.length === 0) {
        showToast('请先选择要删除的图片', true);
        return;
    }
    if (!confirm('确定要删除 ' + selectedCheckboxes.length + ' 张图片吗?\n操作不可恢复!')) return;
    
    var filesToDelete = [];
    for (var i = 0; i < selectedCheckboxes.length; i++) {
        filesToDelete.push(selectedCheckboxes[i].getAttribute('data-filename'));
    }
    var filesParam = filesToDelete.join(',');
    
    var xhr = new XMLHttpRequest();
    xhr.open('POST', scriptName + '?mode=deleteMultiple', true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                try {
                    var result = JSON.parse(xhr.responseText);
                    if (result.success) {
                        showToast('&#9989; 成功删除 ' + result.deletedCount + ' 张图片');
                        fetchImageList();
                    } else {
                        showToast('删除失败: ' + (result.message || '未知错误'), true);
                    }
                } catch(e) {
                    showToast('删除失败: 响应解析错误', true);
                }
            } else {
                showToast('删除失败: HTTP ' + xhr.status, true);
            }
        }
    };
    xhr.send('files=' + encodeURIComponent(filesParam));
}

// 预览图片
function previewImage(src) {
    var modal = document.getElementById('previewModal');
    var previewImg = document.getElementById('previewImage');
    previewImg.src = src;
    modal.style.display = 'flex';
}

// 关闭预览
document.querySelector('.close-preview').onclick = function() {
    document.getElementById('previewModal').style.display = 'none';
};
document.getElementById('previewModal').onclick = function(e) {
    if (e.target === this) {
        this.style.display = 'none';
    }
};
// ESC键关闭预览
document.onkeydown = function(e) {
    if (e.key === 'Escape') {
        document.getElementById('previewModal').style.display = 'none';
    }
};

// 刷新按钮
document.getElementById('refreshBtn').onclick = function() {
    fetchImageList();
    showToast('刷新中...');
};

// 删除按钮
document.getElementById('deleteSelectedBtn').onclick = deleteSelectedImages;

// 页面加载
fetchImageList();
</script>
<%end if%>
</body>
</html>

01.png (29.65 KB, 下载次数: 0)

01.png

02.png (19.92 KB, 下载次数: 0)

02.png

03.png (131.47 KB, 下载次数: 0)

03.png

04.png (452.64 KB, 下载次数: 0)

04.png

capture.rar

1.09 MB, 下载次数: 85, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 4吾爱币 +4 热心值 +4 收起 理由
mandrake74 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
chinalaodeng + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
shd1971 + 1 + 1 用心讨论,共获提升!
bsxl + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
bookaccount 发表于 2026-7-15 00:57
直接用hta得了,不需要web服务器,权限拉满,还可以调用powershell
推荐
dork 发表于 2026-7-14 22:37
挺好的,就是没有备注照片功能,还要手动修改,另外如果手机照片设置高清的那种一张几十MB的,上传会卡顿。
沙发
448190058 发表于 2026-7-14 22:23
4#
又是馒头 发表于 2026-7-14 23:34
怎么启动啊 给个ASP服务器环境软件啊
5#
倔-强 发表于 2026-7-15 00:01
感觉类似于自建图床功能一样,优化了流程
6#
disvison 发表于 2026-7-15 00:21
有空试试,多谢分享
7#
wem78t 发表于 2026-7-15 00:43
多谢分享
9#
longlang2014 发表于 2026-7-15 01:54
感谢大佬分享
10#
chlong 发表于 2026-7-15 06:31
这个可以有,下来研究两下
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-16 09:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表