html5 input default,HTML5 Input datetime-local default value of today and current time

该博客讨论了如何使用JavaScript和jQuery为HTML5的datetime-local输入字段设置默认值为当前日期和时间。提供了多个简洁的解决方案,包括直接使用JavaScript函数和jQuery扩展方法。这些方法在页面加载时自动填充输入字段,确保显示的是本地时间。

问题

Is there anyway that I can make a default value of HTML5 input type='datetime-local' to today's date and this current time.

Thanks before

回答1:

It's possible. By using a JQuery function, you can have a really complete solution.

Here is an example.

JSFiddle http://jsfiddle.net/v8MNx/1/

HTML

Date:

JQuery:

//Function found here: https://gist.github.com/ryanburnette/8803238

$.fn.setNow = function (onlyBlank) {

var now = new Date($.now())

, year

, month

, date

, hours

, minutes

, seconds

, formattedDateTime

;

year = now.getFullYear();

month = now.getMonth().toString().length === 1 ? '0' + (now.getMonth() + 1).toString() : now.getMonth() + 1;

date = now.getDate().toString().length === 1 ? '0' + (now.getDate()).toString() : now.getDate();

hours = now.getHours().toString().length === 1 ? '0' + now.getHours().toString() : now.getHours();

minutes = now.getMinutes().toString().length === 1 ? '0' + now.getMinutes().toString() : now.getMinutes();

seconds = now.getSeconds().toString().length === 1 ? '0' + now.getSeconds().toString() : now.getSeconds();

formattedDateTime = year + '-' + month + '-' + date + 'T' + hours + ':' + minutes + ':' + seconds;

if ( onlyBlank === true && $(this).val() ) {

return this;

}

$(this).val(formattedDateTime);

return this;

}

$(function () {

// Handler for .ready() called.

$('input[type="datetime"]').setNow();

});

回答2:

The accepted answer seems pretty complicated to me... here a shorter solution that doesn't need jQuery

JSFiddle: https://jsfiddle.net/rzaceg8v/

window.addEventListener("load", function() {

var now = new Date();

var utcString = now.toISOString().substring(0,19);

var year = now.getFullYear();

var month = now.getMonth() + 1;

var day = now.getDate();

var hour = now.getHours();

var minute = now.getMinutes();

var second = now.getSeconds();

var localDatetime = year + "-" +

(month < 10 ? "0" + month.toString() : month) + "-" +

(day < 10 ? "0" + day.toString() : day) + "T" +

(hour < 10 ? "0" + hour.toString() : hour) + ":" +

(minute < 10 ? "0" + minute.toString() : minute) +

utcString.substring(16,19);

var datetimeField = document.getElementById("myDatetimeField");

datetimeField.value = localDatetime;

});

回答3:

You can make it shorter:

window.addEventListener('load', () => {

const now = new Date();

now.setMinutes(now.getMinutes() - now.getTimezoneOffset());

document.getElementById('cal').value = now.toISOString().slice(0, -1);

});

回答4:

The methods above worked but were too verbose for me.

Here's my version:

window.addEventListener("load", function() {

var now = new Date();

var offset = now.getTimezoneOffset() * 60000;

var adjustedDate = new Date(now.getTime() - offset);

var formattedDate = adjustedDate.toISOString().substring(0,16); // For minute precision

var datetimeField = document.getElementById("myDatetimeField");

datetimeField.value = formattedDate;

});

回答5:

This worked perfectly!

One note, I tried this the only month it would break, October. It would give me a '010', instead of 10.

month = (now.getMonth() +1 ).toString().length === 1 ? '0' + (now.getMonth() + 1).toString() : now.getMonth() + 1;

回答6:

Big line works for me, if it doesn't for you you can introduce whitespaces and line breaks in the expressions.

function setDatetimeInput(element, t = new Date()){

function p(number){return number.toString().padStart(2, '0');}//number to 2 digit, 0 padded string

element.value = `${t.getFullYear()}-${p(t.getMonth()+1)}-${p(t.getDate())}T${p(t.getHours())}:${p(t.getMinutes())}`;

}

来源:https://stackoverflow.com/questions/24468518/html5-input-datetime-local-default-value-of-today-and-current-time

