3. 工单创建功能开发:前端表单设计、后端路由与数据入库
好,咱们今天来聊聊工单创建这个核心功能。说实话,这是整个系统里用户打交道最多的一个环节,也是我当年踩坑最多的地方。
工单创建,说白了就是让用户填一张表单,然后我们把它存到数据库里。听起来简单?嗯,但细节决定成败。我见过太多项目,表单设计得花里胡哨,结果用户填到一半就放弃了。或者后端验证没做好,数据库里一堆脏数据。
今天咱们就一步步来,把这件事做扎实。
3.1 前端表单设计:用户第一眼看到的东西
我个人习惯,设计表单前先想清楚用户要填什么。工单创建,核心字段就三个:标题、描述、优先级。别贪多,用户没耐心填十几个字段。
来看一个我常用的表单结构:
<!-- templates/create_ticket.html -->
<form method="POST" action="/tickets/create">
<div class="form-group">
<label for="title">工单标题 <span class="required">*</span></label>
<input type="text" id="title" name="title"
placeholder="请简要描述问题,例如:无法登录系统"
maxlength="100" required>
</div>
<div class="form-group">
<label for="description">详细描述</label>
<textarea id="description" name="description"
rows="6"
placeholder="请详细描述您遇到的问题,包括:
1. 出现问题的具体操作步骤
2. 期望的结果是什么
3. 实际出现的结果是什么"></textarea>
</div>
<div class="form-group">
<label for="priority">优先级 <span class="required">*</span></label>
<select id="priority" name="priority" required>
<option value="">-- 请选择 --</option>
<option value="low">低 - 不影响正常使用</option>
<option value="medium" selected>中 - 有变通方案</option>
<option value="high">高 - 功能受阻</option>
<option value="urgent">紧急 - 系统不可用</option>
</select>
</div>
<button type="submit" class="btn btn-primary">提交工单</button>
</form>
这里有个细节:优先级下拉框。我见过有人用数字 1、2、3、4 来表示优先级。你想想看,用户看到「请选择优先级:1」,他哪知道 1 是最高还是最低?所以一定要用文字描述,而且每个选项后面加一句说明。这样用户一眼就能看懂。
3.2 后端路由与视图函数:接收数据的第一道关卡
前端表单写好了,接下来就是后端接收。我用 Flask 框架来演示,因为轻量、直观。
# app.py
from flask import Flask, render_template, request, redirect, url_for, flash
from datetime import datetime
app = Flask(__name__)
app.secret_key = 'your-secret-key-here' # 实际项目中别写死
@app.route('/tickets/create', methods=['GET', 'POST'])
def create_ticket():
if request.method == 'GET':
# 显示表单页面
return render_template('create_ticket.html')
# POST 请求:处理表单提交
title = request.form.get('title', '').strip()
description = request.form.get('description', '').strip()
priority = request.form.get('priority', '')
# 这里先简单打印,后面再处理入库
print(f"收到工单:标题={title}, 优先级={priority}")
return redirect(url_for('create_ticket'))
注意看,我用了 request.form.get() 而不是 request.form['title']。为什么?因为 get() 方法在字段不存在时返回空字符串,不会报错。这是我在项目中吃过亏才养成的习惯——永远假设用户可能不按套路出牌。
3.3 数据验证:别让脏数据进数据库
嗯,这里要重点讲。数据验证是工单系统的生命线。我见过一个项目,因为没做验证,数据库里存了标题为空的工单,导致后续所有查询都报错。
我个人习惯,验证分两步:前端验证和后端验证。前端验证是为了用户体验,后端验证是为了数据安全。记住:永远不要信任前端传来的数据。
来看一个完整的验证逻辑:
from flask import Flask, render_template, request, redirect, url_for, flash
@app.route('/tickets/create', methods=['GET', 'POST'])
def create_ticket():
if request.method == 'GET':
return render_template('create_ticket.html')
# 获取并清理数据
title = request.form.get('title', '').strip()
description = request.form.get('description', '').strip()
priority = request.form.get('priority', '')
# 验证数据
errors = []
# 标题验证
if not title:
errors.append('工单标题不能为空')
elif len(title) > 100:
errors.append('工单标题不能超过100个字符')
# 描述验证(可选字段,但如果有值则检查长度)
if description and len(description) > 2000:
errors.append('工单描述不能超过2000个字符')
# 优先级验证
valid_priorities = ['low', 'medium', 'high', 'urgent']
if priority not in valid_priorities:
errors.append('请选择有效的优先级')
# 如果有错误,返回表单并显示错误信息
if errors:
for error in errors:
flash(error, 'error')
return render_template('create_ticket.html',
title=title,
description=description,
priority=priority)
# 验证通过,准备入库
# ...(下一步处理)
3.4 数据入库:把工单存起来
验证通过后,就该入库了。我用 SQLite 做演示,因为零配置,适合教学。实际生产环境换成 MySQL 或 PostgreSQL 就行,逻辑一样。
先建表:
-- schema.sql
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(100) NOT NULL,
description TEXT,
priority VARCHAR(10) NOT NULL DEFAULT 'medium',
status VARCHAR(20) NOT NULL DEFAULT 'open',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
然后写入库逻辑:
import sqlite3
from datetime import datetime
def save_ticket(title, description, priority):
"""保存工单到数据库"""
conn = sqlite3.connect('tickets.db')
cursor = conn.cursor()
try:
cursor.execute('''
INSERT INTO tickets (title, description, priority, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
''', (title, description, priority, datetime.now(), datetime.now()))
conn.commit()
ticket_id = cursor.lastrowid
return ticket_id
except Exception as e:
conn.rollback()
print(f"保存工单失败:{e}")
return None
finally:
conn.close()
# 在视图函数中调用
@app.route('/tickets/create', methods=['POST'])
def create_ticket():
# ...(前面的验证代码)
# 验证通过,入库
ticket_id = save_ticket(title, description, priority)
if ticket_id:
flash('工单创建成功!', 'success')
return redirect(url_for('view_ticket', ticket_id=ticket_id))
else:
flash('工单创建失败,请稍后重试', 'error')
return render_template('create_ticket.html',
title=title,
description=description,
priority=priority)
3.5 完整流程串起来
好了,现在我们把所有环节串起来。整个流程是这样的:
- 用户访问
/tickets/create,看到表单 - 用户填写标题、描述、选择优先级,点击提交
- 前端 HTML5 验证(比如 required 属性)先做一轮检查
- 后端收到数据,做更严格的验证
- 验证通过,数据入库
- 跳转到工单详情页,告诉用户「创建成功」
这个流程看起来简单,但每一步都有坑。我建议你动手写一遍,把每个环节的边界情况都测试一下:
- 标题为空会怎样?
- 描述填了 3000 字会怎样?
- 优先级传了个「abc」会怎样?
- 数据库连接失败会怎样?
把这些情况都测一遍,你的工单创建功能就稳了。
下一章,咱们聊聊工单列表展示和分页。到时候你会看到,今天入库的数据,明天怎么优雅地展示出来。