js new Image 有时失效,image.width =0

查到的方法

 

因为网速问题。图片并不是一打好src就能加载的

比较保险的办法,IE使用onreadystatechange监听readyState为 loaded或complete时再获取
其它浏览器可使用onload获取

if(window.ActiveXObject) {
   img.onreadystatechange = function() {
      if(img.readyState == "loaded" || img.readyState == "complete") {
        img.onreadystatechange = null;
        alert(img.width);
     }
  }
} else {
  img.onload = function() {
    img.onload  = null;
    alert(img.width);
   }
}


但是自己使用,有些jpg无法实现

 

自己的可以实现的代码如下

 

function validate(){
     var img = new Image;
     if(document.formupload.Attachment_0.value ==null||document.formupload.Attachment_0.value ==''){
        alert("请上传资料!");
       return;
     }
  
    img.src = document.formupload.Attachment_0.value;   
    
    var imgFileSize=Math.round(img.fileSize/1024*100)/100;//取得图片文件的大小 
    
    var imgwidth=img.width;
    var imgHeight=img.height;  
    //alert("imgwidth:"+img.width+"imgHeight"+imgHeight)
    if(imgwidth==0||imgHeight==0) {
     setTimeout(function(){
      //alert("imgwidth:"+img.width+"imgHeight"+img.height)
      if(img.width>800 || img.height>600){
       alert("图片的尺寸超过800x600,请重新处理图片再上传!");
       
       return;
      }
   formupload.method.value="upload";
   formupload.submit();
     },500);
     return;
    }
    if(imgwidth>800 || imgHeight>600){
     alert("图片的尺寸超过800x600,请重新处理图片再上传!");
     
     return;
    }
    
  formupload.method.value="upload";
  formupload.submit();
 }


 