import os import time import threading import RPi.GPIO as GPIO import cv2 import numpy as np import pyaudio import vosk import json import serial import pynmea2 import datetime import sqlite3 from pathlib import Path import logging import sys # ------------------- 硬件与模型配置 ------------------- BUZZER_PIN = 18 # BCM模式 FREQ_LOW = 1000 FREQ_HIGH = 2000 BUZZER_DURATION = 60 # 蜂鸣器持续秒数 PIR_PIN = 17 # BCM模式 FACE_CASCADE_PATH = '/home/pi/.local/lib/python3.7/site-packages/cv2/data/haarcascade_frontalface_alt2.xml' PROFILE_CASCADE_PATH = '/home/pi/.local/lib/python3.7/site-packages/cv2/data/haarcascade_profileface.xml' VOSK_MODEL_PATH = "/home/pi/vosk-model-small-cn-0.22" # ------------------- 全局变量 ------------------- pwm = None detection_triggered = False # 检测到人/救命标记 buzzer_active = False # 蜂鸣器运行标记 cap = None # 全局摄像头对象 face_cascade = None # 全局人脸分类器 profile_cascade = None # 全局侧脸分类器 # ------------------- 蜂鸣器控制 ------------------- def setup_buzzer(): global pwm GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(BUZZER_PIN, GPIO.OUT) pwm = GPIO.PWM(BUZZER_PIN, FREQ_LOW) def run_buzzer(): global buzzer_active, detection_triggered buzzer_active = True pwm.start(50) start_time = time.time() while (time.time() - start_time < BUZZER_DURATION) and buzzer_active and not detection_triggered: pwm.ChangeFrequency(FREQ_LOW) time.sleep(0.5) pwm.ChangeFrequency(FREQ_HIGH) time.sleep(0.5) pwm.stop() buzzer_active = False # ------------------- PIR + 人脸检测 ------------------- def setup_pir(): GPIO.setup(PIR_PIN, GPIO.IN) def setup_camera_and_cascades(): global cap, face_cascade, profile_cascade cap = cv2.VideoCapture(0) if not cap.isOpened(): print("错误:摄像头无法打开!") return False cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240) face_cascade = cv2.CascadeClassifier(FACE_CASCADE_PATH) profile_cascade = cv2.CascadeClassifier(PROFILE_CASCADE_PATH) if face_cascade.empty() or profile_cascade.empty(): print("错误:人脸分类器加载失败!") return False print("摄像头+分类器初始化完成") return True def detect_human(): global cap, face_cascade, profile_cascade if not cap or not cap.isOpened(): return False ret, img = cap.read() if not ret: return False img = cv2.flip(img, 1) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30)) profiles = profile_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30)) if len(faces) > 0 or len(profiles) > 0: print(f"检测到:正脸{len(faces)}个,侧脸{len(profiles)}个") return len(faces) > 0 or len(profiles) > 0 def monitor_pir(): global detection_triggered, buzzer_active print("PIR监控线程启动") while buzzer_active and not detection_triggered: current_val = GPIO.input(PIR_PIN) if current_val == 1: print("PIR检测到活动,持续人脸检测...") if detect_human(): detection_triggered = True print("1(检测到人体)") break time.sleep(0.5) print("PIR监控线程退出") # ------------------- Vosk 人声识别 ------------------- def setup_voice_recognition(): if not os.path.exists(VOSK_MODEL_PATH): print("错误:Vosk模型目录不存在!") return None, None, None model = vosk.Model(VOSK_MODEL_PATH) recognizer = vosk.KaldiRecognizer(model, 16000) p = pyaudio.PyAudio() stream = p.open( format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=4000, input_device_index=None ) return recognizer, stream, p def monitor_voice(recognizer, stream): global detection_triggered, buzzer_active while buzzer_active and not detection_triggered: data = stream.read(4000, exception_on_overflow=False) if recognizer.AcceptWaveform(data): result = json.loads(recognizer.Result()) text = result.get("text", "").strip() clean_text = text.replace(" ", "").replace("!", "").replace(",", "") if "救命" in clean_text: detection_triggered = True print("1(人声识别到'救命')") break # ------------------- 资源释放 ------------------- def cleanup(): global buzzer_active, detection_triggered, cap buzzer_active = False detection_triggered = False if cap and cap.isOpened(): cap.release() print("摄像头已释放") if pwm: pwm.stop() GPIO.cleanup() cv2.destroyAllWindows() if 'stream' in globals() and 'p_audio' in globals(): stream.stop_stream() stream.close() p_audio.terminate() print("程序已停止,资源已释放") # ------------------- GPS 功能 ------------------- def check_dialout_permission(): try: groups = os.popen('groups').read().strip() if 'dialout' in groups: return True else: logging.warning("当前用户不在dialout组中,可能无法访问串口") logging.warning("请运行: sudo usermod -aG dialout $USER,然后重新登录") return False except Exception as e: logging.warning(f"检查权限时出错: {e}") return False class ATK1218GPS: def __init__(self, port='/dev/ttyS0', baudrate=9600, timeout=2): self.port = port self.baudrate = baudrate self.timeout = timeout self.ser = None self.db_conn = None self.init_database() logging.info(f"配置为使用 {self.port} 串口") def connect(self): try: self.ser = serial.Serial( self.port, self.baudrate, timeout=self.timeout, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, xonxoff=False, rtscts=False ) if self.ser.is_open: logging.info(f"成功连接到GPS模块: {self.port}") return True else: logging.error(f"无法打开 {self.port} 端口") return False except PermissionError: logging.error(f"权限不足,无法访问 {self.port}") logging.error("请确保用户已加入dialout组并重新登录") return False except Exception as e: logging.error(f"连接GPS模块失败: {e}") return False def init_database(self): try: self.db_conn = sqlite3.connect("gps_data/location.db") cursor = self.db_conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME, latitude REAL, longitude REAL, altitude REAL, satellites INTEGER, speed REAL, course REAL, fix_quality INTEGER ) ''') cursor.execute("PRAGMA table_info(locations)") columns = [column[1] for column in cursor.fetchall()] if 'speed' not in columns: cursor.execute("ALTER TABLE locations ADD COLUMN speed REAL") logging.info("已添加speed列到数据库表") if 'course' not in columns: cursor.execute("ALTER TABLE locations ADD COLUMN course REAL") logging.info("已添加course列到数据库表") self.db_conn.commit() logging.info("数据库初始化成功") except Exception as e: logging.error(f"数据库初始化失败: {e}") def parse_nmea_sentence(self, sentence): try: if not sentence.startswith('$'): return None msg = pynmea2.parse(sentence) if isinstance(msg, pynmea2.GGA): data = { 'timestamp': datetime.datetime.now(), 'latitude': self.convert_to_decimal(msg.lat, msg.lat_dir), 'longitude': self.convert_to_decimal(msg.lon, msg.lon_dir), 'altitude': float(msg.altitude) if msg.altitude else None, 'satellites': int(msg.num_sats) if msg.num_sats else None, 'speed': None, 'course': None, 'fix_quality': int(msg.gps_qual) if msg.gps_qual else 0 } return data elif isinstance(msg, pynmea2.RMC): data = { 'timestamp': datetime.datetime.now(), 'latitude': self.convert_to_decimal(msg.lat, msg.lat_dir), 'longitude': self.convert_to_decimal(msg.lon, msg.lon_dir), 'altitude': None, 'satellites': None, 'speed': float(msg.spd_over_grnd) if msg.spd_over_grnd else None, 'course': float(msg.true_course) if msg.true_course else None, 'fix_quality': 1 if msg.status == 'A' else 0 } return data return None except pynmea2.ParseError: return None except Exception as e: logging.warning(f"解析NMEA数据出错: {e}, 数据: {sentence}") return None def convert_to_decimal(self, value, direction): if not value: return None try: val = float(value) degrees = int(val // 100) minutes = val % 100 decimal_degrees = degrees + minutes / 60 if direction in ['S', 'W']: decimal_degrees = -decimal_degrees return round(decimal_degrees, 6) except: return None def save_location(seimport os import time import threading import RPi.GPIO as GPIO import cv2 import numpy as np import pyaudio import vosk import json import serial import pynmea2 import datetime import sqlite3 from pathlib import Path import logging import sys # ------------------- 硬件与模型配置 ------------------- BUZZER_PIN = 18 # BCM模式 FREQ_LOW = 1000 FREQ_HIGH = 2000 BUZZER_DURATION = 60 # 蜂鸣器持续秒数 PIR_PIN = 17 # BCM模式 FACE_CASCADE_PATH = '/home/pi/.local/lib/python3.7/site-packages/cv2/data/haarcascade_frontalface_alt2.xml' PROFILE_CASCADE_PATH = '/home/pi/.local/lib/python3.7/site-packages/cv2/data/haarcascade_profileface.xml' VOSK_MODEL_PATH = "/home/pi/vosk-model-small-cn-0.22" # ------------------- 全局变量 ------------------- pwm = None detection_triggered = False # 检测到人/救命标记 buzzer_active = False # 蜂鸣器运行标记 cap = None # 全局摄像头对象 face_cascade = None # 全局人脸分类器 profile_cascade = None # 全局侧脸分类器 # ------------------- 蜂鸣器控制 ------------------- def setup_buzzer(): global pwm GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(BUZZER_PIN, GPIO.OUT) pwm = GPIO.PWM(BUZZER_PIN, FREQ_LOW) def run_buzzer(): global buzzer_active, detection_triggered buzzer_active = True pwm.start(50) start_time = time.time() while (time.time() - start_time < BUZZER_DURATION) and buzzer_active and not detection_triggered: pwm.ChangeFrequency(FREQ_LOW) time.sleep(0.5) pwm.ChangeFrequency(FREQ_HIGH) time.sleep(0.5) pwm.stop() buzzer_active = False # ------------------- PIR + 人脸检测 ------------------- def setup_pir(): GPIO.setup(PIR_PIN, GPIO.IN) def setup_camera_and_cascades(): global cap, face_cascade, profile_cascade cap = cv2.VideoCapture(0) if not cap.isOpened(): print("错误:摄像头无法打开!") return False cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240) face_cascade = cv2.CascadeClassifier(FACE_CASCADE_PATH) profile_cascade = cv2.CascadeClassifier(PROFILE_CASCADE_PATH) if face_cascade.empty() or profile_cascade.empty(): print("错误:人脸分类器加载失败!") return False print("摄像头+分类器初始化完成") return True def detect_human(): global cap, face_cascade, profile_cascade if not cap or not cap.isOpened(): return False ret, img = cap.read() if not ret: return False img = cv2.flip(img, 1) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30)) profiles = profile_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30)) if len(faces) > 0 or len(profiles) > 0: print(f"检测到:正脸{len(faces)}个,侧脸{len(profiles)}个") return len(faces) > 0 or len(profiles) > 0 def monitor_pir(): global detection_triggered, buzzer_active print("PIR监控线程启动") while buzzer_active and not detection_triggered: current_val = GPIO.input(PIR_PIN) if current_val == 1: print("PIR检测到活动,持续人脸检测...") if detect_human(): detection_triggered = True print("1(检测到人体)") break time.sleep(0.5) print("PIR监控线程退出") # ------------------- Vosk 人声识别 ------------------- def setup_voice_recognition(): if not os.path.exists(VOSK_MODEL_PATH): print("错误:Vosk模型目录不存在!") return None, None, None model = vosk.Model(VOSK_MODEL_PATH) recognizer = vosk.KaldiRecognizer(model, 16000) p = pyaudio.PyAudio() stream = p.open( format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=4000, input_device_index=None ) return recognizer, stream, p def monitor_voice(recognizer, stream): global detection_triggered, buzzer_active while buzzer_active and not detection_triggered: data = stream.read(4000, exception_on_overflow=False) if recognizer.AcceptWaveform(data): result = json.loads(recognizer.Result()) text = result.get("text", "").strip() clean_text = text.replace(" ", "").replace("!", "").replace(",", "") if "救命" in clean_text: detection_triggered = True print("1(人声识别到'救命')") break # ------------------- 资源释放 ------------------- def cleanup(): global buzzer_active, detection_triggered, cap buzzer_active = False detection_triggered = False if cap and cap.isOpened(): cap.release() print("摄像头已释放") if pwm: pwm.stop() GPIO.cleanup() cv2.destroyAllWindows() if 'stream' in globals() and 'p_audio' in globals(): stream.stop_stream() stream.close() p_audio.terminate() print("程序已停止,资源已释放") # ------------------- GPS 功能 ------------------- def check_dialout_permission(): try: groups = os.popen('groups').read().strip() if 'dialout' in groups: return True else: logging.warning("当前用户不在dialout组中,可能无法访问串口") logging.warning("请运行: sudo usermod -aG dialout $USER,然后重新登录") return False except Exception as e: logging.warning(f"检查权限时出错: {e}") return False class ATK1218GPS: def __init__(self, port='/dev/ttyS0', baudrate=9600, timeout=2): self.port = port self.baudrate = baudrate self.timeout = timeout self.ser = None self.db_conn = None self.init_database() logging.info(f"配置为使用 {self.port} 串口") def connect(self): try: self.ser = serial.Serial( self.port, self.baudrate, timeout=self.timeout, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, xonxoff=False, rtscts=False ) if self.ser.is_open: logging.info(f"成功连接到GPS模块: {self.port}") return True else: logging.error(f"无法打开 {self.port} 端口") return False except PermissionError: logging.error(f"权限不足,无法访问 {self.port}") logging.error("请确保用户已加入dialout组并重新登录") return False except Exception as e: logging.error(f"连接GPS模块失败: {e}") return False def init_database(self): try: self.db_conn = sqlite3.connect("gps_data/location.db") cursor = self.db_conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME, latitude REAL, longitude REAL, altitude REAL, satellites INTEGER, speed REAL, course REAL, fix_quality INTEGER ) ''') cursor.execute("PRAGMA table_info(locations)") columns = [column[1] for column in cursor.fetchall()] if 'speed' not in columns: cursor.execute("ALTER TABLE locations ADD COLUMN speed REAL") logging.info("已添加speed列到数据库表") if 'course' not in columns: cursor.execute("ALTER TABLE locations ADD COLUMN course REAL") logging.info("已添加course列到数据库表") self.db_conn.commit() logging.info("数据库初始化成功") except Exception as e: logging.error(f"数据库初始化失败: {e}") def parse_nmea_sentence(self, sentence): try: if not sentence.startswith('$'): return None msg = pynmea2.parse(sentence) if isinstance(msg, pynmea2.GGA): data = { 'timestamp': datetime.datetime.now(), 'latitude': self.convert_to_decimal(msg.lat, msg.lat_dir), 'longitude': self.convert_to_decimal(msg.lon, msg.lon_dir), 'altitude': float(msg.altitude) if msg.altitude else None, 'satellites': int(msg.num_sats) if msg.num_sats else None, 'speed': None, 'course': None, 'fix_quality': int(msg.gps_qual) if msg.gps_qual else 0 } return data elif isinstance(msg, pynmea2.RMC): data = { 'timestamp': datetime.datetime.now(), 'latitude': self.convert_to_decimal(msg.lat, msg.lat_dir), 'longitude': self.convert_to_decimal(msg.lon, msg.lon_dir), 'altitude': None, 'satellites': None, 'speed': float(msg.spd_over_grnd) if msg.spd_over_grnd else None, 'course': float(msg.true_course) if msg.true_course else None, 'fix_quality': 1 if msg.status == 'A' else 0 } return data return None except pynmea2.ParseError: return None except Exception as e: logging.warning(f"解析NMEA数据出错: {e}, 数据: {sentence}") return None def convert_to_decimal(self, value, direction): if not value: return None try: val = float(value) degrees = int(val // 100) minutes = val % 100 decimal_degrees = degrees + minutes / 60 if direction in ['S', 'W']: decimal_degrees = -decimal_degrees return round(decimal_degrees, 6) except: return None def save_location(self, location_data): if not location_data or not self.db_conn: return False try: cursor = self.db_conn.cursor() cursor.execute(''' INSERT INTO locations (timestamp, latitude, longitude, altitude, satellites, speed, course, fix_quality) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', ( location_data['timestamp'], location_data['latitude'], location_data['longitude'], location_data['altitude'], location_data['satellites'], location_data['speed'], location_data['course'], location_data['fix_quality'] )) self.db_conn.commit() with open("gps_data/latest_location.txt", "w") as f: f.write(f"时间: {location_data['timestamp']}\n") f.write(f"纬度: {location_data['latitude']}\n") f.write(f"经度: {location_data['longitude']}\n") if location_data['altitude']: f.write(f"海拔: {location_data['altitude']} 米\n") if location_data['speed']: f.write(f"速度: {location_data['speed']} 节\n") f.write(f"航向: {location_data['course']} 度\n") if location_data['satellites']: f.write(f"卫星数量: {location_data['satellites']}\n") f.write(f"定位质量: {location_data['fix_quality']}\n") return True except Exception as e: logging.error(f"保存定位信息失败: {e}") return False def get_location(self, timeout=30): """获取有效位置信息,超时返回None""" if not self.ser or not self.ser.is_open: logging.warning("请先连接GPS模块") return None start_time = time.time() logging.info("开始获取GPS定位...") try: while time.time() - start_time < timeout: data = self.ser.readline().decode('utf-8', errors='ignore').strip() if data: location_data = self.parse_nmea_sentence(data) if location_data and location_data.get('fix_quality', 0) > 0: logging.info(f"成功获取定位: {location_data}") self.save_location(location_data) return location_data time.sleep(0.1) logging.warning("获取GPS定位超时") return None except Exception as e: logging.error(f"获取定位数据出错: {e}") return None def close(self): if self.ser and self.ser.is_open: self.ser.close() logging.info("已关闭GPS模块连接") if self.db_conn: self.db_conn.close() logging.info("已关闭数据库连接") # ------------------- LORA 功能 ------------------- class LoRaReceiver: def __init__(self, port='/dev/ttyACM0', baudrate=9600, timeout=0.1): self.port = port self.baaudrate = baudrate self.timeout = timeout self.ser = None self.running = False def open(self): try: self.ser = serial.Serial(self.port, self.baudrate, timeout=self.timeout) print(f"成功打开串口 {self.ser.name}") self.running = True return True except Exception as e: print(f"打开串口失败:{e}") return False def receive_data(self): if not self.ser or not self.ser.is_open: print("串口未打开,请先调用open()方法") return None if self.ser.in_waiting > 0: try: data = self.ser.read(self.ser.in_waiting).decode('utf-8', errors='ignore') timestamp = time.strftime('%H:%M:%S') return { 'timestamp': timestamp, 'data': data.strip() } except Exception as e: print(f"接收数据错误:{e}") return None return None def close(self): if self.ser and self.ser.is_open: self.ser.close() self.running = False print("串口已关闭") def start_listening(self, callback=None): if not self.open(): return try: print("开始监听LoRa数据... (按Ctrl+C停止)") while self.running: data = self.receive_data() if data: print(f"[{data['timestamp']}] 收到LoRa数据:{data['data']}") if callback and callable(callback): callback(data) time.sleep(0.1) except KeyboardInterrupt: print("\n用户中断监听") finally: self.close() class LoRaSender: def __init__(self, port='/dev/ttyACM0', baud_rate=9600, timeout=1): self.port = port self.baud_rate = baud_rate self.timeout = timeout self.ser = None def open(self): try: self.ser = serial.Serial(self.port, self.baud_rate, timeout=self.timeout) print(f"成功打开串口 {self.ser.name}") return True except Exception as e: print(f"打开串口失败:{e}") return False def send_message(self, message): if not self.ser or not self.ser.is_open: print("串口未打开,请先调用open()方法") return False if not message: print("消息为空,不发送") return False try: self.ser.write(message.encode('utf-8')) print(f"✅ 已发送:{message}") return True except Exception as e: print(f"发送失败:{e}") return False def close(self): if self.ser and self.ser.is_open: self.ser.close() print("串口已关闭") def send_and_close(self, message): try: if self.open(): self.send_message(message) time.sleep(0.1) finally: self.close() # ------------------- 主函数 ------------------- def main(): # 确保数据目录存在 data_dir = Path("gps_data") data_dir.mkdir(exist_ok=True) # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("gps_data/gps_log.log"), logging.StreamHandler() ] ) # 检查权限 if not check_dialout_permission(): logging.warning("串口访问权限可能存在问题") # 初始化LoRa接收器 lora_receiver = LoRaReceiver(port='/dev/ttyACM0') # 等待接收LoRa信号 print("等待接收LoRa信号...") lora_receiver.open() try: while True: data = lora_receiver.receive_data() if data: print(f"接收到LoRa信号: {data['data']}") # 如果接收到信号,启动检测流程 start_detection() # 检测完成后退出循环 break time.sleep(1) except KeyboardInterrupt: print("\n用户中断程序") finally: lora_receiver.close() cleanup() def start_detection(): global detection_triggered, buzzer_active # 初始化硬件 setup_buzzer() setup_pir() if not setup_camera_and_cascades(): cleanup() return recognizer, stream, p_audio = setup_voice_recognition() if not recognizer or not stream or not p_audio: cleanup() return # 启动状态 detection_triggered = False buzzer_active = True # 启动线程 buzzer_thread = threading.Thread(target=run_buzzer, daemon=True) pir_thread = threading.Thread(target=monitor_pir, daemon=True) voice_thread = threading.Thread(target=monitor_voice, args=(recognizer, stream), daemon=True) buzzer_thread.start() pir_thread.start() voice_thread.start() # 主线程等待 while buzzer_active and not detection_triggered: time.sleep(1) # 如果检测到人,获取GPS位置并通过LoRa发送 if detection_triggered: # 获取GPS位置 gps = ATK1218GPS(port='/dev/ttyAMA0', baudrate=38400) if gps.connect(): location_data = gps.get_location(timeout=30) gps.close() if location_data: # 格式化位置信息 location_str = f"紧急位置: 纬度 {location_data['latitude']}, 经度 {location_data['longitude']}" if location_data['altitude']: location_str += f", 海拔 {location_data['altitude']}米" # 通过LoRa发送位置信息 lora_sender = LoRaSender(port='/dev/ttyACM0') lora_sender.send_and_close(location_str) else: logging.error("无法获取GPS定位信息") else: logging.error("无法连接GPS模块") # 清理资源 cleanup() if __name__ == '__main__': try: main() except Exception as e: logging.error(f"程序执行出错: {e}") cleanup() sys.exit(1)我把多个入口程序合并成一个主函数入口,按照我的逻辑来写主函数的代码,首先需要用接收函数读出接收到的信息,如果接收到了这个信息以后那么进行原来靠前的_main_块(直接启动就行,不需要再让用户输入1),如果检测到有人就进行GPS的定位,然后读取出GPS的信息以后在通过LORA模块发送函数,把这个信息发送出去。你看我发给你的代码能不能满足我的要求,有什么缺陷,可以订正了一个给我完整
09-11
import streamlit as st import pyodbc import pandas as pd import time from datetime import datetime, timedelta import os import sys import plotly.graph_objects as go import plotly.express as px # 页面配置 - 设置更宽的布局和标题 st.set_page_config( page_title="KUSO", layout="wide", page_icon="📊", initial_sidebar_state="expanded" ) # 配置数据库连接参数 server = 'svcntpj02.leoni.local' database = 'LEP_FAAR_CN' username = 'sqlfacility' password = '52s0nszj' connection_string = f'DRIVER={{SQL Server}};SERVER={server};DATABASE={database};UID={username};PWD={password}' # Excel文件路径 excel_file_path = r'\\SVCNTFILE01\infos\Public\08 FST\2.Production plan\JIS\JIS Production Plan 2018.xlsx' # 班次时间常量 SHIFT_START = "08:00:00" SHIFT_C1 = "20:00:00" SHIFT_C2 = "08:00:00" # 黑色主题CSS样式 - 更新表格样式 st.markdown(""" <style> /* 全局黑色背景 */ .stApp { background-color: #000000; color: #ffffff; } /* 主标题样式 */ .main-title { font-size: 3rem; color: #00b4d8; text-align: center; margin-bottom: 30px; font-weight: 700; text-shadow: 0 0 10px rgba(0, 180, 216, 0.7); } /* KOSU表格样式 - 更新列宽 */ .kosu-table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 1.1rem; table-layout: fixed; } .kosu-table th { background-color: #1a1a1a; color: #00b4d8; padding: 12px 15px; text-align: center; border: 1px solid #333; font-weight: bold; } .kosu-table td { padding: 12px 15px; text-align: center; border: 1px solid #333; background-color: #1a1a1a; } .kosu-table tr:hover td { background-color: #2a2a2a; } /* 表头行样式 */ .table-header { background-color: #004d66 !important; color: white !important; font-size: 1.2rem; font-weight: bold; } /* 高完成率样式 */ .high-productivity { color: #00ff00; font-weight: bold; } /* 低完成率样式 */ .low-productivity { color: #ff3333; font-weight: bold; } /* 中等完成率样式 */ .medium-productivity { color: #ffa500; font-weight: bold; } /* 侧边栏样式 */ .css-1d391kg, .css-1d391kg button { background-color: #121212; color: #ffffff; } /* 按钮样式 */ .stButton button { background-color: #00b4d8; color: #000000; border-radius: 6px; border: none; padding: 10px 24px; font-weight: 500; transition: all 0.3s; } .stButton button:hover { background-color: #0096c7; box-shadow: 0 0 10px rgba(0, 180, 216, 0.5); } /* 其他样式 */ .stSlider { color: #00b4d8; } .stAlert { border-radius: 10px; background-color: #1a1a1a; border: 1px solid #333; } .update-time { font-size: 0.9rem; color: #888; text-align: right; font-style: italic; } .stSpinner > div { text-align: center; color: #00b4d8; } /* 下拉选择器样式 */ .stSelectbox > div > div { background-color: #1a1a1a; color: white; border: 1px solid #333; } .stSelectbox label { color: #00b4d8; font-weight: bold; } /* 时间显示样式 */ .time-display { font-size: 1.5rem; color: #00b4d8; text-align: center; margin-bottom: 20px; font-weight: bold; } /* 日期显示样式 - 更新字体和大小 */ .date-cell { color: #00b4d8; font-weight: bold; font-size: 4.5rem; /* 72px 约等于 4.5rem */ font-family: SimHei, "黑体", sans-serif; /* 黑体字体 */ line-height: 1.2; } /* 数字和百分比样式 - 更新字体和大小 */ .number-cell { font-size: 4.5rem; /* 72px 约等于 4.5rem */ font-family: SimSun, "宋体", serif; /* 宋体字体 */ line-height: 1.2; } /* 饼图容器样式 */ .pie-chart-container { background-color: #001f3f; border-radius: 10px; padding: 15px; margin: 10px 0; border: 1px solid #00b4d8; } /* 设置特定列宽 */ .col-line { width: 65.38mm !important; } .col-order { width: 36.38mm !important; } .col-trend { width: 34mm !important; } .col-produced { width: 36mm !important; } .col-productivity { width: 55mm !important; } .col-rework { width: 26.5mm !important; } .col-diff { width: 30.63mm !important; } </style> """, unsafe_allow_html=True) # 应用标题 st.markdown('<div class="main-title">KOSU 生产监控系统</div>', unsafe_allow_html=True) # 计算完成率函数 def calculate_productivity(completed, planned): if planned == 0: return 0.00 return (completed / planned) * 100 # 根据完成率获取CSS类名 def get_productivity_class(percentage): if percentage >= 80: return "high-productivity" elif percentage >= 50: return "medium-productivity" else: return "low-productivity" # 从Excel文件读取数据的函数 - 扩展读取所有必要数据 @st.cache_data(ttl=300, show_spinner="正在从Excel获取数据...") def get_excel_data(): try: # 读取Capacity工作表数据 df_capacity = pd.read_excel( excel_file_path, sheet_name='Capacity', header=None ) # 读取"请勿修改"工作表数据 df_no_modify = pd.read_excel( excel_file_path, sheet_name='请勿修改', header=None ) # 获取计划订单数据 (F列) planned_data = { 'ASS30': int(df_capacity.iloc[4, 5]) if not pd.isna(df_capacity.iloc[4, 5]) else 0, # F5 'ASS31': int(df_capacity.iloc[5, 5]) if not pd.isna(df_capacity.iloc[5, 5]) else 0, # F6 'ASS32': int(df_capacity.iloc[6, 5]) if not pd.isna(df_capacity.iloc[6, 5]) else 0 # F7 } # 获取工作时间数据 (G列) work_start_times = { 'ASS30': df_capacity.iloc[4, 6] if not pd.isna(df_capacity.iloc[4, 6]) else "08:00:00", # G5 'ASS31': df_capacity.iloc[5, 6] if not pd.isna(df_capacity.iloc[5, 6]) else "08:00:00", # G6 'ASS32': df_capacity.iloc[6, 6] if not pd.isna(df_capacity.iloc[6, 6]) else "08:00:00" # G7 } # 获取效率数据 (N列) - 修复了缺少逗号的问题 efficiency_data = { 'ASS30': float(df_capacity.iloc[4, 13]) if not pd.isna(df_capacity.iloc[4, 13]) else 13.47, # N5 'ASS31': float(df_capacity.iloc[5, 13]) if not pd.isna(df_capacity.iloc[5, 13]) else 13.47, # N6 'ASS32': float(df_capacity.iloc[6, 13]) if not pd.isna(df_capacity.iloc[6, 13]) else 13.47 # N7 } # 获取午餐开始时间 (请勿修改工作表的F3, F4, F5) lunch_start_times = { 'ASS30': df_no_modify.iloc[2, 5] if not pd.isna(df_no_modify.iloc[2, 5]) else "12:00:00", # F3 'ASS31': df_no_modify.iloc[3, 5] if not pd.isna(df_no_modify.iloc[3, 5]) else "12:00:00", # F4 'ASS32': df_no_modify.iloc[4, 5] if not pd.isna(df_no_modify.iloc[4, 5]) else "12:00:00" # F5 } return planned_data, work_start_times, efficiency_data, lunch_start_times except Exception as e: st.error(f"读取Excel文件失败: {str(e)}") # 返回默认数据 planned_data = {'ASS30': 0, 'ASS31': 0, 'ASS32': 0} work_start_times = {'ASS30': "08:00:00", 'ASS31': "08:00:00", 'ASS32': "08:00:00"} efficiency_data = {'ASS30': 13.47, 'ASS31': 13.47, 'ASS32': 13.47} lunch_start_times = {'ASS30': "12:00:00", 'ASS31': "12:00:00", 'ASS32': "12:00:00"} return planned_data, work_start_times, efficiency_data, lunch_start_times # 计算Trend(应完成)的逻辑函数 def calculate_trend_value(current_time, work_start_time_str, lunch_start_time_str, efficiency): """ 计算Trend(应完成)值,考虑午餐休息时间 Args: current_time: 当前时间 (datetime对象) work_start_time_str: 工作开始时间字符串 (格式: "HH:MM:SS") lunch_start_time_str: 午餐开始时间字符串 (格式: "HH:MM:SS") efficiency: 效率 (每小时完成数量) Returns: int: 应完成数量 """ try: # 获取当前日期 current_date = current_time.date() # 转换为完整的时间字符串 work_start_full = f"{current_date} {work_start_time_str}" lunch_start_full = f"{current_date} {lunch_start_time_str}" # 转换为datetime对象 work_start = datetime.strptime(work_start_full, "%Y-%m-%d %H:%M:%S") lunch_start = datetime.strptime(lunch_start_full, "%Y-%m-%d %H:%M:%S") # 计算时间差(分钟) minutes_to_start = (current_time - work_start).total_seconds() / 60 minutes_to_lunch = (current_time - lunch_start).total_seconds() / 60 minutes_lunch_to_start = (lunch_start - work_start).total_seconds() / 60 # 午餐休息逻辑判断 if minutes_to_start < 0: # 当前时间在工作开始时间之前 worktime_hours = 0 elif minutes_to_lunch > 0 or minutes_lunch_to_start > 0: # 条件1: 当前时间在午餐开始时间之前 或 午餐开始时间在工作开始时间之后 worktime_hours = minutes_to_start / 60 elif abs(minutes_to_lunch) < 30: # 条件2: 当前时间在午餐开始后的30分钟内(午餐休息期间) worktime_hours = (lunch_start - work_start).total_seconds() / 3600 else: # 条件3: 午餐休息结束后 worktime_hours = minutes_to_start / 60 - 0.5 # 减去30分钟午餐时间 # 计算应完成数量 = 工作时间(小时) &times; 效率(每小时数量) trend_value = int(worktime_hours * efficiency) return max(0, trend_value) # 确保不为负数 except Exception as e: print(f"计算Trend值错误: {e}") return 0 # 处理跨班次的时间逻辑 def get_shift_adjusted_time(current_time, work_start_time, lunch_start_time, efficiency, line_name): """ 处理跨班次的时间调整逻辑 Args: current_time: 当前时间 work_start_time: 工作开始时间字符串 lunch_start_time: 午餐开始时间字符串 efficiency: 效率 line_name: 生产线名称 Returns: int: 调整后的Trend值 """ # 班次判断逻辑 shift_start_time = datetime.strptime(f"{current_time.date()} {SHIFT_START}", "%Y-%m-%d %H:%M:%S") shift_c1_time = datetime.strptime(f"{current_time.date()} {SHIFT_C1}", "%Y-%m-%d %H:%M:%S") shift_c2_time = datetime.strptime(f"{current_time.date()} {SHIFT_C2}", "%Y-%m-%d %H:%M:%S") if shift_start_time <= current_time <= shift_c1_time: # 白班 - 直接计算 return calculate_trend_value(current_time, work_start_time, lunch_start_time, efficiency) elif current_time > shift_c2_time and current_time < shift_start_time: # 不工作时间段 return 0 elif current_time.hour <= int(SHIFT_C2.split(':')[0]): # 夜班(跨天情况)- 使用前一天的时间 prev_day = current_time.date() - timedelta(days=1) prev_work_start = f"{prev_day} {work_start_time}" prev_lunch_start = f"{prev_day} {lunch_start_time}" # 计算到夜班结束的时间 night_end = datetime.strptime(f"{current_time.date()} {SHIFT_C2}", "%Y-%m-%d %H:%M:%S") trend_at_night_end = calculate_trend_value(night_end, work_start_time, lunch_start_time, efficiency) return trend_at_night_end else: # 正常夜班 return calculate_trend_value(current_time, work_start_time, lunch_start_time, efficiency) # 优化查询函数 def fetch_data(): try: # 建立数据库连接 conn = pyodbc.connect(connection_string) st.sidebar.success("数据库连接成功!", icon="✅") # 获取今天的日期 today = datetime.now().strftime('%Y-%m-%d') tomorrow = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d') # 修改查询语句以获取更详细的数据,使用今天的日期 query = f""" SELECT A.[MachineName], B.ResultName, COUNT(DISTINCT a.[Identifier]) AS Ruilin FROM [LEP_FAAR_CN].[TRK_Item].[_ProcessLog_1] A INNER JOIN [LEP_FAAR_CN].[TRK_Item].[_ProcessLog_1] B ON A.Identifier = B.Identifier WHERE A.StepName = 'MA031' AND A.Completed >= '{today} 00:00:00' AND A.Completed < '{tomorrow} 00:00:00' AND B.ResultName IN ('OK', 'Rework') GROUP BY A.MachineName, B.ResultName """ # 使用您提供的批量处理方法 cursor = conn.cursor() cursor.execute(query) # 获取列名 columns = [column[0] for column in cursor.description] # 分批获取数据 data = [] while True: batch = cursor.fetchmany(5000) if not batch: break data.extend(batch) # 转为DataFrame df = pd.DataFrame.from_records(data, columns=columns) return df except pyodbc.Error as e: error_msg = f"数据库连接失败: {str(e)}" st.sidebar.error(error_msg, icon="🚨") return pd.DataFrame() except Exception as e: st.error(f"查询错误: {str(e)}") return pd.DataFrame() # 处理数据库数据,提取各ASS的OK和Rework数量 def process_db_data(df): # 初始化结果字典 result = { 'ASS30': {'OK': 0, 'Rework': 0}, 'ASS31': {'OK': 0, 'Rework': 0}, 'ASS32': {'OK': 0, 'Rework': 0} } if not df.empty: for _, row in df.iterrows(): machine = row['MachineName'] result_type = row['ResultName'] ruilin = row['Ruilin'] # 标准化机器名称(去除空格,转换为大写) machine_clean = machine.strip().upper() # 标准化结果类型(处理大小写问题) result_type_clean = result_type.strip().upper() # 检查是否是有效的机器名 if machine_clean in result: # 处理OK/Ok大小写问题 if result_type_clean == 'OK': result[machine_clean]['OK'] = ruilin elif result_type_clean == 'REWORK': result[machine_clean]['Rework'] = ruilin return result # 创建饼状图函数 def create_pie_chart(ass_name, planned, completed, productivity): # 计算剩余数量 remaining = max(0, planned - completed) # 创建饼图数据 labels = ['已完成', '剩余'] values = [completed, remaining] # 创建饼图 fig = go.Figure() # 添加环形图 fig.add_trace(go.Pie( labels=labels, values=values, hole=0.6, # 空心饼图 marker=dict( colors=['#00b4d8', 'rgba(255, 165, 0, 0.3)'], # 蓝色已完成,橙色空心部分 line=dict(color='#ffa500', width=2) # 橙色边框 ), textinfo='none', # 不显示文本标签 hoverinfo='label+percent' )) # 更新布局 fig.update_layout( title={ 'text': f'{ass_name} - 完成率: {productivity:.1f}%', 'x': 0.5, 'xanchor': 'center', 'font': {'size': 16, 'color': '#00b4d8'} }, showlegend=True, legend=dict( orientation="h", yanchor="bottom", y=-0.2, xanchor="center", x=0.5, font=dict(color='white') ), paper_bgcolor='#001f3f', # 科技感蓝色背景 plot_bgcolor='#001f3f', font=dict(color='white'), height=300, margin=dict(l=20, r=20, t=50, b=50) ) # 在中心添加文本 fig.add_annotation( text=f"{productivity:.1f}%", x=0.5, y=0.5, xref="paper", yref="paper", showarrow=False, font=dict(size=20, color='#00b4d8') ) return fig # 侧边栏配置 with st.sidebar: st.header("控制面板") # 添加页面选择下拉框 page_option = st.selectbox( "选择页面", ["四面大屏", "新项目1", "新项目2"], help="选择要查看的监控页面" ) # 只在四面大屏页面显示刷新率控制 if page_option == "四面大屏": refresh_rate = st.slider("刷新频率(秒)", 1, 300, 60, help="设置数据自动刷新的时间间隔") st.divider() # 添加日期选择器 selected_date = st.date_input("选择日期", datetime.now()) st.divider() # 添加导出功能 if st.button('导出当前数据', key="export_btn"): df = fetch_data() if not df.empty: csv = df.to_csv(index=False).encode('utf-8-sig') st.download_button( label="下载CSV", data=csv, file_name=f"返工数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", mime='text/csv', key="download_btn" ) else: st.warning("无数据可导出") # 根据选择的页面显示不同内容 if page_option == "四面大屏": # 主展示区 placeholder = st.empty() while True: # 获取Excel数据 planned_data, work_start_times, efficiency_data, lunch_start_times = get_excel_data() with placeholder.container(): # 获取当前日期和时间 current_date = datetime.now().strftime("%Y-%m-%d") current_time = datetime.now().strftime("%H:%M") # 只显示时间,不显示日期 current_datetime = datetime.now() # 完整的当前时间 st.markdown(f'<div class="time-display">{current_time}</div>', unsafe_allow_html=True) # 获取数据库数据并处理 db_data = process_db_data(fetch_data()) # 计算各生产线的完成率 prod_30 = calculate_productivity(db_data['ASS30']['OK'], planned_data['ASS30']) prod_31 = calculate_productivity(db_data['ASS31']['OK'], planned_data['ASS31']) prod_32 = calculate_productivity(db_data['ASS32']['OK'], planned_data['ASS32']) # 计算Trend(应完成)值 - 应用新的逻辑 trend_30 = get_shift_adjusted_time( current_datetime, str(work_start_times['ASS30']), str(lunch_start_times['ASS30']), efficiency_data['ASS30'], 'ASS30' ) trend_31 = get_shift_adjusted_time( current_datetime, str(work_start_times['ASS31']), str(lunch_start_times['ASS31']), efficiency_data['ASS31'], 'ASS31' ) trend_32 = get_shift_adjusted_time( current_datetime, str(work_start_times['ASS32']), str(lunch_start_times['ASS32']), efficiency_data['ASS32'], 'ASS32' ) # 创建KOSU表格,使用从Excel获取的数据和处理后的数据库数据 html_table = f""" <table class="kosu-table"> <tr class="table-header"> <th class="col-line">Line</th> <th class="col-order">Order (计划订单)</th> <th class="col-trend">Trend (应完成)</th> <th class="col-produced">Produced (已完成)</th> <th class="col-productivity">Productivity (完成率)</th> <th class="col-rework">Rework (返工)</th> <th class="col-diff">Diff. (差额)</th> </tr> <tr> <td class="date-cell col-line">ASS30</td> <td class="number-cell col-order">{int(planned_data['ASS30'])}</td> <td class="number-cell col-trend">{trend_30}</td> <td class="number-cell col-produced">{db_data['ASS30']['OK']}</td> <td class="number-cell col-productivity {get_productivity_class(prod_30)}">{prod_30:.2f}%</td> <td class="number-cell col-rework">{db_data['ASS30']['Rework']}</td> <td class="number-cell col-diff">{int(planned_data['ASS30'] - db_data['ASS30']['OK'])}</td> </tr> <tr> <td class="date-cell col-line">ASS31</td> <td class="number-cell col-order">{int(planned_data['ASS31'])}</td> <td class="number-cell col-trend">{trend_31}</td> <td class="number-cell col-produced">{db_data['ASS31']['OK']}</td> <td class="number-cell col-productivity {get_productivity_class(prod_31)}">{prod_31:.2f}%</td> <td class="number-cell col-rework">{db_data['ASS31']['Rework']}</td> <td class="number-cell col-diff">{int(planned_data['ASS31'] - db_data['ASS31']['OK'])}</td> </tr> <tr> <td class="date-cell col-line">ASS32</td> <td class="number-cell col-order">{int(planned_data['ASS32'])}</td> <td class="number-cell col-trend">{trend_32}</td> <td class="number-cell col-produced">{db_data['ASS32']['OK']}</td> <td class="number-cell col-productivity {get_productivity_class(prod_32)}">{prod_32:.2f}%</td> <td class="number-cell col-rework">{db_data['ASS32']['Rework']}</td> <td class="number-cell col-diff">{int(planned_data['ASS32'] - db_data['ASS32']['OK'])}</td> </tr> </table> """ st.markdown(html_table, unsafe_allow_html=True) # 显示工作时间信息(可选) with st.expander("生产参数详情", expanded=False): col1, col2, col3 = st.columns(3) with col1: st.subheader("ASS30") st.write(f"工作开始时间: {work_start_times['ASS30']}") st.write(f"午餐开始时间: {lunch_start_times['ASS30']}") st.write(f"效率: {efficiency_data['ASS30']} 个/小时") with col2: st.subheader("ASS31") st.write(f"工作开始时间: {work_start_times['ASS31']}") st.write(f"午餐开始时间: {lunch_start_times['ASS31']}") st.write(f"效率: {efficiency_data['ASS31']} 个/小时") with col3: st.subheader("ASS32") st.write(f"工作开始时间: {work_start_times['ASS32']}") st.write(f"午餐开始时间: {lunch_start_times['ASS32']}") st.write(f"效率: {efficiency_data['ASS32']} 个/小时") # 创建三个饼状图 st.subheader("生产完成率饼状图") col1, col2, col3 = st.columns(3) with col1: st.markdown('<div class="pie-chart-container">', unsafe_allow_html=True) fig30 = create_pie_chart("ASS30", planned_data['ASS30'], db_data['ASS30']['OK'], prod_30) st.plotly_chart(fig30, use_container_width=True) st.markdown('</div>', unsafe_allow_html=True) with col2: st.markdown('<div class="pie-chart-container">', unsafe_allow_html=True) fig31 = create_pie_chart("ASS31", planned_data['ASS31'], db_data['ASS31']['OK'], prod_31) st.plotly_chart(fig31, use_container_width=True) st.markdown('</div>', unsafe_allow_html=True) with col3: st.markdown('<div class="pie-chart-container">', unsafe_allow_html=True) fig32 = create_pie_chart("ASS32", planned_data['ASS32'], db_data['ASS32']['OK'], prod_32) st.plotly_chart(fig32, use_container_width=True) st.markdown('</div>', unsafe_allow_html=True) # 可折叠的SQL查询部分 with st.expander("执行SQL查询", expanded=False): if st.button("执行SQL查询", key="sql_query_btn"): with st.spinner("正在查询数据库..."): df = fetch_data() if not df.empty: st.subheader("SQL查询结果") st.dataframe( df.style .highlight_max(axis=0, color="#C2A516") .set_properties(**{'background-color': '#1a1a1a', 'color': 'white'}), use_container_width=True, height=400 ) # 显示处理后的数据 db_data = process_db_data(df) st.subheader("处理后的数据") st.json(db_data) else: st.info("数据库查询无结果") # 更新时间显示 st.markdown(f'<div class="update-time">最后更新: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</div>', unsafe_allow_html=True) time.sleep(refresh_rate) elif page_option == "新项目1": st.info("新项目1页面尚未更新,敬请期待!") st.image("https://via.placeholder.com/800x400/1a1a1a/00b4d8?text=新项目1+开发中", use_column_width=True) elif page_option == "新项目2": st.info("新项目2页面尚未更新,敬请期待!") st.image("https://via.placeholder.com/800x400/1a1a1a/00b4d8?text=新项目2+开发中", use_column_width=True) 将python语句按功能拆分成多个模块,并在excel模块中加入验证的功能。我是新手你要注释每一行代码
09-25
<template> <view :class="disabled ? 'wrapper form-disable' : 'wrapper'"> <u-form ref="uForm" :model="form" :rules="rules" error-type="toast" label-width="auto" label-position="left" > <view class="model-box"> <u-form-item label="保单号" prop="policyNumber" required borderBottom @longpress="showHighlighPop($event, 'policyNumber')" > <u-input v-model="form.policyNumber" :color="formatterWarn(form.policyNumber, 'policyNumber')" placeholder="请输入保单号" maxlength="128" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16"></u-icon> </u-form-item> <u-form-item label="保司名称" prop="companyName" required borderBottom @longpress="showHighlighPop($event, 'companyName')" > <view class="input-box flex-row-fe"> <u-input v-model="form.companyName" :color="formatterWarn(form.companyName, 'companyName')" placeholder="请输入或选择保司名称" @input="handleInsurerInput" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" ></u-icon> <u-icon name="arrow-right" color="#999" size="14" @click="showSearchPop('companyName', 'companyList', 'name')" ></u-icon> </view> </u-form-item> <!-- 11111111111111111111111111111 --> <u-form-item label="地区" prop="areaInfo" required borderBottom @longpress="showHighlighPop($event, 'areaInfo')" > <view class="input-box flex-row-fe" @click="showPop('areaInfo', 'postalAddressNationCode', 'id')" > <view v-html="formatterWarn(form.areaInfo_name, 'areaInfo', 'select')" ></view> <u-icon name="arrow-right" color="#999" size="14"></u-icon> </view> </u-form-item> <u-form-item label="商户名称" prop="merchantName" required borderBottom @longpress="showHighlighPop($event, 'merchantName')" > <u-icon name="error-circle" color="#333" size="18" @click="showWarn('商户名称', '商户名称')" ></u-icon> <u-input v-model="form.merchantName" :color="formatterWarn(form.merchantName, 'merchantName')" placeholder="请输入商户名称" maxlength="128" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16"></u-icon> </u-form-item> <u-form-item label="产品分类" prop="productTypeName" required borderBottom @longpress="showHighlighPop($event, 'productTypeName')" > <view class="input-box flex-row-fe" @click="showPop('productTypeName', 'productTypeList', 'id')" > <view v-html=" formatterWarn( form.productTypeName_name, 'productTypeName', 'select' ) " ></view> <u-icon name="arrow-right" color="#999" size="14"></u-icon> </view> </u-form-item> <u-form-item label="投保产品名称" prop="skuName" required borderBottom @longpress="showHighlighPop($event, 'skuName')" > <view class="input-box flex-row-fe"> <u-input v-model="form.skuName" :color="formatterWarn(form.skuName, 'skuName', 'select')" placeholder="请输入或选择产品名称" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" ></u-icon> <u-icon name="arrow-right" color="#999" size="14" @click="showPop('skuName', 'skuList', 'id')" ></u-icon> </view> </u-form-item> <view style=" background-color: rgba(244, 245, 247, 1); width: 100%; padding-left: 26rpx; " > <u-form-item label="每期保费" prop="instalmentPremium" required borderBottom @longpress="showHighlighPop($event, 'instalmentPremium')" > <!-- 显示时用格式化后的值,输入时处理为原始值 --> <u-input :value="formatNumber(form.instalmentPremium)" @input="handleInput($event, 'instalmentPremium')" :color=" formatterWarn(form.instalmentPremium, 'instalmentPremium') " placeholder="请输入" maxlength="128" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" ></u-icon> </u-form-item> <u-form-item label="保费单位" prop="currency" required borderBottom @longpress="showHighlighPop($event, 'currency')" > <view class="input-box flex-row-fe" @click="showPop('currency', 'currencyList', 'id')" > <view v-html="formatterWarn(form.currency, 'currency', 'select')" ></view> <u-icon name="arrow-right" color="#999" size="14"></u-icon> </view> </u-form-item> <u-form-item label="保额" prop="coverageMoney" borderBottom @longpress="showHighlighPop($event, 'coverageMoney')" > <u-input :value="formatNumber(form.coverageMoney)" @input="handleInput($event, 'coverageMoney')" :color="formatterWarn(form.coverageMoney, 'coverageMoney')" placeholder="请输入" maxlength="128" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" ></u-icon> </u-form-item> <u-form-item label="缴费期" prop="paymentNum" required borderBottom @longpress="showHighlighPop($event, 'paymentNum')" > <view class="section"> <view class="options" v-for="(item, index) in payment.options" :key="index" > <radio :checked="form.paymentNumRadio == index" @click="selectOption(payment, index, 'paymentNum')" /> <text>{{ item.label }}</text> <!-- 仅当“交X年”或“交至X岁”选中时,显示输入框 --> <u-input v-model="form.paymentNum" v-show="form.paymentNumRadio == index" :color="formatterWarn(form.paymentNum, 'paymentNum')" @input="inputChange($event, 'paymentNum')" type="number" placeholder="请输入" maxlength="128" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" v-show=" item.type !== 'lifetime' && form.paymentNumRadio == index " ></u-icon> <text v-show="form.paymentNumRadio == index && item.type === 'year'" >年</text > <text v-show="item.type === 'age' && form.paymentNumRadio == index" >岁</text > </view> </view> </u-form-item> <u-form-item label="缴费频率" prop="paymentFrequency" required borderBottom @longpress="showHighlighPop($event, 'paymentFrequency')" > <view class="input-box flex-row-fe" @click="showPop('paymentFrequency', 'paymentFrequency', 'id')" > <view v-html=" formatterWarn( form.paymentFrequency_name, 'paymentFrequency', 'select' ) " ></view> <u-icon name="arrow-right" color="#999" size="14"></u-icon> </view> </u-form-item> <u-form-item label="保障期" prop="guaranteeNum" required borderBottom @longpress="showHighlighPop($event, 'guaranteeNum')" > <view class="section"> <view class="options" v-for="(item, index) in coverage.options" :key="index" > <radio :checked="form.guaranteeNumRadio == index" @click="selectOption(coverage, index, 'guaranteeNum')" /> <text>{{ item.label }}</text> <u-input v-model="form.guaranteeNum" v-show="form.guaranteeNumRadio == index" :color="formatterWarn(form.guaranteeNum, 'guaranteeNum')" type="number" placeholder="请输入" maxlength="128" @input="inputChange($event, 'guaranteeNum')" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" v-show=" item.type !== 'lifetime' && form.guaranteeNumRadio == index " ></u-icon> <text v-show=" form.guaranteeNumRadio == index && item.type === 'year' " >年</text > <text v-show=" form.guaranteeNumRadio == index && item.type === 'age' " >岁</text > </view> </view> </u-form-item> </view> <u-form-item label="是否预缴" prop="prepayStatus" required borderBottom @longpress="showHighlighPop($event, 'prepayStatus')" > <view class="input-box flex-row-fe" @click="showPop('prepayStatus', 'prepayStatus', 'id')" > <view v-html=" formatterWarn(form.prepayStatus_name, 'prepayStatus', 'select') " ></view> <u-icon name="arrow-right" color="#999" size="14"></u-icon> </view> </u-form-item> <view style=" background-color: rgba(244, 245, 247, 1); width: 100%; padding-left: 26rpx; " v-if="form.prepayStatus == '1'" > <u-form-item label="预缴金额" prop="prepayMoney" required borderBottom @longpress="showHighlighPop($event, 'prepayMoney')" > <u-input :value="formatNumber(form.prepayMoney)" @input="handleInput($event, 'prepayMoney')" :color="formatterWarn(form.prepayMoney, 'prepayMoney')" placeholder="请输入" maxlength="128" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" ></u-icon> </u-form-item> <u-form-item label="预缴期数" prop="prepayYear" required borderBottom @longpress="showHighlighPop($event, 'prepayYear')" > <u-input v-model="form.prepayYear" :color="formatterWarn(form.prepayYear, 'prepayYear')" placeholder="请输入" maxlength="128" @input="inputChange($event, 'prepayYear')" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" ></u-icon> </u-form-item> </view> <u-form-item label="是否有附加险" prop="additionFlag" required borderBottom @longpress="showHighlighPop($event, 'additionFlag')" > <view class="input-box flex-row-fe" @click="showPop('additionFlag', 'additionFlag', 'id')" > <view v-html=" formatterWarn(form.additionFlag_name, 'additionFlag', 'select') " ></view> <u-icon name="arrow-right" color="#999" size="14"></u-icon> </view> </u-form-item> <view style=" background-color: rgba(244, 245, 247, 1); width: 100%; padding-left: 26rpx; " v-if="form.additionFlag == '1'" > <u-form-item label="附加险名称" prop="additionSkuInfo" required borderBottom @longpress="showHighlighPop($event, 'additionSkuInfo')" > <u-input v-model="form.additionSkuInfo" :color="formatterWarn(form.additionSkuInfo, 'additionSkuInfo')" placeholder="请输入" maxlength="128" border="none" input-align="right" ></u-input> <u-icon class="m-l-4" name="edit-pen" color="#999" size="16" ></u-icon> </u-form-item> </view> </view> </u-form> <view class="tabbar flex-row-sb safe_bottom"> <u-button color="#2F288F" plain text="暂存" @click="submit('Staging')" style="width: 250rpx" ></u-button> <u-button color="#2F288F" text="确定" style="width: 250rpx" @click="submit('save')" ></u-button> </view> <!-- 选择弹窗start --> <view class="popup-wrap"> <u-picker :show="areaCodeshow" :columns="[columns.areaCodeColumns]" confirmColor="#2F288F" @cancel="areaCodeshow = false" @confirm="areaCodeConfirm" ></u-picker> <u-picker :show="popObj.show" :columns="[popObj.selectColumns]" :defaultIndex="[0]" confirmColor="#2F288F" keyName="name" closeOnClickOverlay @confirm="onPickerConfirm" @close="popObj.show = false" @cancel="popObj.show = false" ></u-picker> <u-datetime-picker :show="timePopObj.show" confirmColor="#2F288F" mode="date" :value="timePopObj.defaultDate" :minDate="timePopObj.minDate" :maxDate="timePopObj.maxDate" closeOnClickOverlay @close="timePopObj.show = false" @cancel="timePopObj.show = false" @confirm="confirmTime" ></u-datetime-picker> <!-- 搜索选择器 --> <u-picker-search :key="searchPickerKey" :show="popObjSearch.show" :columns="[popObjSearch.selectColumns]" :defaultIndex="[0]" confirmColor="#2F288F" keyName="name" closeOnClickOverlay @confirm="onPickerSearchConfirm" @close="onPickerSearchClose" @cancel="onPickerSearchClose" ></u-picker-search> </view> </view> </template> <script> import dayjs from "@/uni_modules/uview-ui/libs/util/dayjs.js"; import { POLICYBASICINFOCHECK } from "@/config/checkRules"; import { DICTS } from "@/config/dictionary"; import { getUuid, formatDictName, isEmpty, goBackByLocalKey, getBirthDayByIdCard, geSexByIdCard, Validator, } from "@/config/utils"; const columns = DICTS; export default { components: {}, data() { return { searchPickerKey: 0, payment: { options: [ { label: "交", type: "year", isChecked: false, inputVal: "" }, { label: "交至", type: "age", isChecked: false, inputVal: "" }, { label: "终身", type: "lifetime", isChecked: true, inputVal: "" }, ], }, // 保障期配置(结构同缴费期) coverage: { options: [ { label: "保", type: "year", isChecked: false, inputVal: "" }, { label: "保至", type: "age", isChecked: false, inputVal: "" }, { label: "终身", type: "lifetime", isChecked: true, inputVal: "" }, ], }, quicklySelectCustomerModel: false, columns, disabled: false, formType: "baseInfo", form: { dependentsInfo: [], guaranteeNumRadio: "2", paymentNumRadio: "2", instalmentPremium: "", }, rules: {}, popObj: { show: false, formKey: "", selectColumns: [], valueWay: "", }, // 搜索选择器状态对象 popObjSearch: { show: false, formKey: "", selectColumns: [], valueWay: "", }, timePopObj: { show: false, minDate: dayjs().subtract(100, "year").valueOf(), maxDate: dayjs().valueOf(), defaultDate: dayjs().subtract(20, "year").valueOf(), }, highlighObj: { show: false, key: "", value: false, style: {}, }, initPolicyCustodyInfos: { companyList: [], currencyList: [], productTypeList: [], skuList: [], }, highlightTabList: [], updateTabList: [], isNew: true, areaCodeshow: false, idCardPopObj: { show: false, mode: "idCard", formKey: "", valueWay: "", clickY: 0, }, localKey: "", hasSameBtn: false, showOcrForm: false, preOcrFormKey: null, multipleIDs: false, customerId: "", certificateIDModel: false, certificateParams: {}, }; }, watch: { form: { handler(newVal, oldVal) { let vm = this; var tempData = uni.getStorageSync(vm.localKey); let { ...restFrom } = vm.form; tempData.baseInfo = restFrom; tempData.baseInfoDetail = restFrom; tempData.baseInfoHasRead = true; uni.setStorage({ key: vm.localKey, data: tempData, }); }, deep: true, }, }, async onLoad(opt) { this.formType = opt.type; this.disabled = JSON.parse(opt.disabled || "false"); this.isNew = JSON.parse(opt.isNew || "true"); this.highlightTabList = JSON.parse(opt.highlightTabList || "[]"); this.updateTabList = JSON.parse(opt.updateTabList || "[]"); this.localKey = opt.localKey; var localFormData = uni.getStorageSync(opt.localKey); this.form = localFormData.baseInfo; console.log("onload", localFormData.baseInfo); this.initPolicyCustody(); uni.setNavigationBarTitle({ title: "保单基础信息", }); }, onUnload() { // uni.$emit("highlightTabList", this.highlightTabList); }, methods: { // 处理保司名称输入事件 handleInsurerInput() { const inputVal = this.form.companyName.trim().toLowerCase(); if (!inputVal) { // 清空搜索时恢复完整列表 this.popObjSearch.selectColumns = this.initPolicyCustodyInfos.companyList; return; } // 过滤匹配项 const filtered = this.initPolicyCustodyInfos.companyList.filter((item) => item.name.toLowerCase().includes(inputVal) ); // 更新搜索弹窗的选项 if ( this.popObjSearch.show && this.popObjSearch.formKey === "companyName" ) { this.popObjSearch.selectColumns = filtered; } }, deepClone(source) { if (source === null || typeof source !== 'object') { return source; } if (Array.isArray(source)) { return source.map(item => this.deepClone(item)); } const clone = {}; for (const key in source) { if (source.hasOwnProperty(key)) { clone[key] = this.deepClone(source[key]); } } return clone; }, onPickerSearchClose() { this.popObjSearch.show = false; }, // 显示带搜索的选择器 showSearchPop(formKey, selectColumnsKey, valueWay) { // 递增key值强制重置组件 this.searchPickerKey++; // 获取完整数据源(深拷贝) let selectColumns = []; if ( selectColumnsKey == "companyList" || selectColumnsKey == "currencyList" || selectColumnsKey == "productTypeList" || selectColumnsKey == "skuList" ) { // 使用深拷贝方法创建新数组 selectColumns = this.deepClone(this.initPolicyCustodyInfos[selectColumnsKey] || []); } else { // 使用深拷贝方法创建新数组 selectColumns = this.deepClone(columns[selectColumnsKey] || []); } // 重置搜索选择器状态 this.popObjSearch = { show: true, formKey, selectColumns, valueWay }; }, // 搜索选择器确认事件 onPickerSearchConfirm(e) { let vm = this; this.$set( this.form, vm.popObjSearch.formKey, e.value[0][vm.popObjSearch.valueWay] ); // 同步显示名称 if (vm.popObjSearch.formKey === "companyName") { this.$set(this.form, "companyName", e.value[0].name); } this.popObjSearch.show = false; }, formatNumber(value) { if (!value) return ""; // 先转为数字,再处理千分位 const num = Number(value); if (isNaN(num)) return value; // 非数字保持原样 return num.toLocaleString("zh-CN"); // 千分位格式化 }, // 处理输入,将千分位字符串还原为原始值(纯数字) handleInput(val, key) { // 去除所有非数字字符(保留负号和小数点,根据需求调整) const rawValue = val.replace(/[^\d.-]/g, ""); // 更新原始值(纯数字) this.form[key] = rawValue; }, selectOption(group, index, type) { group.options.forEach((item, i) => { item.isChecked = i === index; }); if (type == "paymentNum") { this.form.paymentNumRadio = index; } else if ((type = "guaranteeNum")) { this.form.guaranteeNumRadio = index; } }, // 输入框值绑定 inputChange(event, type) { const currentValue = event; this.validateNumber(currentValue, type); }, // 通用校验函数 validateNumber(value, type) { if (value === "") { return { valid: true, message: "", suggestedValue: null }; } // 检查是否为有效数字 if (!/^\d+$/.test(value)) { return { valid: false, message: "只能输入数字", suggestedValue: null }; } const num = parseInt(value, 10); // 检查是否在范围内 if ((num < 1 || num > 100) && type == "prepayYear") { uni.$u.toast("数值应该在1-100"); } if ((num < 1 || num > 200) && type != "prepayYear") { uni.$u.toast("数值应该在1-200"); } }, async initPolicyCustody() { const { data } = await uni.$u.http.get("/app/policyCustody/dict"); this.initPolicyCustodyInfos = data; if (this.initPolicyCustodyInfos) { // 确保所有列表都是对象数组格式 this.initPolicyCustodyInfos.companyList = ( this.initPolicyCustodyInfos.companyList || [] ).map((company) => ({ id: company, name: company, })); this.initPolicyCustodyInfos.currencyList = ( this.initPolicyCustodyInfos.currencyList || [] ).map((company) => ({ id: company, name: company, })); this.initPolicyCustodyInfos.productTypeList = ( this.initPolicyCustodyInfos.productTypeList || [] ).map((company) => ({ id: company, name: company, })); this.initPolicyCustodyInfos.skuList = ( this.initPolicyCustodyInfos.skuList || [] ).map((company) => ({ id: company, name: company, })); } console.log("初始化保单托管数据:", this.initPolicyCustodyInfos); }, // 弹出弹窗 showPop(formKey, selectColumnsKey, valueWay) { let selectColumns = ""; if ( selectColumnsKey == "companyList" || selectColumnsKey == "currencyList" || selectColumnsKey == "productTypeList" || selectColumnsKey == "skuList" ) { selectColumns = this.initPolicyCustodyInfos[selectColumnsKey]; } else { selectColumns = columns[selectColumnsKey]; } this.popObj = { show: true, formKey, selectColumns, valueWay, }; }, formatDictName, handleComputeInsure(simple) { let { form } = this; var validator = new Validator(); form.multipleIDs = true; var insureRules = POLICYBASICINFOCHECK(form, simple); insureRules.forEach((ele) => { var currentValue = _.get(form, ele.key); var currentRuleList = [ { strategy: "isNonEmpty", errorMsg: `【保单基础信息】${ele.name}不能为空`, }, ]; if (ele.rules) { for (var i = 0; i < ele.rules.length; i++) { var validateFuncRes = ele.rules[i].func(currentValue); currentRuleList.push({ strategy: "customerFunc", func: () => validateFuncRes == true, errorMsg: `${ele.rules[i].errorMsg}`, }); } } validator.add(currentValue, currentRuleList); }); console.log("insureRules", insureRules); var errorList = _.uniq(validator.checkAll()); return errorList; }, // 弹出提示框 showWarn(title, content) { uni.showModal({ title, content, showCancel: false, }); }, // 确认选择 onPickerConfirm(e) { let vm = this; this.$set(this.form, vm.popObj.formKey, e.value[0][this.popObj.valueWay]); this.$set(this.form, `${vm.popObj.formKey}_name`, e.value[0].name); this.popObj.show = false; }, // 确认时间 confirmTime(e) { let vm = this; this.$set( this.form, "birthDate", uni.$u.timeFormat(e.value, "yyyy-mm-dd") ); this.$set(this.form, "age", vm.calculateAge(this.form.birthDate)); this.timePopObj.show = false; }, // 计算年龄 calculateAge(birthDate) { var birthDate = new Date(birthDate); var today = new Date(); var age = today.getFullYear() - birthDate.getFullYear(); var m = today.getMonth() - birthDate.getMonth(); if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) { age--; } return age; }, areaCodeConfirm(e) { this.$set(this.form, "phoneAreaCode", e.value[0].id); this.$set(this.form, "phoneArea", e.value[0].text); this.areaCodeshow = false; }, // 将key转化为文字 keyIntoText() { let otherKeys = [ "observerCardType", "relationStatus", "gender", "maritalStatus", "certificateType", "schooling", "title", "numberDependents", "employmentType", "industryCode", "retireAge", "entryHkCardType", ]; otherKeys.forEach((item) => { if (isEmpty(this.form[item])) { return false; } console.log("columns[item]", columns, item); let res = columns[item].find( (item_son) => item_son.id == this.form[item] ); this.form[`${item}_name`] = res ? res.name : ""; }); console.log(this.form); }, // 提交 async submit(flag) { let { localKey, multipleIDs, form } = this; if (flag != "Staging") { let errorList = this.handleComputeInsure(true, flag); if (errorList.length > 0) { return uni.$u.toast(errorList[0]); } } uni.$emit("baseInfo"); var path = "pages/policy/index"; goBackByLocalKey(localKey, path); }, // 弹出高亮 showHighlighPop(e, key) { if (this.isNew) { return false; } this.highlighObj = { show: true, key, value: this.highlightTabList.findIndex( (item) => item === `${this.formType}.${key}` ) !== -1, style: { top: e.currentTarget.offsetTop + "px", }, }; }, // 设置高亮 async setHighlight() { await uni.$u.http.post("/app/subscribeOrder/scalarData", { fieldName: `${this.formType}.${this.highlighObj.key}`, orderId: this.form.orderId, }); this.highlightTabList.push(`${this.formType}.${this.highlighObj.key}`); this.highlighObj.show = false; }, // 取消高亮 async cancelHighlight() { await uni.$u.http.post("/app/subscribeOrder/cancelScalarData", { fieldName: `${this.formType}.${this.highlighObj.key}`, orderId: this.form.orderId, }); let index = this.highlightTabList.findIndex( (item) => item === `${this.formType}.${this.highlighObj.key}` ); if (index !== -1) { this.$delete(this.highlightTabList, index); } this.highlighObj.show = false; }, // 高亮提示处理函数 formatterWarn(text, keyName, type = "input") { const key = `${this.formType}.${keyName}`; const keyDetail = `${this.formType}Detail.${keyName}`; let highlighFlag = this.highlightTabList.findIndex( (item) => item === key || item === keyDetail ) !== -1; let updateFlag = this.updateTabList.findIndex( (item) => item === key || item === keyDetail ) !== -1; let waitFlag = text === "待确认"; if (type === "input") { // 输入框返回颜色 return updateFlag ? "#FF3141" : waitFlag || highlighFlag ? "#FF7700" : "#333333"; } else { // 上拉选择返回富文本 return `<text class="${ updateFlag ? "cff3" : waitFlag || highlighFlag ? "cff7" : "c333" }">${isEmpty(text) ? "" : text}</text>`; } }, handleSetSamePalce() { this.hasSameBtn = !this.hasSameBtn; if (this.hasSameBtn) { this.form.postalAddress = this.form.residentialAddress; } }, handleResidentialAddressChange() { if (this.hasSameBtn) { this.form.postalAddress = this.form.residentialAddress; } }, }, }; </script> <style lang="scss" scoped> .wrapper { position: relative; } .model-box { margin: 20upx; background-color: white; padding: 0 32upx 16upx; margin-bottom: 200upx; border-radius: 5upx; .model-box-title { font-size: 30upx; font-weight: bold; padding: 20upx 0; border-bottom: 1upx solid rgb(214, 215, 217); } .input-box { flex-grow: 1; } .sameBtn { display: flex; align-items: center; align-content: center; } .square { margin: 0 2rpx; width: 24px; height: 24px; border: 1px solid #d6d7d9; } .memberCountItem { padding: 20upx; border-radius: 10upx; background: #f4f6f9; margin-bottom: 10upx; } } .model-box-son { border-bottom: 20upx solid #f4f6f9; } .highlight-pop { position: absolute; left: 0; top: 0; bottom: 0; right: 0; z-index: 10; .pop-content { position: absolute; right: 40upx; top: 0; background-color: #333333; border-radius: 10upx; transform: translateY(-100%); &::after { content: ""; display: block; position: absolute; right: 16upx; bottom: -8upx; width: 16upx; height: 16upx; background-color: #333333; transform: rotate(45deg); } .item { padding: 10upx 16upx; font-size: 20upx; .circle { width: 20upx; height: 20upx; border-radius: 50%; margin: 0 auto 10upx; background-color: white; } } .active { .circle { background-color: #ff7700; } } } } .tabbar { height: 146upx; position: fixed; z-index: 999; left: 0; right: 0; bottom: 0; background-color: white; padding: 0 30upx; box-sizing: content-box; border-top: 1px solid #f4f6f9; } .tabbar-placeholder { box-sizing: content-box; height: 146upx; } .ocrPic { width: 57upx; height: 45upx; } .multipleIDsItem { position: relative; padding: 0 36rpx; background: #f5f6fa; border: 1rpx solid #2f288f; margin-bottom: 10rpx; border-radius: 10rpx; &:first-of-type { background: #fff; border: none; } .removeBtn { position: absolute; right: -12rpx; top: -12rpx; } } .select-customer-btn { padding: 14rpx 16rpx; border-radius: 8rpx; border: 1rpx solid #2f288f; color: #2f288f; font-size: 28rpx; } .multipleIDsBtn { pointer-events: all; display: flex; justify-content: center; align-content: center; align-items: center; white-space: nowrap; width: 48rpx; height: 48rpx; margin-left: 10rpx; border: 1rpx solid #2f288f; color: #2f288f; font-size: 40rpx; line-height: 50rpx; border-radius: 10rpx; } .title { font-weight: bold; margin-bottom: 10rpx; } .options { display: flex; align-items: center; margin-bottom: 10rpx; } ::v-deep uni-radio .uni-radio-input { -webkit-appearance: none; appearance: none; margin-right: 5px; outline: 0; border: 1px solid #d1d1d1; background-color: #fff; border-radius: 50%; width: 30rpx; height: 30rpx; position: relative; } ::v-deep .uni-radio-input-checked { background: #2f288f !important; border: 1px solid #2f288f !important; } </style> 同理把产品分类的下拉框里也加个搜索的弹框
最新发布
11-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值