表单必填_forms. 表单(中)

博客介绍了输入框的种类、状态、关联元素、控件、样式等,还提及输入框的间距、圆角等设计细节。重点讨论了表单必填项的标记问题,指出即使多数为必填项也需标记,并介绍了文字、星号、图形三种标注方式,此外还涉及按钮的视觉设计规范。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

b51ab82e205ae2909dd5d571d2ac948d.png

bf8f8fa75738f25b026d597279a37ffc.png

输入框有什么问题?他的基本要素有哪些,和他关联的内容有哪些,在设计输入框的时候要注意哪些问题?

6bbec12b384d6986ad6fbdeca708e7f7.png

输入框的类型

输入框有很多种类:数字输入,密码输入、文本输入、标签录入。

也有很多状态:普通状态、悬停状态、选中状态、完成状态、禁用状态。

还有很多关联元素:提示语、完成反馈、错误反馈、按钮、气泡、占位符、图标、必要符号。

还可以作为不同的控件:下拉菜单、日期选择器、级选择器。

也有不同的样式:下划线、Box、文本框。

7767cfdc4a8e419224e0df7e8e111a88.png

iGas-输入框的尺寸 输入框的尺寸 尺寸是输入框的基本,在1920P的屏幕下通常使用的字符尺寸是14/16/18…那么这些尺寸下,输入框的高度则需要合理设置。在IGW项目中,出现过一个非常恼人的问题。就是我的设计图是按照1440的宽度出的效果,字符也基本是14左右。但是考虑到用户群体可能会有分辨率较低的屏幕,所以需要放大字符的大小。这一改就需要动全局的字符大小,从提示字符到标签到标题。这一改就会影响到输入框的高度从26放大到28px。而改动了输入框的高度则需要改表单中控件间距,非常繁琐(这里体现了模块化思维和Auto Layout的重要性,具体内容查看往期文章《Stack.设计中的堆栈》)

2de95cadd677eb9a212b93e012479ecd.png

输入框的间距

 输入框的间距 输入框到右对齐标签的间距,输入框到下方辅助文字的间距,内部文本的间距…都要遵从a*n的原则(a=最小原子距离)在数据录入中,如果需要错误反馈和辅助文本,则需要预留足够的空间。留够足够的空间,能够增加页面的通透感。

f97c04ef96bd7279d908b7b99c5fccf7.png

