账号切换这个功能用着太舒服了

8,706 阅读6分钟

去年进入新部门之前,自己负责的工作是做客服系统的工单工作,工单建立数据稍微复杂一点,还要一步步的节点审核,走一个完整的流程就要耗费很长时间。本来以为这是比较正常的,进入新部门后,发现这个系统有个非常好用的功能,就是连续点击右上角的头像超过10下,就会出现一个切换账号的对话框,在这里就可以快捷实现用户账号的切换。用了一段时间,发现是真的香。下面就直入正题,开始介绍。

这个账号切换功能会比较适用于一下场景:

适用场景举例

场景1:查看项目数据

  • 问题:自身账号没有项目数据,项目数据构建复杂。
  • 解决方案:通过切换到拥有项目数据的账号,可以直接查看和分析数据,无需重新构建数据集,节省时间和资源。

场景2:项目流转和审批

  • 问题:项目需要不同审批人处理,频繁沟通影响效率。
  • 解决方案:直接切换到审批人的账号进行审批处理,减少沟通成本,提高工作效率。如果系统支持数据转交功能,也可以将任务转交给自己处理,进一步简化流程。

场景3:多角色系统开发

  • 问题:开发过程中需要频繁切换角色权限,配置繁琐。
  • 解决方案:使用账号切换功能,快速在超级管理员和普通角色间切换,便于开发和测试,同时避免频繁修改角色配置。

场景4:问题定位和数据检查

  • 问题:业务、测试或UI报告问题,但数据繁杂,难以快速定位。
  • 解决方案:切换到报告问题的业务或测试账号,直接查看相关数据,快速定位问题。对于UI设计,可以切换到数据丰富的账号进行界面走查,确保设计符合实际数据展示需求。

当然适用的场景可能不仅仅是这些,适用范围会更加广泛,我只是简单介绍一些比较常用的场景。

注意

  • 账号切换并不是所有人都可以切换,只有开发人员、测试和UI人员才可以切换,生产环境更快限制会更加严格,只有个别负责人才能够账号切换,毕竟信息安全更加重要。

优劣势

这里我们介绍账号切换功能的优劣势,这里仅仅是简单介绍:

优势:

  1. 提高效率
    • 用户无需反复登录退出,即可快速切换至所需账号,节省时间成本。
  2. 提升安全性
    • 避免因频繁登录导致密码泄露的风险。

劣势:

  1. 安全性风险
    • 频繁切换账号可能会触及系统的使用限制,建议合理安排切换频率,以免造成不必要的麻烦。

具体实现

业务介绍

系统的右上角有用户的头像,每个页面都有,可以全局配置,可以通过用户连续点击10次,页面出现一个对话框,对话框里包含两个输入框,第一个是用户姓名输入框,并且右侧有个查询按钮,第二个输入框是工号,用户可以通过姓名来查询对应的工号,如果姓名对应工号为空,则提示查不到该用户,如果只有一个结果,则直接回填到工号输入框,如果有多个结果,则第二个工号输入框变为单选选择框,选择数据为姓名-工号,对应值为工号数据,姓名输入框为选填,提交时不进行参数传递,第二个工号必填,如果知道工号,也可以输入工号,直接进行账号切换。工号作为唯一参数传递给后端,请求成功后,替换本地的用户信息,重新加载页面,实现账号切换登录。

实现账号切换的具体步骤可以分为前端和后端两部分。这里我们只介绍前端部分,后端只需要提供两个接口:

  • 根据姓名查询工号的接口,毕竟工号不是很好记,可以通过姓名来查找对应的工号,如果公司大的话,同名同姓的人会比较多。
  • 第二个结果是根据工号,返回工号对应的token信息,直接替换本地的用户信息。

这里后端是实现这个功能的关键,可能有些系统的后端并没有这样的权限。

组件封装

以下是封装后的React组件代码:

import React, { useState, useEffect } from 'react';
import { Modal, Input, Select, Button, Form, Row, Col } from 'antd';
import axios from 'axios';