你把这个代码原封不动的写出来/* eslint-disable one-var */ /* eslint-disable no-invalid-this */ import { domUtil } from '../../utils'; import { AbstractVerifyWidget } from '../../providers/abstractWidget'; import basicUtils from '../../utils/basicUtils'; export default class SignatureTrace extends AbstractVerifyWidget { constructor(options = {}) { super(options); this.lastX = 0; this.lastY = 0; this.ctx = null; this.mousePressed = false; this.signImage = null; this.resultImg = []; // 当前签字的索引 this.currentSignIndex = 0; this.savedSignPicInfo = null; this.savedSignWhitePicInfo = null; this.whiteSign = []; const { namePicInfo, signConfig, signConfirmCallback } = options; this.namePicInfo = namePicInfo || []; this.signConfig = signConfig; this.signConfirmCallback = signConfirmCallback; } draw(x, y, isDown) { if (isDown) { this.ctx.beginPath(); // 颜色 this.ctx.strokeStyle = '#000'; // 线宽 this.ctx.lineWidth = this.signConfig?.lineWeight || 10; this.ctx.lineJoin = 'round'; // 设置画笔最大线宽 this.ctx.lineMax = 20; // 设置画笔最小线宽 this.ctx.lineMin = 10; // 设置画笔笔触压力 this.ctx.linePressure = 1.2; // 设置画笔笔触大小变化的平滑度 this.ctx.smoothness = 30; // 设置背景底图颜色 const imageData = this.ctx.getImageData(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); for (let i = 0; i < imageData.data.length; i += 4) { // 当该像素是透明的,则设置成白色 if (imageData.data[i + 3] === 0) { imageData.data[i] = 255; imageData.data[i + 1] = 255; imageData.data[i + 2] = 255; imageData.data[i + 3] = 255; } } // this.ctx.putImageData(imageData, 0, 0); this.ctx.moveTo(this.lastX, this.lastY); this.ctx.lineTo(x, y); this.ctx.closePath(); this.ctx.stroke(); } this.lastX = x; this.lastY = y; if (!this.isCanvasBlank(this.canvasEle)) { this.signatuBreTrauceDetermine.style.opacity = '1'; this.signatureTraceResetting.style.opacity = '1'; } } onCanvasTouchStart = (eventTs) => { // 阻止浏览器默认事件(重要) if (eventTs.targetTouches.length === 1) { eventTs.preventDefault(); const touch = eventTs.targetTouches[0]; this.mousePressed = true; this.draw( touch.clientX - this.canvasEle.getBoundingClientRect().left, touch.clientY - this.canvasEle.getBoundingClientRect().top, false ); } } onCanvasTouchMove = (eventTm) => { if (eventTm.targetTouches.length === 1) { // 阻止浏览器默认事件,重要 eventTm.preventDefault(); const touch = eventTm.targetTouches[0]; if (this.mousePressed) { this.draw( touch.clientX - this.canvasEle.getBoundingClientRect().left, touch.clientY - this.canvasEle.getBoundingClientRect().top, true ); } } } onCanvasTouchEnd = (evt) => { if (evt.targetTouches.length === 1) { // 阻止浏览器默认事件,防止手写的时候拖动屏幕,重要 evt.preventDefault(); this.mousePressed = false; } } onCanvasMouseDown = (eventMd) => { this.mousePressed = true; this.draw( eventMd.clientX - this.canvasEle.getBoundingClientRect().left, eventMd.clientY - this.canvasEle.getBoundingClientRect().top, false ); } onCanvasMouseMove = (eventMv) => { if (this.mousePressed) { this.draw( eventMv.clientX - this.canvasEle.getBoundingClientRect().left, eventMv.clientY - this.canvasEle.getBoundingClientRect().top, true ); } } onCanvasMouseUp = () => { this.mousePressed = false; } getContext() { return this.canvasEle.getContext('2d'); } // 设置描红按钮文字 handleSignConfirmButtonText() { this.signatuBreTrauceDetermine.innerHTML = this.currentSignIndex < this.namePicInfo.length - 1 ? '下一个' : '确定'; } /** * @method handleSignConfirm * @desc 处理签字确认事件 **/ handleSignConfirm = () => { // 判断签名处是否为空 if (this.isCanvasBlank(this.canvasEle)) { return; } this.signImage = this.canvasEle.toDataURL('image/png'); // 装载当前签字的base64 this.resultImg.push(this.signImage); // 获取白底图片并放置全局的whiteSign 获取白底的回掉函数是异步的在获取完白底后执行保存 this.transparentToWhiteSign(this.signImage, (whiteSign) => { this.whiteSign.push(whiteSign); // 最后一次点确定 if (this.currentSignIndex === this.namePicInfo.length - 1) { // 保存以前的签名图片 this.savedSignPicInfo = this.getSignFileInfo(); this.savedSignWhitePicInfo = this.getSignWhiteFileInfo(); this.signConfirmCallback(this.savedSignPicInfo, this.savedSignWhitePicInfo); this.close(); return; } // 累计签名索引 this.currentSignIndex++; // 处理当是最后一个字的时候,按钮文字 this.handleSignConfirmButtonText(this.resultImg, this.namePicInfo); // 第二次点确定看是否有图片如果有图片战术图片没有就展示文字 this.refreshImage(); }); } /** * @method transparentToWhiteSign * @desc 白底图片转化 **/ transparentToWhiteSign(base64String, callback) { const img = new Image(); img.onload = function () { // 创建一个和原图尺寸一样大的Canvas const canvas = document.createElement('canvas'); canvas.width = this.width; canvas.height = this.height; // 绘制白底 this.ctx2 = canvas.getContext('2d'); this.ctx2.fillStyle = '#fff'; this.ctx2.fillRect(0, 0, canvas.width, canvas.height); // 绘制原图像素 this.ctx2.drawImage(this, 0, 0); // 导出新的Base64字符串(默认为png格式) this.newBase64String = canvas.toDataURL('image/png'); // 通过回掉函数获取闭包内的白底图片 callback(this.newBase64String); }; img.src = base64String; } /** * @method getSignWhiteFileInfo * @desc 获取格式转化后的白底图片 **/ getSignWhiteFileInfo() { this.signWhiteFileInfo = {}; if (this.savedSignWhitePicInfo) { this.namePicInfo.forEach((item, index) => { this.savedSignWhitePicInfo[item.characterId] = this.whiteSign[index]; }); this.signWhiteFileInfo = this.savedSignWhitePicInfo; } else { this.namePicInfo.forEach((item, index) => { this.signWhiteFileInfo[item.characterId] = this.whiteSign[index]; }); } return this.signWhiteFileInfo; } /** * @method getSignFileInfo * @desc 获取格式转化后的图片 **/ getSignFileInfo() { this.signFileInfo = {}; if (this.savedSignPicInfo) { this.namePicInfo.forEach((item, index) => { this.savedSignPicInfo[item.characterId] = this.resultImg[index]; }); this.signFileInfo = this.savedSignPicInfo; } else { this.namePicInfo.forEach((item, index) => { this.signFileInfo[item.characterId] = this.resultImg[index]; }); } return this.signFileInfo; } /** * @method isCanvasBlank * @desc 判断签名处是否为空 **/ isCanvasBlank(canvas) { const blank = document.createElement('canvas'); blank.width = canvas.width; blank.height = canvas.height; // 比较值相等则为空 return canvas.toDataURL() === blank.toDataURL(); } /** * @method clearSignArea * @desc 清空签名 **/ clearSignArea = () => { // 按钮样式修改 this.signatuBreTrauceDetermine.style.opacity = '0.3'; this.signatureTraceResetting.style.opacity = '0.3'; this.ctx.setTransform(1, 0, 0, 1, 0, 0); this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); } initDOM() { this.signatureTraceRootElement = domUtil.createElement('div', 'signature-trace-drawer'); // 组件主容器 this.signatureTraceContentElement = domUtil.createElement('div', 'signature-trace-content'); // 内层包装器 const signatureTraceWrapperBodyElement = domUtil.createElement('div', 'signature-trace-wrapper-body'); // 关闭按钮 this.signatureTraceClose = domUtil.createElement('div', 'signature-trace-wrapper-close'); const signatureTraceCloseSpan = document.createElement('span'); signatureTraceCloseSpan.className = 'iconfont svg_ac_LEDClose'; // 标题 this.signatureTraceTitleElement = domUtil.createElement('div', 'signature-trace-title', '请根据展示描写'); // 米图背景颜色 this.imageContainer = domUtil.createElement('div', 'signature-trace-image-container'); // 按钮 const signatureTraceButton = domUtil.createElement('div', 'signature-trace-button'); // 重置 this.signatureTraceResetting = domUtil.createElement('div', 'signature-trace-resetting', '重签'); // 确定按钮 this.signatuBreTrauceDetermine = domUtil.createElement('div', 'signature-trace-determine', '下一个'); // 图片loading this.signatureTraceSpinningElement = domUtil.createElement('div', 'signature-trace-spinner'); // 模板文字图片 this.signatureTraceTemplateTextImg = domUtil.createElement('img', 'signature-trace-template-img'); // 模板文字 this.signatureTraceTemplateText = domUtil.createElement('div', 'signature-trace-template-text'); // cavans 区域 this.signWrapEle = domUtil.createElement('div', 'ps-sign'); this.canvasEle = domUtil.createElement('canvas', 'canvas'); // 重新加载字段容器 this.signatureTraceReloadContent = domUtil.createElement('div', 'signature-trace-reload-content'); // 重新加载字段 this.signatureTraceReload = domUtil.createElement('div', 'signature-trace-reload', '加载失败,请点击刷新'); // 重新加载 loading const signatureTraceSpanLoading = document.createElement('span'); signatureTraceSpanLoading.className = 'iconfont svg_nav_Refresh'; // 插入dom document.body.appendChild(this.signatureTraceRootElement); this.signatureTraceRootElement.appendChild(this.signatureTraceContentElement); this.signatureTraceContentElement.appendChild(signatureTraceWrapperBodyElement); signatureTraceWrapperBodyElement.appendChild(this.signatureTraceClose); this.signatureTraceClose.appendChild(signatureTraceCloseSpan); signatureTraceWrapperBodyElement.appendChild(this.signatureTraceTitleElement); signatureTraceWrapperBodyElement.appendChild(this.imageContainer); signatureTraceWrapperBodyElement.appendChild(signatureTraceButton); signatureTraceButton.appendChild(this.signatureTraceResetting); signatureTraceButton.appendChild(this.signatuBreTrauceDetermine); this.imageContainer.appendChild(this.signWrapEle); this.signWrapEle.appendChild(this.canvasEle); this.imageContainer.appendChild(this.signatureTraceSpinningElement); this.imageContainer.appendChild(this.signatureTraceTemplateTextImg); this.imageContainer.appendChild(this.signatureTraceTemplateText); this.imageContainer.appendChild(this.signatureTraceReloadContent); this.signatureTraceReloadContent.appendChild(this.signatureTraceReload); this.signatureTraceReloadContent.appendChild(signatureTraceSpanLoading); // 遮罩层 this.modalBackdropElement = domUtil.createElement('div', 'signature-trace-backdrop signature-trace-fade'); this.closeRefresh(); } addEvent() { // 刷新描红图片 this.signatureTraceReloadContent.addEventListener('click', this.refreshImage.bind(this), false); // 关闭事件 this.signatureTraceClose.addEventListener('click', () => { this.clearSignArea(); return this.close(); }, false); this.signatureTraceResetting.addEventListener('click', this.clearSignArea, false); this.signatuBreTrauceDetermine.addEventListener('click', this.handleSignConfirm, false); this.canvasEle.addEventListener('touchstart', this.onCanvasTouchStart, false); this.canvasEle.addEventListener('touchmove', this.onCanvasTouchMove, false); this.canvasEle.addEventListener('touchend', this.onCanvasTouchEnd, false); this.canvasEle.addEventListener('mousedown', this.onCanvasMouseDown, false); this.canvasEle.addEventListener('mousemove', this.onCanvasMouseMove, false); this.canvasEle.addEventListener('mouseup', this.onCanvasMouseUp, false); } removeEvent() { // 刷新描红图片 this.signatureTraceReloadContent.removeEventListener('click', this.refreshImage, false); // 关闭事件 this.signatureTraceClose.removeEventListener('click', () => { this.clearSignArea(); return this.close(); } , false); this.signatureTraceTemplateTextImg.onload = this.imageSuccessfully.bind(this); this.signatureTraceTemplateTextImg.onerror = this.imageFailed.bind(this); this.signatureTraceResetting.removeEventListener('click', this.clearSignArea, false); this.signatuBreTrauceDetermine.removeEventListener('click', this.handleSignConfirm, false); this.canvasEle.removeEventListener('touchstart', this.onCanvasTouchStart, false); this.canvasEle.removeEventListener('touchmove', this.onCanvasTouchMove, false); this.canvasEle.removeEventListener('touchend', this.onCanvasTouchEnd, false); this.canvasEle.removeEventListener('mousedown', this.onCanvasMouseDown, false); this.canvasEle.removeEventListener('mousemove', this.onCanvasMouseMove, false); this.canvasEle.removeEventListener('mouseup', this.onCanvasMouseUp, false); } init() { this.initDOM(); this.addEvent(); this.ctx = this.getContext(); // this.getInitSignature(); super.init(); } start() { // 为内容区域的tranform动画打开优化 this.signatureTraceRootElement.style.willChange = 'transform'; // 动态设置canvas宽高 this.canvasEle.width = this.imageContainer.offsetWidth; this.canvasEle.height = this.imageContainer.offsetHeight; // 动态设置兜底字体大小 this.signatureTraceTemplateText.style.fontSize = `${this.imageContainer.offsetWidth / 1.2}px`; super.start(); // 清除默认滑动事件 document.body.style.overflow = 'hidden'; // 刷新第一张图片 this.refreshImage(); } /** * @method closeLoading * @desc 隐藏loading **/ closeLoading() { this.signatureTraceSpinningElement.style.display = 'none'; this.signWrapEle.style.display = 'block'; } /** * @method openLoading * @desc 打开loading **/ openLoading() { this.signatureTraceSpinningElement.style.display = 'block'; this.signatureTraceTemplateTextImg.style.display = 'none'; this.signatureTraceTemplateText.style.display = 'none'; this.signWrapEle.style.display = 'none'; // 十五秒图片未加载成功则打开刷新关闭loading this.imgTimeout = setTimeout(() => { this.closeLoading(); this.signatureTraceTemplateTextImg.src = ''; this.openRefresh(); }, 15000); } /** * @method closeRefresh * @desc 清空刷新按钮和文字 **/ closeRefresh() { this.signatureTraceReloadContent.style.display = 'none'; } /** * @method openRefresh * @desc 打开刷新按钮和文字 **/ openRefresh() { this.signWrapEle.style.display = 'none'; this.signatureTraceReloadContent.style.display = 'flex'; } /** * @method imageFailed * @desc 图片加载失败 **/ imageFailed() { this.signatureTraceTemplateTextImg.style.display = 'none'; this.signatureTraceSpinningElement.style.display = 'none'; clearTimeout(this.imgTimeout); this.openRefresh(); } /** * @method imageSuccessfully * @desc 图片加载成功 **/ imageSuccessfully() { this.signatureTraceTemplateTextImg.style.display = 'block'; clearTimeout(this.imgTimeout); this.closeLoading(); } /** * @method refreshImage * @desc 图片刷新加载 **/ refreshImage = () => { console.log(this.resultImg); console.log(this.namePicInfo); // 绑定加载成功和失败事件 this.signatureTraceTemplateTextImg.onload = this.imageSuccessfully.bind(this); this.signatureTraceTemplateTextImg.onerror = this.imageFailed.bind(this); // 当前需要签署的图片信息 const currentNeedSignPictureInfo = this.namePicInfo[this.currentSignIndex]; const {picUrl: pictureUrl, character, isPoint = '0'} = currentNeedSignPictureInfo; // 清空签字画布 this.clearSignArea(); // 关闭刷新按钮 this.closeRefresh(); // 使重签按钮恢复 画布恢复 (兼容上一个字是"●"的情况 , 会设置不同的样式和禁用签字区域) this.signatureTraceResetting.style.display = 'block'; domUtil.removeClass(this.signatuBreTrauceDetermine, 'center-block'); // 恢复顶部提示文案(兼容上一个字是"●"的情况) this.signatureTraceTitleElement.innerHTML = '请根据展示描写'; // 恢复滑动(兼容上一个字是"●"的情况, 禁用签字区域) this.canvasEle.style.pointerEvents = 'auto'; // 处理服务端下发的图片 basicUtils.isNotEmpty(pictureUrl) ? this.loadSignImage(pictureUrl) : this.handleNoSignImage(isPoint, character); } handleNoSignImage(isPoint, character) { // 处理未下发图片的情况 this.signatureTraceTemplateText.style.display = 'flex'; this.signatureTraceTemplateText.innerHTML = character; // 动态设置兜底字体大小 this.signatureTraceTemplateText.style.fontSize = `${this.imageContainer.offsetWidth / 1.2}px`; // 不显示服务端下发的图片 this.signatureTraceTemplateTextImg.style.display = 'none'; // 不符合 "●" 的情况不做处理 if (isPoint !== '1') { return; } // 使重签按钮消失 画布失效 this.signatureTraceResetting.style.display = 'none'; // 使文字强制不显示 this.signatureTraceTemplateText.innerHTML = ''; this.signatuBreTrauceDetermine.style.opacity = '1'; // 使按钮应用新的样式(直接进行下一个, 它的按钮大小与非 "点"的有所不同) domUtil.addClass(this.signatuBreTrauceDetermine, 'center-block'); const titleEndPartText = this.currentSignIndex < this.namePicInfo.length - 1 ? '下一个' : '确定'; // 顶部文字变换 this.signatureTraceTitleElement.innerHTML = `无需描写,请直接点击${titleEndPartText}`; // 无法滑动 this.canvasEle.style.pointerEvents = 'none'; // 绘制原点(这个数据用于展示在最终签好的画板上供用户预览) setTimeout(() => { this.ctx2 = this.canvasEle.getContext('2d'); this.ctx2.fillStyle = 'black'; this.ctx2.beginPath(); this.ctx2.arc(this.canvasEle.width / 2, this.canvasEle.height / 2, 24, 0, Math.PI * 2, true); this.ctx2.fill(); this.ctx2.stroke(); }, 0); } loadSignImage(pictureUrl) { // 不显示通过文字渲染的底图 this.signatureTraceTemplateText.style.display = 'none'; // 启动loading this.openLoading(); // 降低透明度 来达到视觉效果 this.signatureTraceTemplateTextImg.style.opacity = '0.4'; // 重新写入src , 迫使图片重新加载 this.signatureTraceTemplateTextImg.src = pictureUrl; } /** * @method reSign * @desc 重签显示组件 **/ reSign(renamePicInfo) { // renamePicInfo为新改的字段[{character: '吴',characterId: '998E9CC42369F0AAEDE9988F3D6',picUrl: ''},] id必须和以前一致 this.resultImg = []; this.whiteSign = []; this.namePicInfo = renamePicInfo; // 打开dom this.signatureTraceRootElement.style.display = 'flex'; // 恢复签名索引 this.currentSignIndex = 0; // 重签只有一个字,修改描红上的按钮为 确定 this.handleSignConfirmButtonText(); // 重置初始化模板字段 this.refreshImage(); } /** * @method close * @desc 关闭组件 **/ close() { // 移除遮罩层show样式 this.signatureTraceRootElement.style.display = 'none'; this.signWrapEle.style.display = 'block'; this.currentSignIndex = 0; clearTimeout(this.imgTimeout); this.closeRefresh(); } /** * @method destroy * @desc 销毁组件 **/ destroy() { // 清除默认滑动事件 document.body.style.overflow = ''; this.signatureTraceRootElement.remove(); this.removeEvent(); } }
05-23
仔细检查一下 现在这两个是我新建的文件,是否跟原本的xib实现效果一致 // // NewCoverImageBigContainerView.swift // SurveillanceHome // // Created by MaCong on 2025/12/8. // Copyright © 2025 tplink. All rights reserved. // import UIKit import SnapKit class NewCoverImageBigContainerView: NewCoverImageContainerView { // MARK: - Additional Subviews from XIB let nvrChannelNameContainer = UIView() let nvrChannelNameLabel = UILabel() let helpButton = UIButton(type: .custom) let helpLabel = UILabel() // Gradient Background inside nvrChannelNameContainer private let gradientView = GradientView() // MARK: - Initializers override init(frame: CGRect) { super.init(frame: frame) setupSubviews() setupConstraints() setupAppearance() } required init?(coder: NSCoder) { super.init(coder: coder) setupSubviews() setupConstraints() setupAppearance() } // MARK: - Private Setup Methods private func setupSubviews() { // Add gradient background to container gradientView.startColor = UIColor(white: 0, alpha: 0) gradientView.endColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.7) gradientView.horizontalMode = false gradientView.startLocation = 0.0 gradientView.endLocation = 1.0 nvrChannelNameContainer.addSubview(gradientView) nvrChannelNameContainer.addSubview(nvrChannelNameLabel) addSubview(nvrChannelNameContainer) addSubview(helpButton) addSubview(helpLabel) } private func setupConstraints() { // Layout using SnapKit to match XIB constraints nvrChannelNameContainer.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview() make.height.equalTo(24) } gradientView.snp.makeConstraints { make in make.edges.equalToSuperview() } nvrChannelNameLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(10) make.trailing.equalToSuperview().offset(-6) make.centerY.equalToSuperview().offset(2) } helpButton.snp.makeConstraints { make in make.centerX.equalToSuperview() make.centerY.equalToSuperview().offset(20) make.size.equalTo(CGSize(width: 68, height: 29)) } helpLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.centerY.equalToSuperview().offset(20) make.leading.greaterThanOrEqualToSuperview().offset(20) make.trailing.lessThanOrEqualToSuperview().offset(-20) } } private func setupAppearance() { // Apply styles matching XIB layer.cornerRadius = 2 clipsToBounds = true nvrChannelNameContainer.layer.cornerRadius = 2 nvrChannelNameContainer.clipsToBounds = true nvrChannelNameLabel.font = UIFont(name: "PingFangSC-Regular", size: 13) nvrChannelNameLabel.textColor = .white nvrChannelNameLabel.textAlignment = .left nvrChannelNameLabel.text = "Label" helpButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 12) helpButton.setTitleColor(.white, for: .normal) helpButton.layer.cornerRadius = 14 helpButton.clipsToBounds = true helpLabel.font = UIFont(name: "PingFangSC-Regular", size: 12) helpLabel.textColor = .white helpLabel.textAlignment = .center helpLabel.text = "查看帮助" helpLabel.isUserInteractionEnabled = false } // MARK: - Public APIs (to match original IBOutlet usage) func showHelpSection(visible: Bool) { helpButton.isHidden = !visible helpLabel.isHidden = !visible } func setChannelName(_ name: String?) { guard let name = name, !name.isEmpty else { nvrChannelNameContainer.isHidden = true return } nvrChannelNameLabel.text = name nvrChannelNameContainer.isHidden = false } func setMaskText(_ text: String?) { maskLabel?.text = text ?? "" } func setCoverImage(_ image: UIImage?) { coverImageView?.image = image } } // NewCoverImageContainerView.swift // SurveillanceHome // // Created by MaCong on 2025/12/8. // Copyright © 2025 tplink. All rights reserved. // import UIKit import SnapKit class NewCoverImageContainerView: UIView { // MARK: - Subviews var coverImageView: UIImageView! var maskImageView: UIImageView! var maskLabel: UILabel! var helpButton: UIButton? var helpLabel: UILabel! var collectButton: UIButton! // Constraints private var maskLabelCenterYConstraint: Constraint? // Data & Actions private var isCollected: Bool = false { didSet { let image = isCollected ? TPImageLiteral("device_have_collect_card") : TPImageLiteral("device_not_collect_card") collectButton.setImage(image, for: .normal) } } var collectAction: ((Bool) -> Void)? var helpAction: (() -> Void)? // Status Icons (for future extension if needed) private var statusImageViews = [UIImageView]() private let numberOfStatusImages = 0 private let needHideMask = 1 // MARK: - Initializers override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { setupViews() setupConstraints() } // MARK: - Setup Views private func setupViews() { backgroundColor = .clear layer.cornerRadius = 2 layer.masksToBounds = true // === Cover Image View === coverImageView = UIImageView() coverImageView.contentMode = .scaleToFill coverImageView.clipsToBounds = true coverImageView.isUserInteractionEnabled = false // === Mask Image View === maskImageView = UIImageView() maskImageView.contentMode = .scaleToFill maskImageView.clipsToBounds = true maskImageView.isUserInteractionEnabled = false maskImageView.tag = 3 // === Mask Label === maskLabel = UILabel() maskLabel.tag = 4 maskLabel.text = LocalizedString(key: deviceListNoSuchDevice) maskLabel.font = UIFont(name: "PingFangSC-Regular", size: 12) maskLabel.textColor = UIColor(white: 1.0, alpha: 1.0) maskLabel.textAlignment = .center maskLabel.backgroundColor = .clear maskLabel.numberOfLines = 1 maskLabel.adjustsFontSizeToFitWidth = false maskLabel.baselineAdjustment = .alignBaselines // === Help Button (transparent clickable area) === helpButton = UIButton(type: .custom) helpButton?.backgroundColor = .clear helpButton?.layer.cornerRadius = 14 helpButton?.clipsToBounds = true helpButton?.layer.borderWidth = 1 helpButton?.layer.borderColor = UIColor.tpbTextDisabled.cgColor helpButton?.addTarget(self, action: #selector(helpButtonClicked), for: .touchUpInside) // === Help Label (inside helpButton) === helpLabel = UILabel() helpLabel.text = LocalizedString(key: deviceListOfflineNeedHelp) helpLabel.font = UIFont(name: "PingFangSC-Regular", size: 12) helpLabel.textColor = UIColor(white: 1.0, alpha: 1.0) helpLabel.textAlignment = .center helpLabel.backgroundColor = .clear helpButton?.addSubview(helpLabel) // === Collect Button === collectButton = UIButton(type: .custom) collectButton.setImage(TPImageLiteral("device_not_collect_card"), for: .normal) collectButton.addTarget(self, action: #selector(collectButtonClicked), for: .touchUpInside) collectButton.isHidden = true // Add to hierarchy addSubview(coverImageView) addSubview(maskImageView) addSubview(maskLabel) addSubview(collectButton) addSubview(helpButton!) } // MARK: - Setup Constraints with SnapKit private func setupConstraints() { coverImageView.snp.makeConstraints { make in make.edges.equalToSuperview() } maskImageView.snp.makeConstraints { make in make.edges.equalToSuperview() } maskLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() self.maskLabelCenterYConstraint = make.centerY.equalToSuperview().constraint make.height.equalTo(17) } collectButton.snp.makeConstraints { make in make.top.equalToSuperview().offset(8) make.trailing.equalToSuperview().offset(-8) make.width.height.equalTo(26) } helpButton?.snp.makeConstraints { make in make.centerY.equalTo(maskLabel) make.trailing.equalToSuperview().offset(-8) make.width.greaterThanOrEqualTo(30) } helpLabel.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)) } // Setup status image views (if enabled later) for i in 0..<numberOfStatusImages { let imageView = UIImageView() imageView.isHidden = true imageView.isUserInteractionEnabled = false statusImageViews.append(imageView) addSubview(imageView) } } // MARK: - Actions @objc private func collectButtonClicked() { isCollected.toggle() collectAction?(isCollected) } @objc private func helpButtonClicked() { helpAction?() } // MARK: - Public Configuration Methods func shouldShowCollectButton(device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo? = nil, isCard: Bool) { let isVms = TPAppContextFactory.shared().isVmsLogin collectButton.isHidden = (device.displayType == .solar || device.listType == .local || !isCard || (TPSSAppContext.shared.loginType == .personal ? false : TPSSAppContext.shared.isVmsDeviceListFavoriteOldVersion)) if device.deviceType == .IPC { isCollected = isVms ? device.isVMSFavorited : device.isCollected } else if device.deviceType == .NVR { if let channel = channel { isCollected = isVms ? channel.isVMSFavorited : device.isCollected && device.collectChannels.contains(channel.channelId as NSNumber) } else { isCollected = isVms ? device.isVMSFavorited : device.isCollected } } } func configureCoverWith(size: TPSSDeviceForDeviceList.CoverImageSize, device: TPSSDeviceForDeviceList, channel: TPSSChannelInfo? = nil, hideMaskIfNeeded: Bool = false, isCard: Bool = false, folded: Bool = false, leadingConst: NSLayoutConstraint? = nil, trailConst: NSLayoutConstraint? = nil, isSmall: Bool = false, isModelCover: Bool = false, noDevice: Bool = false, isHideLogo: Bool = false) { var hasNoChannels = true if let channel = channel { hasNoChannels = !device.activeChannelsInfo.contains(channel) } shouldShowCollectButton(device: device, channel: channel, isCard: isCard) var coverImage: UIImage? = device.displayType == .solar ? TPSSDeviceForDeviceList.defaultSolarCoverImage(of: size) : TPSSDeviceForDeviceList.defaultCoverImage(of: size) if isModelCover { coverImage = TPSSDeviceForDeviceList.getDeviceModelImage(type: .IPC, model: noDevice ? "" : channel?.deviceModel ?? "") } if !hasNoChannels { coverImage = device.coverImage(of: size, channelID: channel?.channelId.intValue ?? -1, listType: device.listType) } if hideMaskIfNeeded && !maskImageView.isHidden { maskImageView.isHidden = maskImageView.tag == needHideMask && coverImage != nil } coverImageView.image = coverImage ?? (device.displayType == .solar ? TPSSDeviceForDeviceList.defaultSolarCoverImage(of: size) : (isModelCover ? TPSSDeviceForDeviceList.getDeviceModelImage(type: .IPC, model: noDevice ? "" : channel?.deviceModel ?? "") : TPSSDeviceForDeviceList.defaultCoverImage(of: size))) // Handle fisheye / pano logic var isFisheye = false if TPSSAppContext.shared.isVmsLogin && device.listType == .remote { isFisheye = coverImage != nil && device.coverImageIsFisheye(of: size, channelID: channel?.channelId.intValue ?? -1, listType: device.listType) } else if let channelInfo = channel, device.displayType == .NVR { isFisheye = coverImage != nil && channelInfo.supportsFisheye } else { isFisheye = coverImage != nil && device.supportFishEye } var isPano = false if let channelInfo = channel, device.displayType == .NVR { isPano = coverImage != nil && channelInfo.isSupportSplicingSetting } else { isPano = coverImage != nil && device.supportPano } let imgSize = coverImage?.size ?? CGSize(width: 1, height: 1) if (!isFisheye && imgSize.width > 1.1 && abs(imgSize.width - imgSize.height) < 1) { isFisheye = true } coverImageView.contentMode = isFisheye ? (isCard ? .scaleAspectFit : .scaleAspectFill) : .scaleToFill isPano = isPano || (imgSize.width / imgSize.height >= 3) backgroundColor = isFisheye ? .tpbFisheyeBackground : .clear if device.displayType != .solar { if let oldImg = coverImageView.image, let newImg = cropFishScaleImg(img: oldImg, isFishEye: isFisheye), oldImg != newImg { coverImageView.image = newImg coverImageView.contentMode = isCard ? .scaleAspectFit : .scaleAspectFill backgroundColor = .tpbFisheyeBackground } if isPano { coverImageView.contentMode = .scaleAspectFill backgroundColor = .clear } if let oldImg = coverImageView.image, let newImg = cropFishScaleImg(img: oldImg, isFishEye: isFisheye), isCard && (isFisheye || oldImg != newImg) && (!folded || device.deviceType == .IPC) { coverImageView.contentMode = .scaleAspectFit } } if device.displayType == .solar || (isModelCover && (noDevice || coverImage == nil)) { coverImageView.contentMode = .scaleAspectFit coverImageView.backgroundColor = .tpbBackground } else { coverImageView.backgroundColor = .clear } if isHideLogo && coverImage == nil { coverImageView.isHidden = true } else { coverImageView.isHidden = false } guard let finalImage = coverImageView.image, !isFisheye, finalImage.size.height > finalImage.size.width else { return } backgroundColor = .tpbFisheyeBackground coverImageView.contentMode = .scaleAspectFill } func configureWith(cloudStorageEnabled: Bool = false, supportsAlarm: Bool = false, alarm: Bool = true, noDevice: Bool = false, channelOffline: Bool = false, channelHidden: Bool = false, channelUnAuth: Bool = false, channelUninitialized: Bool = false, offline: Bool = false, nvrOffline: Bool = false, ipcOffline: Bool = false, offlineTime: String = "", notInSharePeriod: Bool = false, notShareEnable: Bool = false, notConnected: Bool = false, supportConnectionInfo: Bool = false, connectedViaWifi: Bool = false, wifiRssi: Int = 0, showHelp: Bool = false, playButtonHidden: Bool = false, size: TPSSDeviceForDeviceList.CoverImageSize, showText: Bool = true, showIcons: Bool = true, hasNoVmsLicense: Bool = false, isChannel: Bool = false) { let showAlarm = supportsAlarm && !noDevice let showWifiStatus = supportConnectionInfo && connectedViaWifi && !(offline || nvrOffline || ipcOffline) let showCloudStorage = cloudStorageEnabled let images: [UIImage] = [ showAlarm ? (alarm ? TPImageLiteral("devicelist_indicator_alarm_on") : TPImageLiteral("devicelist_indicator_alarm_off")) : nil, showWifiStatus ? UIImage(named: "common_wifi_indicator\(wifiRssi)") : nil, showCloudStorage ? TPImageLiteral("cloud_label") : nil ].compactMap { $0 } for i in 0..<min(statusImageViews.count, images.count) { statusImageViews[i].image = images[i] statusImageViews[i].isHidden = !showIcons } for i in images.count..<statusImageViews.count { statusImageViews[i].isHidden = true } let deviceOffline = offline || nvrOffline || ipcOffline || channelOffline let basicMaskVisible = noDevice || deviceOffline || notInSharePeriod || notShareEnable || notConnected || channelHidden || hasNoVmsLicense maskImageView.isHidden = !basicMaskVisible maskLabel.isHidden = !basicMaskVisible && !playButtonHidden maskImageView.image = nil maskImageView.backgroundColor = basicMaskVisible ? .tpbDeviceMask : .clear maskImageView.contentMode = .scaleAspectFit maskImageView.tag = basicMaskVisible ? 0 : needHideMask maskLabel.numberOfLines = basicMaskVisible ? (offlineTime.isEmpty ? 1 : 0) : 1 if deviceOffline { if isChannel && noDevice { maskLabel.text = LocalizedString(key: deviceListNoSuchDevice) } else if isChannel && channelUnAuth { maskLabel.text = LocalizedString(key: NVRAddIPCDeviceNotCertified) } else if isChannel && channelUninitialized { maskLabel.text = LocalizedString(key: multiWindowPlayerUninitialized) } else { let firstLine = notInSharePeriod ? LocalizedString(key: deviceListNotInSharePeriod) : LocalizedString(key: deviceListDeviceNotOnline) if !offlineTime.isEmpty { let secondLine = String(format: LocalizedString(key: deviceListOfflineTimeFormat), offlineTime) let attributedText = NSMutableAttributedString(string: "\(firstLine)\n\(secondLine)") let style = NSMutableParagraphStyle() style.lineSpacing = 8 style.alignment = .center attributedText.addAttributes([ .font: UIFont.projectFont(ofSize: 12), .paragraphStyle: style ], range: NSMakeRange(0, firstLine.count)) attributedText.addAttributes([ .font: UIFont.projectFont(ofSize: 10) ], range: NSMakeRange(firstLine.count + 1, secondLine.count)) maskLabel.attributedText = attributedText } else { maskLabel.text = firstLine } } } else if noDevice { maskLabel.text = LocalizedString(key: deviceListNoSuchDevice) } else if notShareEnable { maskLabel.text = LocalizedString(key: deviceListNotShareEnable) } else if notInSharePeriod && !deviceOffline { maskLabel.text = LocalizedString(key: deviceListNotInSharePeriod) } else if channelUnAuth { maskLabel.text = LocalizedString(key: NVRAddIPCDeviceNotCertified) } else if channelHidden { maskLabel.text = LocalizedString(key: deviceListChannelHidden) } else if notConnected { maskLabel.text = LocalizedString(key: wirelessDirectNotConnected) } else if hasNoVmsLicense { maskLabel.text = LocalizedString(key: deviceListCloudVmsWithoutLicenseMaskLabel) } else { maskImageView.isHidden = playButtonHidden maskLabel.isHidden = true switch size { case .small: maskImageView.image = TPImageLiteral("devicelist_preview_small") maskImageView.contentMode = .scaleAspectFit case .medium: maskImageView.image = TPImageLiteral("devicelist_preview_medium") maskImageView.contentMode = .center default: maskImageView.image = TPImageLiteral("devicelist_preview_large") maskImageView.contentMode = .center } maskImageView.backgroundColor = .clear maskImageView.tag = needHideMask } if !showText { maskLabel.text = "" } // Update help button visibility and label position helpButton?.isHidden = !showHelp helpLabel.isHidden = !showHelp maskLabelCenterYConstraint?.update(offset: showHelp ? -22 : 0) } // Helper method (assumed to exist) private func cropFishScaleImg(img: UIImage?, isFishEye: Bool) -> UIImage? { // Implement or keep existing version return img } } // MARK: - Extension: Configure Overloads extension NewCoverImageContainerView { enum CoverType { case play case selection } func configure(ipc: TPSSDeviceForDeviceList, size: TPSSDeviceForDeviceList.CoverImageSize = .medium, type: CoverType = .play, isCard: Bool = false, folded: Bool = false) { shouldShowCollectButton(device: ipc, isCard: isCard) let showAccessory = size != .small let showDetailText = showAccessory && size != .medium let showPlayButton = (ipc.displayType != .solar && type == .play) if ipc.listType == .remote { configureWith( cloudStorageEnabled: ipc.cloudStorageService()?.serviceState.isValid ?? false, supportsAlarm: ipc.supportMessage && ipc.displayOnline && !ipc.isSharedDevice, alarm: ipc.messageEnable, ipcOffline: !ipc.displayOnline, offlineTime: showDetailText ? ipc.offlineTime : "", notInSharePeriod: (ipc.isSharedDevice && !ipc.isInSharePeriod), notShareEnable: (ipc.isSharedDevice && !ipc.isShareEnable), supportConnectionInfo: ipc.supportConnectInfo, connectedViaWifi: ipc.linkType == .wifi, wifiRssi: ipc.linkRssi, showHelp: showAccessory && !ipc.displayOnline && !ipc.isSharedDevice, playButtonHidden: !showPlayButton, size: size, showText: showAccessory, showIcons: showAccessory, hasNoVmsLicense: ipc.isLicenseNotActive ) } else { configureWith( ipcOffline: !ipc.displayOnline, offlineTime: showDetailText ? ipc.offlineTime : "", supportConnectionInfo: ipc.supportConnectInfo, connectedViaWifi: ipc.linkType == .wifi, wifiRssi: ipc.linkRssi, showHelp: showAccessory && !ipc.displayOnline && !ipc.isSharedDevice, playButtonHidden: !showPlayButton, size: size, showText: showAccessory, showIcons: showAccessory ) } configureCoverWith(size: size, device: ipc, channel: ipc.channelsInfo.first, hideMaskIfNeeded: true, isCard: isCard, folded: folded) statusImageViews.forEach { $0.isHidden = true } } func configure(channel: TPSSChannelInfo, nvr: TPSSDeviceForDeviceList, size: TPSSDeviceForDeviceList.CoverImageSize = .medium, type: CoverType = .play, isCard: Bool = false, folded: Bool = false, leadingConst: NSLayoutConstraint? = nil, trialConst: NSLayoutConstraint? = nil, isSmall: Bool = false, isSigleChannel: Bool = false, isHideLogo: Bool = false) { let showAccessory = size != .small let showPlayButton = type == .play && !isSigleChannel shouldShowCollectButton(device: nvr, channel: channel, isCard: isCard) if nvr.listType == .remote { configureWith( cloudStorageEnabled: nvr.cloudStorageService(channel: channel)?.serviceState.isValid ?? false, supportsAlarm: channel.supportsMessagePush && channel.online && !nvr.isSharedDevice, alarm: channel.messageEnable, noDevice: !nvr.activeChannelsInfo.contains(channel), channelOffline: !channel.online, channelUnAuth: channel.authResult == 1, channelUninitialized: channel.authResult == 3, notInSharePeriod: (nvr.isSharedDevice && !channel.isInSharePeriod), notShareEnable: (nvr.isSharedDevice && !channel.isShareEnable), showHelp: !channel.online && channel.authResult == 0 && channel.active, playButtonHidden: !showPlayButton, size: size, showText: showAccessory, showIcons: showAccessory, hasNoVmsLicense: channel.isLicenseNotActive, isChannel: true ) } else { configureWith( noDevice: !nvr.activeChannelsInfo.contains(channel), channelOffline: !channel.online, channelUnAuth: channel.authResult == 1, channelUninitialized: channel.authResult == 3, showHelp: !channel.online && channel.authResult == 0, playButtonHidden: !showPlayButton, size: size, showText: showAccessory, showIcons: showAccessory, isChannel: true ) } configureCoverWith(size: size, device: nvr, channel: channel, hideMaskIfNeeded: true, isCard: isCard, folded: folded, leadingConst: leadingConst, trailConst: trialConst, isSmall: isSmall, isHideLogo: !isCard && isHideLogo) statusImageViews.forEach { $0.isHidden = true } } func configure(nvr: TPSSDeviceForDeviceList, size: TPSSDeviceForDeviceList.CoverImageSize = .medium, type: CoverType = .play) { let showAccessory = size != .small let showPlayButton = type == .play let showDetailText = showAccessory && size != .medium let channelHidden = nvr.channelsInfo.count == 0 && nvr.allChannelsInfo.count != 0 var noDevice = nvr.activeChannelsInfo.count == 0 && !channelHidden if nvr.isSharedFromOthers { for channel in nvr.activeChannelsInfo { if !channel.isSharedFromOthers { noDevice = true } else { noDevice = false break } } } if nvr.listType == .remote { configureWith( noDevice: noDevice, channelHidden: channelHidden, nvrOffline: !nvr.displayOnline, offlineTime: showDetailText ? nvr.offlineTime : "", showHelp: showAccessory && !nvr.displayOnline && !nvr.isSharedDevice, playButtonHidden: !showPlayButton, size: size, showText: showAccessory, showIcons: showAccessory, ) } else { configureWith( noDevice: noDevice, channelHidden: channelHidden, nvrOffline: !nvr.displayOnline, offlineTime: showDetailText ? nvr.offlineTime : "", showHelp: showAccessory && !nvr.displayOnline && !nvr.isSharedDevice, playButtonHidden: !showPlayButton, size: size, showText: showAccessory, showIcons: showAccessory, ) } configureCoverWith(size: size, device: nvr, channel: nil, hideMaskIfNeeded: true) } func showEmpty(size: TPSSDeviceForDeviceList.CoverImageSize = .medium) { configureWith(playButtonHidden: true, size: size, showText: false, showIcons: false) coverImageView.image = TPSSDeviceForDeviceList.defaultCoverImage(of: size) } } // MARK: - Device Setting Extension extension NewCoverImageContainerView { func configure(device: TPSSDeviceForSetting, channel: TPSSChannelInfo?, size: TPSSDeviceForDeviceList.CoverImageSize = .medium, model: String = “”) { configureWith(playButtonHidden: true, size: size, showText: false, showIcons: false) let channelID = channel?.channelId.intValue ?? -1 let coverImage = device.coverImage(at: channelID, listType: device.deviceListType) coverImageView.image = coverImage ?? device.defaultCoverImage(at: channelID, size: size, model: model) var isFisheye = false if TPSSAppContext.shared.isVmsLogin && device.deviceListType == .remote { isFisheye = coverImage != nil && device.coverImageIsFisheye(at: channelID, listType: device.deviceListType) } else { isFisheye = coverImage != nil && (channel?.supportsFisheye ?? device.isFishEye) } if isFisheye { coverImageView.contentMode = .scaleAspectFill } else if device.deviceType == .solar || coverImage == nil { coverImageView.contentMode = .scaleAspectFit coverImageView.backgroundColor = .tpbProgressBackground coverImageView.layer.cornerRadius = 8 } else { let mode = (coverImage?.size.height ?? 0) > (coverImage?.size.width ?? 0) ? UIView.ContentMode.scaleAspectFit : .scaleAspectFill if let ch = channel, ch.supportCorridor { coverImageView.contentMode = mode coverImageView.backgroundColor = .tpbProgressBackground } else if device.supportCorridor { coverImageView.contentMode = mode coverImageView.backgroundColor = .tpbProgressBackground } else if let ch = channel, ch.isSupportSplicingSetting { coverImageView.contentMode = .scaleAspectFill } else if device.isSupportSplicingSetting { coverImageView.contentMode = .scaleAspectFill } else { coverImageView.contentMode = .scaleToFill } } backgroundColor = isFisheye ? .tpbFisheyeBackground : .clear } }
最新发布
12-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值