IWOS-输入框暗示填写内容

 输入框的长度 输入框的长度和高度,和其关联的内容应该是高度相关的。比如在留言框中,如果极限文本长度140字,需要留够足够的高度。“不要让用户读标签”(https://www.nngroup.com/articles/required-fields/ ) NNg一篇报道中的观点:用户很不情愿阅读表单中的标签,相比标签,用户更愿意直接去看“需要我输入的内容”。根据这一点可以通过输入域,暗示标签的内容。比如在IWOS项目中文本框则暗示需要输入的长文本“描述内容”,而较短的带着箭头符号box则暗示下拉菜单“选项”,如果有更短的输入框则可能暗示“数字录入”等功能。

 文本框的滚动条 

在页面中较长的表单可能会占据一个以上的屏幕,这时则需要滚动鼠标滚轮。这种情况下不适合在文本框中再加入滚动条。两个以上的滚动条容易造成页面操作混乱。f25002655e3d2a29ee5771b69d9caaaa.png输入框的圆角和对齐方式

 输入框的圆角 

在有的界面中,输入框通常会根据界面统一特性加入圆角的设计。在有圆角的情况之下,辅助性文字和占位符的距离会有所变化。通常,如果弹出窗口中也有输入框,则输入框的圆角不应该大于弹出窗口的圆角(圆角的包含关系)。

75010d880d2b25d2f3aea8c236b90366.png

在填写表单时,通常会碰到必填项RequiredFields,如果不填写则不能提交表单。

我应该标记这些必填项吗?如果页面大多数都是必填项,那还需要标记吗?答案是:需要。

12f1bfb0271984819e43e4a2be94db40.png

花旗银行/美国通运卡

例如以上这两个表单。左边的页面是花旗银行的信用卡申请页面,它在表单的前面用一行小字标明:所有选项必填,除注明opt.者外。这种处理方式看起来很聪明其实犯了错误,虽然让注明变少了,但是有几个问题:1.用户不怎么会注意到表单开头的小字,他们会直接进行填写;2.即使用户阅读了小字内容,也有可能会忘记,这样的行为会增加他们的认知负担。不要加入挑战性环节;3.opt的缩写意义不明;4.用户会浏览整个表单,确定哪些是不需要的。用户更倾向于跳过这些内容。

如何标记必填项

三种常用的方式:文字标注,星号标注,图形标注。

cc17f4131eb36b86117331b070f08c60.png

iOS-电子钱包

文字标注可以标注在输入域内的占位符文本上。通常,特别是对于较长的表单,最好在字段外部而不是内部使用“必填(Required)”一词,以便于识别仍然需要填写的字段。

32cc35ebde6468f3559087eba0a0a2e0.png

IGW-老版本中星号的标注

星号标注非常普遍,尤其是在文本前红色的星号很有引人注目的功能。星号虽然不一定是在文本的左边,也可以在右边。但建议不要使用纯度较低,偏灰的颜色的星号来进行标注。略微柔和的颜色可能具有美学优势,但是真正的低对比度符号会构成弱视或老年人的可访问性问题,并且会降低每个人对表单的视觉处理速度。

2659c9e4998e9765337285b06a48d81d.png

IGW-新版本中的标注IGW新版本中,通过改变了标注记号的样式,使得界面更加整洁。原本的界面标签是左对齐的,所以在标签右边的*号,可能会造成视觉上的不平衡。如果这里是右对齐的标签,则不一定这样处理。d14efa53137ab064d455dd17e367c261.png

在视觉设计规范中,通常需要考虑到不同情况下的按钮。图标+文字、单独文字、单独图标、三种不同状态等。在表单填写的时候,页面内可能会有多个功能性按钮。

4fb3e44ea7196bbe7c9f1e0a4bc46d7a.png

39e111eb46d8c51175191738d564dd6b.png

IGW-视觉设计规范

了解到此板块此页面主要解决用户什么样的问题,对于按钮的功能性有不一样的编排。在优先级对等的按钮间的排列,和优先级不统一的按钮之间的排列有很大的不同。

7122c53e1cac35bcfeafd12ba566cd41.png

iGas-信息录入界面

下期文章《forms.表单(下)》内容:单列-双列表单、栅格系统和组件、表单的留白、高级表单往期文章:《forms.表单(上)》 B端实战:表单(上)《anima.学习笔记》 Anima:自动布局和模块化设计思维《Stack.设计中的堆栈》 堆栈思想在设计中的应用《Principle.学习笔记》 Principle的灵魂:Animate & Drivers 的进阶用法

a83f25cef1072231f3751cbe62e702cc.gif

import curses import requests from bs4 import BeautifulSoup from urllib.parse import urljoin import os # 文件路径 FAVORITES_FILE = 'favorites.txt' HISTORY_FILE = 'history.txt' # 加载数据 def load_list(filename): try: with open(filename, 'r', encoding='utf-8') as f: return [line.strip() for line in f if line.strip()] except FileNotFoundError: return [] # 保存数据 def save_list(filename, items): with open(filename, 'w', encoding='utf-8') as f: for item in items: f.write(item + '\n') # 获取网页纯文本 def fetch_page(url): try: res = requests.get(url, timeout=5) res.raise_for_status() soup = BeautifulSoup(res.text, 'html.parser') for tag in soup(['script', 'style']): tag.decompose() return soup.get_text() except Exception as e: return f"加载失败: {e}" # 提取超链接和表单(扩展版) def extract_links_and_forms(html, base_url): soup = BeautifulSoup(html, 'html.parser') # 提取超链接 links = [] for a in soup.find_all('a', href=True): href = a['href'] if not href.startswith('http'): href = urljoin(base_url, href) links.append((href, a.get_text(strip=True))) # 提取表单 forms = [] for form in soup.find_all('form'): action = form.get('action', '') if not action.startswith('http'): action = urljoin(base_url, action) method = form.get('method', 'get').lower() inputs = [] # 提取所有输入字段 for input_tag in form.find_all(['input', 'textarea', 'select']): name = input_tag.get('name') if not name: continue input_type = input_tag.get('type', 'text') value = input_tag.get('value', '') required = input_tag.has_attr('required') placeholder = input_tag.get('placeholder', '') if input_tag.name == 'textarea': input_type = 'textarea' value = input_tag.get_text() elif input_tag.name == 'select': input_type = 'select' options = [(opt.get('value') or opt.text, opt.text) for opt in input_tag.find_all('option')] inputs.append({ 'name': name, 'type': input_type, 'value': value, 'required': required, 'placeholder': placeholder, 'options': options }) continue # 处理 checkbox 和 radio if input_type == 'checkbox' or input_type == 'radio': inputs.append({ 'name': name, 'type': input_type, 'value': value, 'checked': input_tag.has_attr('checked'), 'required': required }) continue inputs.append({ 'name': name, 'type': input_type, 'value': value, 'required': required, 'placeholder': placeholder }) forms.append((action, method, inputs)) return links, forms # 输入框 def input_box(stdscr, prompt): curses.echo() stdscr.clear() stdscr.addstr(0, 0, prompt) stdscr.refresh() input_str = stdscr.getstr(1, 0).decode('utf-8') curses.noecho() return input_str # 提交表单(扩展版) def submit_form(stdscr, form): action, method, inputs = form data = {} for field in inputs: name = field['name'] input_type = field['type'] required = field.get('required', False) placeholder = field.get('placeholder', '') value = field.get('value', '') prompt = f"{name}" if placeholder: prompt += f"(提示:{placeholder})" if required: prompt += " [必填]" if input_type == 'select': options = field['options'] stdscr.clear() stdscr.addstr(0, 0, f"请选择 {name}:") for i, (val, text) in enumerate(options): stdscr.addstr(i + 1, 0, f"{i + 1}. {text}") stdscr.refresh() idx = int(stdscr.getstr(len(options) + 2, 0).decode('utf-8')) - 1 data[name] = options[idx][0] elif input_type == 'checkbox': checked = field.get('checked', False) res = input_box(stdscr, f"{name} [复选框] 是否选中?(y/n):") data[name] = 'on' if res.lower() == 'y' else '' elif input_type == 'radio': res = input_box(stdscr, f"{name} [单选] 是否选中?(y/n):") data[name] = value if res.lower() == 'y' else '' else: default = value if value else '' user_input = input_box(stdscr, f"{prompt}:") data[name] = user_input if user_input else default try: if method == 'post': res = requests.post(action, data=data) else: res = requests.get(action, params=data) return res.text except Exception as e: return f"表单提交失败:{e}" # 收藏夹菜单 def favorites_menu(stdscr, favorites, current_url): while True: options = ["新添", "删除", "退出"] action = show_list(stdscr, options, "收藏夹", options) if action == 0: # 新添 if current_url not in favorites: favorites.append(current_url) save_list(FAVORITES_FILE, favorites) elif action == 1: # 删除 if favorites: idx = show_list(stdscr, favorites, "选择要删除的收藏") if idx >= 0: favorites.pop(idx) save_list(FAVORITES_FILE, favorites) elif action == 2 or action == -1: break # 历史记录菜单 def history_menu(stdscr, history): while True: options = ["清空", "退出"] action = show_list(stdscr, options, "历史记录", options) if action == 0: # 清空 history.clear() save_list(HISTORY_FILE, history) elif action == 1 or action == -1: break elif action >= 0: return history[action] return None # 显示列表(收藏夹/历史记录) def show_list(stdscr, items, title, actions=None): selected = 0 while True: stdscr.clear() stdscr.addstr(0, 0, title) if actions: stdscr.addstr(0, len(title) + 2, f"| {' | '.join(actions)}") stdscr.addstr(1, 0, "-" * 50) for i, item in enumerate(items): if i == selected: stdscr.attron(curses.A_REVERSE) stdscr.addstr(i + 2, 0, f"{i + 1}. {item}") if i == selected: stdscr.attroff(curses.A_REVERSE) stdscr.addstr(len(items) + 3, 0, "方向键选择,Enter确认,q退出") stdscr.refresh() key = stdscr.getch() if key == curses.KEY_UP and selected > 0: selected -= 1 elif key == curses.KEY_DOWN and selected < len(items) - 1: selected += 1 elif key == ord('\n'): return selected elif key == ord('q'): return -1 def main(stdscr): curses.curs_set(0) # 隐藏光标 favorites = load_list(FAVORITES_FILE) history = load_list(HISTORY_FILE) # 初始页面加载 current_url = input_box(stdscr, "请输入网址:") page_text = fetch_page(current_url) if current_url not in history: history.append(current_url) if len(history) > 20: history.pop(0) save_list(HISTORY_FILE, history) # 初始数据 menu_options = ["收藏夹", "新的网页", "历史记录"] selected_menu = 0 links, forms = extract_links_and_forms(page_text, current_url) selected_link = 0 selected_form = 0 mode = "menu" # 当前模式:menu, link, form while True: stdscr.clear() h, w = stdscr.getmaxyx() # 显示顶部菜单 for i, opt in enumerate(menu_options): x = 2 + i * 15 if mode == "menu" and i == selected_menu: stdscr.attron(curses.A_REVERSE) stdscr.addstr(0, x, opt) if mode == "menu" and i == selected_menu: stdscr.attroff(curses.A_REVERSE) stdscr.addstr(1, 0, "-" * w) # 显示网页内容 lines = page_text.split('\n') for i, line in enumerate(lines[:h - 10]): stdscr.addstr(i + 2, 0, line[:w - 1]) # 显示超链接 stdscr.addstr(h - 8, 0, "超链接:") for i, (url, text) in enumerate(links): label = f"{i + 1}. {text[:30]}..." if mode == "link" and i == selected_link: stdscr.attron(curses.A_REVERSE) stdscr.addstr(h - 7 + i, 0, label[:w - 1]) if mode == "link" and i == selected_link: stdscr.attroff(curses.A_REVERSE) # 显示表单 stdscr.addstr(h - 7 + len(links) + 1, 0, "表单:") for i, (action, method, inputs) in enumerate(forms): label = f"表单 {i + 1}: {method.upper()} {action[:30]}..." if mode == "form" and i == selected_form: stdscr.attron(curses.A_REVERSE) stdscr.addstr(h - 6 + len(links) + i + 1, 0, label[:w - 1]) if mode == "form" and i == selected_form: stdscr.attroff(curses.A_REVERSE) # 底部提示 stdscr.addstr(h - 1, 0, "方向键选择,Enter确认,q返回,Tab切换区域") stdscr.refresh() # 用户输入处理 key = stdscr.getch() if key == ord('q'): break elif key == ord('\t'): # 切换模式:菜单 -> 链接 -> 表单 -> 菜单 if mode == "menu": mode = "link" elif mode == "link": mode = "form" else: mode = "menu" elif key == curses.KEY_UP: if mode == "menu" and selected_menu > 0: selected_menu -= 1 elif mode == "link" and selected_link > 0: selected_link -= 1 elif mode == "form" and selected_form > 0: selected_form -= 1 elif key == curses.KEY_DOWN: if mode == "menu" and selected_menu < len(menu_options) - 1: selected_menu += 1 elif mode == "link" and selected_link < len(links) - 1: selected_link += 1 elif mode == "form" and selected_form < len(forms) - 1: selected_form += 1 elif key == ord('\n'): if mode == "menu": if selected_menu == 0: favorites_menu(stdscr, favorites, current_url) elif selected_menu == 1: current_url = input_box(stdscr, "请输入网址:") page_text = fetch_page(current_url) if current_url not in history: history.append(current_url) if len(history) > 20: history.pop(0) save_list(HISTORY_FILE, history) links, forms = extract_links_and_forms(page_text, current_url) elif selected_menu == 2: selected_url = history_menu(stdscr, history) if selected_url: current_url = selected_url page_text = fetch_page(current_url) links, forms = extract_links_and_forms(page_text, current_url) elif mode == "link": if links: current_url = links[selected_link][0] page_text = fetch_page(current_url) links, forms = extract_links_and_forms(page_text, current_url) elif mode == "form": if forms: result = submit_form(stdscr, forms[selected_form]) page_text = result links, forms = extract_links_and_forms(page_text, current_url) return # 启动程序 if __name__ == "__main__": curses.wrapper(main) 这是我的纯文本浏览器的源代码。它报错了:Python 3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license()" for more information. = RESTART: C:\Users\number one\Desktop\纯文本浏览器.py Traceback (most recent call last): File "C:\Users\number one\Desktop\纯文本浏览器.py", line 363, in <module> curses.wrapper(main) File "C:\Users\number one\AppData\Local\Programs\Python\Python311\Lib\curses\__init__.py", line 73, in wrapper stdscr = initscr() File "C:\Users\number one\AppData\Local\Programs\Python\Python311\Lib\curses\__init__.py", line 30, in initscr fd=_sys.__stdout__.fileno()) AttributeError: 'NoneType' object has no attribute 'fileno'
最新发布
07-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值