|
|
@@ -1,4 +1,4 @@
|
|
|
-<%@ page import="java.util.List" %>
|
|
|
+<%@ page import="java.util.List,java.util.Map" %>
|
|
|
<%@ page language="java" pageEncoding="UTF-8" isELIgnored="false" %>
|
|
|
<%@ taglib uri="/ssTag" prefix="ss"%>
|
|
|
|
|
|
@@ -22,26 +22,44 @@
|
|
|
<script type="text/javascript" src="/wd/js/ueditor/dialogs/wdimage/upload.js"></script>
|
|
|
--%><script type="text/javascript" src="/ss/js/upload.js"></script>
|
|
|
<style type="text/css">
|
|
|
- body{
|
|
|
+ html,
|
|
|
+ body,
|
|
|
+ body.env-input-body{
|
|
|
+ /* 功能说明:锁定页面基础高度链,避免右侧长列表因父级高度不确定而外溢 by xu 20260410 */
|
|
|
+ height: 100%;
|
|
|
+ margin: 0;
|
|
|
+ overflow: hidden;
|
|
|
background: #f5f7fb;
|
|
|
}
|
|
|
|
|
|
#app.form-container{
|
|
|
+ /* 功能说明:锁定表单容器高度,给右侧列表滚动提供确定高度基准 by xu 20260410 */
|
|
|
height: 100%;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
.form-container .content-box{
|
|
|
+ /* 功能说明:锁定内容容器高度,避免内部 flex/grid 按内容撑开 by xu 20260410 */
|
|
|
height: 100% !important;
|
|
|
+ min-height: 0;
|
|
|
padding: 0 !important;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
.xy-zjz-page{
|
|
|
+ /* 功能说明:锁定页面主区域高度,避免学生列表卡片继续向外撑出 by xu 20260410 */
|
|
|
height: 100%;
|
|
|
+ min-height: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
.xy-zjz-layout{
|
|
|
+ /* 功能说明:锁定左右布局高度,确保右侧列表只能在剩余空间内滚动 by xu 20260410 */
|
|
|
height: 100%;
|
|
|
+ min-height: 0;
|
|
|
display: flex;
|
|
|
gap: 0;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
.xy-zjz-left{
|
|
|
flex: 0 0 61%;
|
|
|
@@ -56,16 +74,21 @@
|
|
|
border-right: 1px solid #e2e4ec;
|
|
|
}
|
|
|
.xy-zjz-right{
|
|
|
+ /* 功能说明:右侧改为固定信息区 + 分隔线 + 自适应列表区,彻底避免长名单撑出 by xu 20260410 */
|
|
|
flex: 0 0 39%;
|
|
|
min-width: 320px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+ height: 100%;
|
|
|
+ display: grid;
|
|
|
+ grid-template-rows: 234px 1px minmax(0, 1fr);
|
|
|
+ overflow: hidden;
|
|
|
background: #f7f7f7;
|
|
|
}
|
|
|
.xy-zjz-info-wrapper{
|
|
|
height: 234px;
|
|
|
padding: 30px 35px 35px;
|
|
|
box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
.xy-zjz-camera-toolbar{
|
|
|
display: none;
|
|
|
@@ -91,14 +114,51 @@
|
|
|
#sfzImg.xy-zjz-preview.is-live{
|
|
|
border: 1px solid #3f3f3f;
|
|
|
}
|
|
|
- #sfzImg.xy-zjz-preview img,
|
|
|
- #sfzImg.xy-zjz-preview video{
|
|
|
+ /* 功能说明:照片回显层独立控制显示状态,避免误伤蒙版层 by xu 20260410 */
|
|
|
+ #sfzImg.xy-zjz-preview #image{
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ width: 100% !important;
|
|
|
+ height: 100% !important;
|
|
|
+ object-fit: cover;
|
|
|
+ display: none;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+ /* 功能说明:视频预览层独立控制显示状态,避免误伤蒙版层 by xu 20260410 */
|
|
|
+ #sfzImg.xy-zjz-preview #video{
|
|
|
position: absolute;
|
|
|
inset: 0;
|
|
|
width: 100% !important;
|
|
|
height: 100% !important;
|
|
|
object-fit: cover;
|
|
|
display: none;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+ /* 功能说明:给视频预览叠加独立人脸定位蒙版,始终显示在最上层 by xu 20260410 */
|
|
|
+ #sfzImg.xy-zjz-preview .xy-zjz-face-mask{
|
|
|
+ /* 功能说明:蒙版直接铺满整个预览框,消除上下留白 by xu 20260410 */
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: fill;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 4;
|
|
|
+ display: none;
|
|
|
+ opacity: 0.5;
|
|
|
+ }
|
|
|
+ /* 功能说明:未连接状态下占位层放在蒙版下方,仅保留头像引导层级 by xu 20260410 */
|
|
|
+ #sfzImg.xy-zjz-preview .xy-zjz-camera-placeholder{
|
|
|
+ z-index: 2;
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+ /* 功能说明:引导头像位于蒙版下方、背景上方,符合校准视觉层级 by xu 20260410 */
|
|
|
+ #sfzImg.xy-zjz-preview .xy-zjz-camera-placeholder::before{
|
|
|
+ z-index: 3;
|
|
|
+ }
|
|
|
+ /* 功能说明:提示文案层级低于蒙版,避免影响人脸框观察 by xu 20260410 */
|
|
|
+ #sfzImg.xy-zjz-preview .xy-zjz-camera-placeholder{
|
|
|
+ color: rgba(255,255,255,0.72);
|
|
|
}
|
|
|
#canvas,
|
|
|
#file{
|
|
|
@@ -252,12 +312,15 @@
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
.xy-zjz-list-card{
|
|
|
- flex: 1;
|
|
|
+ /* 功能说明:学生列表卡片占满右侧剩余高度,超长时仅列表内部滚动 by xu 20260410 */
|
|
|
+ height: 100%;
|
|
|
padding: 16px 14px 12px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
min-height: 0;
|
|
|
- max-height: none;
|
|
|
+ max-height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ box-sizing: border-box;
|
|
|
background: #f7f7f7;
|
|
|
}
|
|
|
.xy-zjz-filter-row{
|
|
|
@@ -381,6 +444,29 @@
|
|
|
pageContext.setAttribute("bjcyCount", Integer.valueOf(bjcyCount));
|
|
|
pageContext.setAttribute("firstStudent", firstStudent);
|
|
|
%>
|
|
|
+ <script>
|
|
|
+ // 功能说明:将 JSP 侧真实班级成员数据输出给 Vue 使用,替换前端 mock 数据 by xu 20260410
|
|
|
+ window.xyZjzRealStudentList = [
|
|
|
+ <%
|
|
|
+ for (int i = 0; i < bjcyCount; i++) {
|
|
|
+ Object rowObj = bjcyList.get(i);
|
|
|
+ if (!(rowObj instanceof Map)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Map row = (Map) rowObj;
|
|
|
+ String ryid = row.get("ryid") == null ? "" : row.get("ryid").toString().replace("\\", "\\\\").replace("'", "\\'");
|
|
|
+ String xm = row.get("xm") == null ? "" : row.get("xm").toString().replace("\\", "\\\\").replace("'", "\\'");
|
|
|
+ String ryh = row.get("ryh") == null ? "" : row.get("ryh").toString().replace("\\", "\\\\").replace("'", "\\'");
|
|
|
+ String zjzwj = row.get("zjzwj") == null ? "" : row.get("zjzwj").toString().replace("\\", "\\\\").replace("'", "\\'");
|
|
|
+ %>
|
|
|
+ { ryid: '<%= ryid %>', xm: '<%= xm %>', ryh: '<%= ryh %>', zjzwj: '<%= zjzwj %>' }<%= i < bjcyCount - 1 ? "," : "" %>
|
|
|
+ <%
|
|
|
+ }
|
|
|
+ %>
|
|
|
+ ];
|
|
|
+ // 功能说明:同步输出班级名称给 Vue 显示 by xu 20260410
|
|
|
+ window.xyZjzRealBjmc = "<ss:cbTrans cb='bj' val='${bjid}'/>";
|
|
|
+ </script>
|
|
|
<input name="ryid" value="${firstStudent.ryid}" type="hidden"/>
|
|
|
<input name="zjzwj" value="${firstStudent.zjzwj}" type="hidden">
|
|
|
<input name='wdComponentID' type='hidden' value='xyZjz_excelAdd'/>
|
|
|
@@ -392,9 +478,13 @@
|
|
|
<div class="xy-zjz-preview-shell">
|
|
|
<div id="sfzImg" class="photo xy-zjz-preview">
|
|
|
<div class="xy-zjz-camera-placeholder" id="cameraPlaceholder">点击“连接摄像头”后由浏览器弹出设备/权限选择</div>
|
|
|
+ <%-- 功能说明:叠加人脸蒙版图,辅助拍摄时对齐位置 by xu 20260410 --%>
|
|
|
+ <img class="xy-zjz-face-mask" src="/skin/easy/image/xy-zjz-face-mask.png" alt="人脸定位蒙版"/>
|
|
|
+ <%-- 功能说明:修复图片标签未闭合导致 video/canvas DOM 丢失,摄像头初始化取不到元素 by xu 20260410 --%>
|
|
|
<img id="image"
|
|
|
- src="<ss:serv name='dlByHttp' parm='{"wdConfirmationCaptchaService":"0","path":"${firstStudent.zpwj}","type":"img"}'/>"
|
|
|
+ src="<ss:serv name='dlByHttp' parm='{"wdConfirmationCaptchaService":"0","path":"${firstStudent.zjzwj}","type":"img"}'/>"
|
|
|
onerror="this.src='data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'100\' height=\'100\'%3E%3Crect width=\'100\' height=\'100\' fill=\'%23f0f0f0\'/%3E%3Ctext x=\'50\' y=\'55\' font-size=\'12\' fill=\'%23999\' text-anchor=\'middle\'%3E无照片%3C/text%3E%3C/svg%3E'"
|
|
|
+ />
|
|
|
<video autoplay muted playsinline id="video"></video>
|
|
|
<canvas id="canvas"></canvas>
|
|
|
</div>
|
|
|
@@ -405,7 +495,7 @@
|
|
|
<ss-bottom-button
|
|
|
id="photoConnectBtn"
|
|
|
text="连接摄像头"
|
|
|
- @click='xyZjzCameraPage.connectCamera()'
|
|
|
+ @click='handleConnectCamera()'
|
|
|
icon-class="bottom-div-save"
|
|
|
></ss-bottom-button>
|
|
|
</div>
|
|
|
@@ -413,7 +503,7 @@
|
|
|
<ss-bottom-button
|
|
|
id="photoCaptureBtn"
|
|
|
text="拍照"
|
|
|
- @click='xyZjzCameraPage.capturePhoto()'
|
|
|
+ @click='handleCapturePhoto()'
|
|
|
icon-class="bottom-div-save"
|
|
|
></ss-bottom-button>
|
|
|
</div>
|
|
|
@@ -421,13 +511,13 @@
|
|
|
<ss-bottom-button
|
|
|
id="photoRetakeBtn"
|
|
|
text="重拍"
|
|
|
- @click='xyZjzCameraPage.retakePhoto()'
|
|
|
- icon-class="bottom-div-close"
|
|
|
+ @click='handleRetakePhoto()'
|
|
|
+ icon-class="bottom-div-save"
|
|
|
></ss-bottom-button>
|
|
|
<ss-bottom-button
|
|
|
id="photoSubmitBtn"
|
|
|
text="保存并提交"
|
|
|
- @click='xyZjzCameraPage.submitPhotoForm()'
|
|
|
+ @click='handleSubmitPhotoForm()'
|
|
|
icon-class="bottom-div-save"
|
|
|
></ss-bottom-button>
|
|
|
</div>
|
|
|
@@ -440,7 +530,7 @@
|
|
|
<div class="xy-zjz-info-name">{{currentStudent.xm}}</div>
|
|
|
<div class="xy-zjz-info-body">
|
|
|
<div class="xy-zjz-info-avatar">
|
|
|
- <img :src="currentStudent.zpwj ? '/service?ssServ=dlByHttp&type=img&path=' + currentStudent.zpwj : 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'68\' height=\'100\'%3E%3Crect width=\'68\' height=\'100\' fill=\'%23f2f3f4\'/%3E%3Ctext x=\'34\' y=\'55\' font-size=\'11\' fill=\'%23999\' text-anchor=\'middle\'%3E无照片%3C/text%3E%3C/svg%3E'"/>
|
|
|
+ <img :src="currentStudent.zjzwj ? '/service?ssServ=dlByHttp&type=img&path=' + currentStudent.zjzwj : 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'68\' height=\'100\'%3E%3Crect width=\'68\' height=\'100\' fill=\'%23f2f3f4\'/%3E%3Ctext x=\'34\' y=\'55\' font-size=\'11\' fill=\'%23999\' text-anchor=\'middle\'%3E无照片%3C/text%3E%3C/svg%3E'"/>
|
|
|
</div>
|
|
|
<div class="xy-zjz-info-lines">
|
|
|
<div v-if="currentStudent.xbmc" class="xy-zjz-info-line"><strong>性别:</strong>{{currentStudent.xbmc}}</div>
|
|
|
@@ -456,7 +546,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="xy-zjz-right-divider"></div>
|
|
|
- <div class="xy-zjz-list-card">
|
|
|
+ <div class="xy-zjz-list-card" id="studentListCard">
|
|
|
<div class="xy-zjz-filter-row">
|
|
|
<select class="xy-zjz-filter-select">
|
|
|
<option>年级</option>
|
|
|
@@ -468,13 +558,13 @@
|
|
|
<span class="xy-zjz-filter-progress">{{currentIndex + 1}}/{{bjcyCount}}</span>
|
|
|
<input class="xy-zjz-filter-input" type="text" placeholder="姓名"/>
|
|
|
</div>
|
|
|
- <div class="xy-zjz-student-list">
|
|
|
+ <div class="xy-zjz-student-list" id="studentListBody">
|
|
|
<div v-for="(item, index) in studentList" :key="index"
|
|
|
:class="['xy-zjz-student-row', currentIndex === index ? 'is-active' : '']"
|
|
|
@click="selectStudent(index, item)">
|
|
|
<div class="xy-zjz-student-index">{{index + 1}}</div>
|
|
|
<div class="xy-zjz-student-name">{{item.xm}}</div>
|
|
|
- <div class="xy-zjz-student-id">{{item.sfzh}}</div>
|
|
|
+ <div class="xy-zjz-student-id">{{item.ryh}}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -493,7 +583,9 @@
|
|
|
var state = {
|
|
|
stream: null,
|
|
|
mode: "disconnected",
|
|
|
- enumerated: false
|
|
|
+ enumerated: false,
|
|
|
+ capturedBlob: null,
|
|
|
+ capturedDataUrl: ""
|
|
|
};
|
|
|
var elements = {};
|
|
|
|
|
|
@@ -509,10 +601,11 @@
|
|
|
elements.file = $("file");
|
|
|
elements.videoSource = $("videoSource");
|
|
|
elements.placeholder = $("cameraPlaceholder");
|
|
|
+ elements.faceMask = document.querySelector("#sfzImg .xy-zjz-face-mask");
|
|
|
elements.connectActions = $("cameraConnectActions");
|
|
|
elements.captureActions = $("cameraCaptureActions");
|
|
|
elements.reviewActions = $("cameraReviewActions");
|
|
|
- return !!(elements.preview && elements.video && elements.image && elements.canvas && elements.file && elements.videoSource && elements.placeholder && elements.connectActions && elements.captureActions && elements.reviewActions);
|
|
|
+ return !!(elements.preview && elements.video && elements.image && elements.canvas && elements.file && elements.videoSource && elements.placeholder && elements.faceMask && elements.connectActions && elements.captureActions && elements.reviewActions);
|
|
|
}
|
|
|
|
|
|
function stopTracks(stream){
|
|
|
@@ -643,13 +736,17 @@
|
|
|
return;
|
|
|
}
|
|
|
state.mode = mode;
|
|
|
- elements.connectActions.style.display = mode === "disconnected" ? "flex" : "none";
|
|
|
+ // 功能说明:连接按钮仅在首次未建立预览前显示,后续重拍不再回退到首次连接步骤 by xu 20260410
|
|
|
+ var shouldShowConnect = mode === "disconnected" && !state.enumerated;
|
|
|
+ elements.connectActions.style.display = shouldShowConnect ? "flex" : "none";
|
|
|
elements.captureActions.style.display = mode === "live" ? "flex" : "none";
|
|
|
elements.reviewActions.style.display = mode === "captured" ? "flex" : "none";
|
|
|
if (mode === "live") {
|
|
|
elements.preview.classList.add("is-live");
|
|
|
+ elements.faceMask.style.display = "block";
|
|
|
} else {
|
|
|
elements.preview.classList.remove("is-live");
|
|
|
+ elements.faceMask.style.display = "none";
|
|
|
}
|
|
|
if (mode === "disconnected") {
|
|
|
clearStream();
|
|
|
@@ -685,6 +782,7 @@
|
|
|
return connected;
|
|
|
}
|
|
|
|
|
|
+ // 功能说明:拍照后仅截取当前画面做本地预览,不立即上传,等待保存并提交时再上传 by xu 20260410
|
|
|
function capturePhoto(){
|
|
|
if (!ensureElements()) {
|
|
|
return;
|
|
|
@@ -700,8 +798,51 @@
|
|
|
var context = elements.canvas.getContext("2d");
|
|
|
context.clearRect(0, 0, width, height);
|
|
|
context.drawImage(elements.video, 0, 0, width, height);
|
|
|
- var imgdataBase64 = elements.canvas.toDataURL("image/png");
|
|
|
- initCropper1(elements.file, settings, cropConfirm, convertBase64UrlToBlob(imgdataBase64));
|
|
|
+ var imgdataBase64 = elements.canvas.toDataURL("image/jpeg", 0.92);
|
|
|
+ state.capturedBlob = convertBase64UrlToBlob(imgdataBase64);
|
|
|
+ state.capturedDataUrl = imgdataBase64;
|
|
|
+ clearStream();
|
|
|
+ elements.image.src = imgdataBase64;
|
|
|
+ elements.image.style.display = "block";
|
|
|
+ elements.video.style.display = "none";
|
|
|
+ hidePlaceholder();
|
|
|
+ document.querySelector("input[name='zjzwj']").value = "";
|
|
|
+ setActionMode("captured");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 功能说明:将拍照截图直接上传到图片服务,返回文件路径 by xu 20260410
|
|
|
+ function uploadCapturedPhoto(blob){
|
|
|
+ return new Promise(function(resolve, reject){
|
|
|
+ var fileName = new Date().getTime() + ".jpg";
|
|
|
+ var formData = new FormData();
|
|
|
+ formData.append("application", "");
|
|
|
+ formData.append("fileEdit", new File([blob], fileName, { type: "image/jpeg" }));
|
|
|
+ $.ajax({
|
|
|
+ url: settings.action,
|
|
|
+ type: "POST",
|
|
|
+ data: formData,
|
|
|
+ processData: false,
|
|
|
+ contentType: false,
|
|
|
+ success: function(result){
|
|
|
+ try {
|
|
|
+ if (typeof result === "string") {
|
|
|
+ result = eval("(" + result + ")");
|
|
|
+ }
|
|
|
+ var path = result && result.fileList && result.fileList[0] && result.fileList[0].path;
|
|
|
+ if (path) {
|
|
|
+ resolve(path);
|
|
|
+ } else {
|
|
|
+ reject(new Error("上传返回路径为空"));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: function(xhr){
|
|
|
+ reject(xhr);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
function cropConfirm(result){
|
|
|
@@ -719,21 +860,45 @@
|
|
|
setActionMode("captured");
|
|
|
}
|
|
|
|
|
|
- function retakePhoto(){
|
|
|
+ // 功能说明:重拍时直接回到预览态,不再回到首次连接步骤 by xu 20260410
|
|
|
+ async function retakePhoto(){
|
|
|
if (!ensureElements()) {
|
|
|
return;
|
|
|
}
|
|
|
+ state.capturedBlob = null;
|
|
|
+ state.capturedDataUrl = "";
|
|
|
+ document.querySelector("input[name='zjzwj']").value = "";
|
|
|
elements.image.style.display = "none";
|
|
|
- connectCamera();
|
|
|
+ var connected = await startPreview(elements.videoSource.value);
|
|
|
+ if (connected) {
|
|
|
+ setActionMode("live");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- function submitPhotoForm(){
|
|
|
+ // 功能说明:保存并提交时若存在本地截图则先上传,再回填路径并提交表单 by xu 20260410
|
|
|
+ async function submitPhotoForm(){
|
|
|
var formEl = document.getElementById("app") || document.getElementById("xyZjzPhotoForm");
|
|
|
if (!formEl) {
|
|
|
alert("表单不存在");
|
|
|
return false;
|
|
|
}
|
|
|
- if (!document.querySelector("input[name='zjzwj']").value) {
|
|
|
+ var zjzwjInput = document.querySelector("input[name='zjzwj']");
|
|
|
+ if (!zjzwjInput) {
|
|
|
+ alert("缺少照片字段");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!zjzwjInput.value && state.capturedBlob) {
|
|
|
+ try {
|
|
|
+ var uploadedPath = await uploadCapturedPhoto(state.capturedBlob);
|
|
|
+ zjzwjInput.value = uploadedPath;
|
|
|
+ state.capturedBlob = null;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ alert("拍照上传失败,请重试");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!zjzwjInput.value) {
|
|
|
alert("请先拍照");
|
|
|
return false;
|
|
|
}
|
|
|
@@ -741,6 +906,30 @@
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ // 功能说明:支持 Enter 快捷键,未连接时连摄像头、预览时截图、已截图时直接保存并提交 by xu 20260410
|
|
|
+ function handleEnterShortcut(event){
|
|
|
+ var target = event.target || {};
|
|
|
+ var tagName = (target.tagName || "").toLowerCase();
|
|
|
+ if (tagName === "input" || tagName === "textarea" || tagName === "select" || target.isContentEditable) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (event.key !== "Enter") {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ event.preventDefault();
|
|
|
+ if (state.mode === "disconnected") {
|
|
|
+ connectCamera();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (state.mode === "live") {
|
|
|
+ capturePhoto();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (state.mode === "captured") {
|
|
|
+ submitPhotoForm();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
function convertBase64UrlToBlob(urlData) {
|
|
|
var bytes = window.atob(urlData.split(",")[1]);
|
|
|
var ab = new ArrayBuffer(bytes.length);
|
|
|
@@ -760,13 +949,50 @@
|
|
|
submitPhotoForm: submitPhotoForm
|
|
|
};
|
|
|
|
|
|
+
|
|
|
function initPage(){
|
|
|
if (!ensureElements()) {
|
|
|
return;
|
|
|
}
|
|
|
setActionMode("disconnected");
|
|
|
+ // 功能说明:初始化时绑定 Enter 快捷键,仅绑定一次 by xu 20260410
|
|
|
+ if (!window.__xyZjzEnterShortcutBound) {
|
|
|
+ document.addEventListener("keydown", handleEnterShortcut);
|
|
|
+ window.__xyZjzEnterShortcutBound = true;
|
|
|
+ }
|
|
|
+ syncStudentListHeight();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 功能说明:按右侧真实可用高度动态限制学生列表区域,避免长名单把卡片继续撑出 by xu 20260410
|
|
|
+ function syncStudentListHeight(){
|
|
|
+ var rightPanel = document.querySelector(".xy-zjz-right");
|
|
|
+ var listCard = document.getElementById("studentListCard");
|
|
|
+ var filterRow = document.querySelector(".xy-zjz-filter-row");
|
|
|
+ var studentList = document.getElementById("studentListBody");
|
|
|
+ if (!rightPanel || !listCard || !studentList) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var rightRect = rightPanel.getBoundingClientRect();
|
|
|
+ var listCardRect = listCard.getBoundingClientRect();
|
|
|
+ var filterRect = filterRow ? filterRow.getBoundingClientRect() : {height: 0};
|
|
|
+ var listCardStyle = window.getComputedStyle(listCard);
|
|
|
+ var extraHeight = parseFloat(listCardStyle.paddingTop || 0)
|
|
|
+ + parseFloat(listCardStyle.paddingBottom || 0)
|
|
|
+ + filterRect.height
|
|
|
+ + 12;
|
|
|
+ var availableHeight = Math.floor(rightRect.bottom - listCardRect.top);
|
|
|
+ if (availableHeight > 0) {
|
|
|
+ listCard.style.height = availableHeight + "px";
|
|
|
+ listCard.style.maxHeight = availableHeight + "px";
|
|
|
+ studentList.style.maxHeight = Math.max(80, Math.floor(availableHeight - extraHeight)) + "px";
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ window.addEventListener("resize", syncStudentListHeight);
|
|
|
+ window.addEventListener("load", syncStudentListHeight);
|
|
|
+ setTimeout(syncStudentListHeight, 0);
|
|
|
+ setTimeout(syncStudentListHeight, 300);
|
|
|
+
|
|
|
if (document.readyState === "loading") {
|
|
|
document.addEventListener("DOMContentLoaded", initPage);
|
|
|
} else {
|
|
|
@@ -828,80 +1054,83 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
|
|
|
|
|
|
</script>
|
|
|
<script>
|
|
|
- // 写死测试数据,先调UI
|
|
|
- // 策略:劫持 SS.dom.initializeFormApp,在创建 Vue 时自动注入数据
|
|
|
+ // 功能说明:Vue 初始化改为读取 JSP 注入的真实数据,移除前端 mock 数据 by xu 20260410
|
|
|
(function() {
|
|
|
- const studentList = [
|
|
|
- {xm: '张三', xbmc: '男', jyfsmc: '走读', ryh: '2024001', zpwj: '', zjzwj: '', ryid: '1001', sfzh: '110101200001011234'},
|
|
|
- {xm: '李四', xbmc: '女', jyfsmc: '寄宿', ryh: '2024002', zpwj: '', zjzwj: '', ryid: '1002', sfzh: '110101200002021245'},
|
|
|
- {xm: '王五', xbmc: '男', jyfsmc: '走读', ryh: '2024003', zpwj: '', zjzwj: '', ryid: '1003', sfzh: '110101200003031256'},
|
|
|
- {xm: '赵六', xbmc: '女', jyfsmc: '寄宿', ryh: '2024004', zpwj: '', zjzwj: '', ryid: '1004', sfzh: '110101200004041267'},
|
|
|
- {xm: '钱七', xbmc: '男', jyfsmc: '走读', ryh: '2024005', zpwj: '', zjzwj: '', ryid: '1005', sfzh: '110101200005051278'},
|
|
|
- ];
|
|
|
-
|
|
|
- const firstStudent = studentList[0];
|
|
|
-
|
|
|
- // 等待 SS.ready 后劫持 initializeFormApp
|
|
|
+ var studentList = Array.isArray(window.xyZjzRealStudentList) ? window.xyZjzRealStudentList : [];
|
|
|
+ var firstStudent = studentList.length ? studentList[0] : null;
|
|
|
+ var emptyImage = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Crect width='100' height='100' fill='%23f0f0f0'/%3E%3Ctext x='50' y='55' font-size='12' fill='%23999' text-anchor='middle'%3E无照片%3C/text%3E%3C/svg%3E";
|
|
|
+
|
|
|
function setupHijack() {
|
|
|
if (!window.SS || !window.SS.dom || !window.SS.dom.initializeFormApp) {
|
|
|
setTimeout(setupHijack, 50);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- const originalInit = window.SS.dom.initializeFormApp;
|
|
|
-
|
|
|
+
|
|
|
+ var originalInit = window.SS.dom.initializeFormApp;
|
|
|
+
|
|
|
window.SS.dom.initializeFormApp = function(options) {
|
|
|
- // 合并数据到 options
|
|
|
if (!options.data) {
|
|
|
options.data = function() { return {}; };
|
|
|
}
|
|
|
-
|
|
|
- const originalDataFn = options.data;
|
|
|
+
|
|
|
+ var originalDataFn = options.data;
|
|
|
options.data = function() {
|
|
|
- const originalData = originalDataFn.call(this);
|
|
|
- return {
|
|
|
- ...originalData,
|
|
|
- currentStudent: { ...firstStudent },
|
|
|
- bjmc: '一年级1班',
|
|
|
+ var originalData = originalDataFn.call(this);
|
|
|
+ return Object.assign({}, originalData, {
|
|
|
+ currentStudent: firstStudent ? Object.assign({}, firstStudent) : null,
|
|
|
+ bjmc: window.xyZjzRealBjmc || '',
|
|
|
bjcyCount: studentList.length,
|
|
|
currentIndex: 0,
|
|
|
studentList: studentList,
|
|
|
stream: null,
|
|
|
mode: 'disconnected'
|
|
|
- };
|
|
|
+ });
|
|
|
};
|
|
|
-
|
|
|
- // 合并方法
|
|
|
+
|
|
|
if (!options.methods) {
|
|
|
options.methods = {};
|
|
|
}
|
|
|
+ // 功能说明:为 Vue 模板提供拍照按钮桥接方法,统一通过全局拍照对象调用 by xu 20260410
|
|
|
+ options.methods.handleConnectCamera = function() {
|
|
|
+ return window.xyZjzCameraPage && window.xyZjzCameraPage.connectCamera ? window.xyZjzCameraPage.connectCamera() : false;
|
|
|
+ };
|
|
|
+ // 功能说明:为 Vue 模板提供拍照按钮桥接方法,统一通过全局拍照对象调用 by xu 20260410
|
|
|
+ options.methods.handleCapturePhoto = function() {
|
|
|
+ return window.xyZjzCameraPage && window.xyZjzCameraPage.capturePhoto ? window.xyZjzCameraPage.capturePhoto() : false;
|
|
|
+ };
|
|
|
+ // 功能说明:为 Vue 模板提供拍照按钮桥接方法,统一通过全局拍照对象调用 by xu 20260410
|
|
|
+ options.methods.handleRetakePhoto = function() {
|
|
|
+ return window.xyZjzCameraPage && window.xyZjzCameraPage.retakePhoto ? window.xyZjzCameraPage.retakePhoto() : false;
|
|
|
+ };
|
|
|
+ // 功能说明:为 Vue 模板提供拍照按钮桥接方法,统一通过全局拍照对象调用 by xu 20260410
|
|
|
+ options.methods.handleSubmitPhotoForm = function() {
|
|
|
+ return window.xyZjzCameraPage && window.xyZjzCameraPage.submitPhotoForm ? window.xyZjzCameraPage.submitPhotoForm() : false;
|
|
|
+ };
|
|
|
options.methods.selectStudent = function(index, student) {
|
|
|
this.currentIndex = index;
|
|
|
- this.currentStudent = { ...student };
|
|
|
- const ryidInput = document.querySelector("input[name='ryid']");
|
|
|
- const zjzwjInput = document.querySelector("input[name='zjzwj']");
|
|
|
+ this.currentStudent = Object.assign({}, student);
|
|
|
+ var ryidInput = document.querySelector("input[name='ryid']");
|
|
|
+ var zjzwjInput = document.querySelector("input[name='zjzwj']");
|
|
|
if (ryidInput) ryidInput.value = student.ryid || '';
|
|
|
if (zjzwjInput) zjzwjInput.value = student.zjzwj || '';
|
|
|
- const img = document.getElementById("image");
|
|
|
- if (img && student.zpwj) {
|
|
|
- img.src = "/service?ssServ=dlByHttp&type=img&path=" + student.zpwj;
|
|
|
+ var img = document.getElementById("image");
|
|
|
+ if (img && student.zjzwj) {
|
|
|
+ img.src = "/service?ssServ=dlByHttp&type=img&path=" + student.zjzwj;
|
|
|
} else if (img) {
|
|
|
- img.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Crect width='100' height='100' fill='%23f0f0f0'/%3E%3Ctext x='50' y='55' font-size='12' fill='%23999' text-anchor='middle'%3E无照片%3C/text%3E%3C/svg%3E";
|
|
|
+ img.src = emptyImage;
|
|
|
}
|
|
|
- };
|
|
|
|
|
|
- console.log('[Vue] 数据已合并到初始化选项');
|
|
|
+ setTimeout(syncStudentListHeight, 0);
|
|
|
+ };
|
|
|
+
|
|
|
return originalInit.call(this, options);
|
|
|
};
|
|
|
-
|
|
|
- console.log('[Vue] initializeFormApp 已劫持');
|
|
|
}
|
|
|
-
|
|
|
- // 如果 SS 还没准备好,等它
|
|
|
+
|
|
|
if (typeof SS !== 'undefined' && SS.ready) {
|
|
|
SS.ready(setupHijack);
|
|
|
} else {
|
|
|
setTimeout(setupHijack, 100);
|
|
|
}
|
|
|
})();
|
|
|
-</script>
|
|
|
+</script>
|