const SwitchAccountDialog = ({ setIsVisible, setNameCount }) => {
  const [form] = Form.useForm();
  const [searchResults, setSearchResults] = useState([]);
  const [workNumber, setWorkNumber] = useState('');

  // 重置工号信息
  const nameOnChange = (e) => {
    const value = e.target.value;
    if (value) {
      // 当姓名输入框的值变化时,重置工号信息
      setWorkNumber('');
      setSearchResults([]);
    }
  };

  const handleOk = async () => {
    try {
      const values = await form.validateFields();
      const workNo = values.workNumber.split('-')[1]; // 从"姓名-工号"中提取工号
      const response = await axios.get('/api/switch_account', { params: { workNumber: workNo } });
      if (response.data.success) {
        localStorage.setItem('token', JSON.stringify(response.data.userInfo)); // 存储用户信息到localStorage
        setNameCount(0); // 重置点击次数
        setIsVisible(false); // 关闭对话框
        window.location.reload(); // 重新加载页面
      }
    } catch (error) {
      console.error('Error:', error);
      Modal.warning({ title: '提示', content: '账号切换出错' });
    }
  };

  const handleCancel = () => {
    setNameCount(0); // 重置点击次数
    setIsVisible(false); // 关闭对话框
  };

  const handleSearch = async () => {
    const { name } = form.getFieldsValue();
    if (!name) {
      Modal.warning({ title: '提示', content: '请输入姓名' });
      return;
    }
    try {
      const response = await axios.get('/api/search', { params: { name } });
      const results = response.data;
      if (results.length === 0) {
        Modal.warning({ title: '提示', content: '查不到该用户' });
      } else {
        setSearchResults(results.map(item => ({
          key: `${item.name}-${item.work_no}`,
          value: `${item.name}-${item.work_no}`,
          label: `${item.name}-${item.work_no}`
        })));
      }
    } catch (error) {
      console.error('Error:', error);
      Modal.warning({ title: '提示', content: '查询出错' });
    }
  };

  return (
    <Modal
      title="输入信息"
      centered
      onOk={handleOk}
      onCancel={handleCancel}
      maskClosable={false}
    >
      <Form form={form} layout="vertical">
        <Row gutter={16}>
          <Col span={18}>
            <Form.Item
              name="name"
              label="姓名"
            >
              <Input onChange={nameOnChange} />
            </Form.Item>
          </Col>
          <Col span={6}>
            <Button type="primary" onClick={handleSearch} block>
              查询工号
            </Button>
          </Col>
        </Row>
        <Row gutter={16}>
          <Col span={24}>
            <Form.Item
              name="workNumber"
              label="工号"
            >
              {searchResults.length > 0 ? (
                <Select
                  value={workNumber}
                  onChange={setWorkNumber}
                  placeholder="选择工号"
                >
                  {searchResults.map(item => (
                    <Select.Option key={item.key} value={item.value}>
                      {item.label}
                    </Select.Option>
                  ))}
                </Select>
              ) : (
                <Input placeholder="请输入工号" />
              )}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    </Modal>
  );
};

export default SwitchAccountDialog;

组件使用示例

账号切换我们一般会封装在公共页面区域,以便实现全局的功能配置。

同时增加工号的权限控制和生产和其他环境的权限控制:

import React, { useState, useEffect } from 'react';
import SwitchAccountDialog from './SwitchAccountDialog';
import { Avatar } from 'antd';
import { useUser } from './UserContext'; // 引入UserContext的钩子

const isProductionEnvironment = process.env.REACT_APP_ENVIRONMENT === 'production';

const allowedWorkNumbers = isProductionEnvironment 
  ? ['123', '456', '789'] // 生产环境的权限工号
  : ['001', '002', '003']; // 其他环境的权限工号

const App = () => {
  const [isDialogVisible, setIsDialogVisible] = useState(false);
  const [clickCount, setClickCount] = useState(0);
  const { user } = useUser(); // 从UserContext获取用户信息

  const clickThreshold = 10; // 用户需要连续点击头像的次数

  useEffect(() => {
    // 当用户工号在权限数组内且点击次数达到阈值时显示对话框
    if (allowedWorkNumbers.includes(user.workNumber) && clickCount >= clickThreshold) {
      setIsDialogVisible(true);
    } else {
      setIsDialogVisible(false);
    }
  }, [user.workNumber, clickCount, allowedWorkNumbers]);

  const handleDialogCancel = () => {
    setIsDialogVisible(false);
    setClickCount(0); // 对话框关闭时重置点击次数
  };

  return (
    <div style={{ textAlign: 'center', padding: '100px' }}>
      <Avatar
        icon="user"
        style={{ cursor: 'pointer' }}
        onClick={() => setClickCount(prevCount => prevCount + 1)}
      />
      {isDialogVisible && (
        <SwitchAccountDialog
          setIsVisible={setIsDialogVisible}
          setNameCount={setClickCount}
          userWorkNumber={user.workNumber} // 传递用户工号给对话框组件
        />
      )}
    </div>
  );
};

export default App;

这样,就实现了一个完整的功能,允许用户通过连续点击头像来触发账号切换对话框,并且在对话框关闭时重置点击次数。同时,用户工号从store中获取,而不是从localStorage,提高了安全性和数据一致性。

  • 从安全性和数据一致性的角度考虑,最好从后端服务或前端的store(如Redux、Context API等)中获取用户工号,而不是从localStorage

动画.gif