dy-dashboard/routes/users.py.bak_20261005_145541
2026-10-28 11:14:17 +08:00

279 lines
10 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""用户管理路由"""
import csv, io, base64, uuid
from flask import Blueprint, request, jsonify, session, redirect, url_for, render_template
from services.db import query
from services.passwords import hash_password, validate_password
from routes.auth import login_required, role_required
users_bp = Blueprint('users', __name__)
@users_bp.route('/api/users/positions')
@login_required
def position_list():
"""获取启用的岗位列表,供用户管理中的岗位选择和筛选使用。"""
rows = query(
"SELECT position_code AS code, position_name AS name "
"FROM user_positions WHERE status=1 ORDER BY sort_order ASC, id ASC"
)
return jsonify({"code": 0, "data": rows})
@users_bp.route('/api/users/positions/create', methods=['POST'])
@login_required
def position_create():
"""新增岗位。岗位编码由系统生成,避免中文岗位名影响现有业务匹配。"""
if session.get('role') not in ('admin', 'super_admin'):
return jsonify({"code": 403, "msg": "仅管理员可新增岗位"})
data = request.get_json(silent=True) or {}
name = str(data.get('name', '')).strip()
if not name:
return jsonify({"code": 400, "msg": "岗位名称不能为空"})
if len(name) > 100:
return jsonify({"code": 400, "msg": "岗位名称不能超过100个字符"})
try:
exists = query(
"SELECT id FROM user_positions WHERE position_name=%s LIMIT 1",
(name,)
)
if exists:
return jsonify({"code": 400, "msg": "该岗位已存在"})
position_code = 'custom_' + uuid.uuid4().hex[:16]
query(
"INSERT INTO user_positions (position_code, position_name, sort_order, status) "
"VALUES (%s, %s, 0, 1)",
(position_code, name)
)
return jsonify({
"code": 0,
"msg": "岗位新增成功",
"data": {"code": position_code, "name": name}
})
except Exception as e:
return jsonify({"code": 500, "msg": str(e)})
@users_bp.route('/users')
@login_required
def users_page():
if not role_required('users'):
return redirect(url_for('auth.index'))
return render_template('base.html', page='users')
# ─── API: 用户管理 ───
@users_bp.route('/api/users/list')
@login_required
def user_list():
search = request.args.get('search', '').strip()
role = request.args.get('role', '').strip()
position = request.args.get('position', '').strip()
status = request.args.get('status', '').strip()
sort = request.args.get('sort', '').strip()
sql = "SELECT id, username, real_name, role, user_level, position, phone, email, status, last_login, created_at, is_protected FROM dashboard_users WHERE 1=1"
params = []
if search:
sql += " AND (real_name LIKE %s OR username LIKE %s OR position LIKE %s)"
params.extend(['%' + search + '%', '%' + search + '%', '%' + search + '%'])
if role:
sql += " AND role = %s"
params.append(role)
if position:
sql += " AND position = %s"
params.append(position)
if status:
sql += " AND status = %s"
params.append(int(status))
if sort == 'role_asc':
sql += " ORDER BY role ASC, id ASC"
elif sort == 'role_desc':
sql += " ORDER BY role DESC, id ASC"
else:
sql += " ORDER BY CASE WHEN role IN ('super_admin','admin') THEN 0 ELSE 1 END, FIELD(role,'super_admin','admin'), id ASC"
users = query(sql, tuple(params) if params else None)
return jsonify({"code": 0, "data": users})
@users_bp.route('/api/users/create', methods=['POST'])
@login_required
def user_create():
if session.get('role') not in ('admin', 'super_admin'):
return jsonify({"code": 403, "msg": "仅管理员可操作"})
data = request.get_json(silent=True) or {}
valid, message = validate_password(data.get('password', ''))
if not valid:
return jsonify({"code": 400, "msg": message})
try:
query("""
INSERT INTO dashboard_users (username, password, real_name, role, user_level, position, phone, email, must_change_password)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 1)
""", (
data['username'], hash_password(data['password']), data.get('real_name', ''),
data.get('role', 'viewer'), data.get('user_level', 'employee'),
data.get('position', ''), data.get('phone', ''), data.get('email', '')
))
return jsonify({"code": 0, "msg": "创建成功"})
except Exception as e:
return jsonify({"code": 500, "msg": str(e)})
@users_bp.route('/api/users/update', methods=['POST'])
@login_required
def user_update():
if session.get('role') != 'super_admin':
return jsonify({"code": 403, "msg": "权限不足,仅超级管理员可以编辑用户"})
data = request.json
user_id = data['id']
if data.get('password'):
valid, message = validate_password(data['password'])
if not valid:
return jsonify({"code": 400, "msg": message})
query("""
UPDATE dashboard_users SET real_name=%s, role=%s, user_level=%s, position=%s, phone=%s, email=%s,
status=%s, password=%s, must_change_password=1
WHERE id=%s
""", (
data.get('real_name', ''), data.get('role', 'viewer'),
data.get('user_level', 'employee'),
data.get('position', ''), data.get('phone', ''), data.get('email', ''),
int(data.get('status', 1)), hash_password(data['password']), user_id
))
else:
query("""
UPDATE dashboard_users SET real_name=%s, role=%s, user_level=%s, position=%s, phone=%s, email=%s, status=%s
WHERE id=%s
""", (
data.get('real_name', ''), data.get('role', 'viewer'),
data.get('user_level', 'employee'),
data.get('position', ''), data.get('phone', ''), data.get('email', ''),
int(data.get('status', 1)), user_id
))
return jsonify({"code": 0, "msg": "更新成功"})
@users_bp.route('/api/users/delete', methods=['POST'])
@login_required
def user_delete():
if session.get('role') not in ('admin', 'super_admin'):
return jsonify({"code": 403, "msg": "仅管理员可操作"})
user_id = request.json.get('id')
if int(user_id) == session['user_id']:
return jsonify({"code": 500, "msg": "不能删除自己"})
protected = query("SELECT is_protected FROM dashboard_users WHERE id=%s", (user_id,))
if protected and protected[0].get('is_protected') == 1:
return jsonify({"code": 500, "msg": "该用户为受保护账号,不可删除"})
query("DELETE FROM dashboard_users WHERE id=%s", (user_id,))
return jsonify({"code": 0, "msg": "删除成功"})
@users_bp.route('/api/users/template')
@login_required
def user_template():
"""下载用户导入CSV模板"""
csv_content = '\ufeff用户名,密码,真实姓名,角色,员工级别,岗位,手机,邮箱,状态\n'
csv_content += 'zhangsan,Abc123!@#45,张三,procurement_planner,employee,采购专员,13800138000,zhangsan@example.com,1\n'
csv_content += 'lisi,Xyz789$%^01,李四,admin,employee,系统管理员,13900139000,lisi@example.com,1\n'
from urllib.parse import quote
filename = '用户导入模板.csv'
encoded = quote(filename, safe='')
return csv_content, 200, {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': f"attachment; filename*=UTF-8''{encoded}"
}
@users_bp.route('/api/users/import', methods=['POST'])
@login_required
def user_import():
"""导入用户CSV"""
if session.get('role') not in ('admin', 'super_admin'):
return jsonify({"code": 403, "msg": "仅管理员可操作"})
data = request.get_json()
if not data or 'csv_base64' not in data:
return jsonify({"code": 400, "msg": "请提供CSV数据"})
raw_bytes = base64.b64decode(data['csv_base64'])
content = None
for enc in ['utf-8-sig', 'gbk', 'gb18030', 'utf-8']:
try:
decoded = raw_bytes.decode(enc)
test_reader = csv.DictReader(io.StringIO(decoded))
for test_row in test_reader:
if test_row and ('用户名' in test_row or '密码' in test_row):
content = decoded
break
break
if content:
break
except:
continue
if content is None:
content = raw_bytes.decode('utf-8', errors='replace')
reader = csv.DictReader(io.StringIO(content))
success = 0
errors = []
for i, row in enumerate(reader, start=2):
username = (row.get('用户名') or '').strip()
password = (row.get('密码') or '').strip()
if not username or not password:
errors.append(f"第{i}行:用户名和密码不能为空")
continue
valid, message = validate_password(password)
if not valid:
errors.append(f"第{i}行:{message}")
continue
try:
query(
"""INSERT INTO dashboard_users (username, password, real_name, role, user_level, position, phone, email, status, must_change_password)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, 1)""",
(
username, hash_password(password),
(row.get('真实姓名') or '').strip(),
(row.get('角色') or 'viewer').strip(),
(row.get('员工级别') or 'employee').strip(),
(row.get('岗位') or '').strip(),
(row.get('手机') or '').strip(),
(row.get('邮箱') or '').strip(),
int((row.get('状态') or '1').strip())
)
)
success += 1
except Exception as e:
errors.append(f"第{i}行({username}):{str(e)}")
return jsonify({
"code": 0,
"data": {"success": success, "errors": errors},
"msg": f"成功导入{success}个用户" + (f",{len(errors)}个失败" if errors else "")
})
@users_bp.route('/api/users/followers')
@login_required
def user_followers():
"""获取采购跟单人员列表"""
rows = query("SELECT real_name FROM dashboard_users WHERE position='procurement_follower' AND status=1 ORDER BY real_name")
return jsonify({"code": 0, "data": [r['real_name'] for r in rows]})