「前端代码简洁之路」后台系统之详情页设计

这篇具有很好参考价值的文章主要介绍了「前端代码简洁之路」后台系统之详情页设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、乱花迷人眼

我就是被迷的那双眼。有时候需求来了,用熟悉的套路进行开发,确实很节省时间也能保证功能的稳定,但是这些开发的惯性无形中阻碍了我对技术的探索。

我一直想改造详情页,解放重复功能开发的劳动力,但是详情页一眼望都是内容平铺,好像并没有什么可做的代码设计。

后来我拨开繁花,发现详情页的组件化不必想的过于复杂,后台系统风格统一即可。因为大部分的详情页面是内容的展示,偶尔会出现少量的操作功能。将风格统一的部分进行组件化处理,操作功能使用回调函数放回当前页面,避免组件里做过多的业务逻辑。看,这不就成了。

「前端代码简洁之路」后台系统之详情页设计

项目基于React框架开发的,所以代码写法是JSX语法,组件开发使用的hooks函数式组件,UI框架使用的是antd。

二、欲起高楼,先建地基

开发前进行功能设计是我逐渐养成的一个良好习惯,有时候急于开发,可能漏掉一些设计细节或者功能。这次的详情页设计主要包括四个部分,UI组件、模块划分、数据重组、操作回调。

设计的功能如下:

「前端代码简洁之路」后台系统之详情页设计

其中操作回调是为了实现功能性操作按钮的功能,比如取消操作、审核操作、查看等详情页常见的操作按钮。

三、设计实现

我捋了一下现有的业务,除了极个别的详情页设计的比较有自己的风格特点,其他基本都是包括2-n个模块展示数据,部分模块下会有操作按钮,某些模块下的某些数据项会有操作按钮,较长的页面会有快速定位导航等。

所以我会根据功能的复杂度递增,逐步的实现这个详情页UI组件。

注:前面功能实现我主要放关键代码,会把完整代码放在文章的末尾。

3.1 基础款详情页

纯展示,根据接口返回的字段,重组数据,之所以用重组 数据的方式是因为某些数据需要特殊处理,比如时间数据,需要将时间戳转成日期格式;枚举数据,需要将返回值展示为具体文字。

3.1.1 模块划分

假设当前详情页有四个模块:用户信息、订单信息、快递信息、支付信息。四个模块内容展示有相似有不同,但是依旧可以把展示方式分成两种:一排两个的平铺展示和Table表格展示。

模块划分完成之后,页面呈现在脑海中也有了大致的结构。第一个明确的设计点也就有了,既然模块展示具有相似性。我就可以把UI渲染设计成数组循环的方式。对于不同的展示方式,可以根据模块的key值去区分定义展示类型。

详情页

  • 根据模块的划分,定义dataList数组对象,后续页面渲染是使用dataList进行渲染的;
  • 设置contentType-展示形式分类变量,其值为row-平铺,table-表格。会根据contentType将模块展示成不同的形式;
  • 订单列表因为是Table格式,它的表格列的配置描述维护在常量管理文件中;
/**
* @description 详情页
*/
import React, { useState, useEffect } from 'react';
......
import { ORDER_COLUMNS } from '@/constants/detailBase';

const DetailBase = () => {
  /** @name 页面内容数组对象  */
  let dataListInit = [
    {
      key: 'userInfo', // 模块key值
      name: '用户信息', //模块标题
    },
    {
      key: 'orderList',
      name: '订单信息',
      columns: _.cloneDeep(ORDER_COLUMNS),
    },
    {
      key: 'postInfo',
      name: '快递信息',
    },
    {
      key: 'payInfo',
      name: '支付信息',
    },
  ];
  
  // 列表数据重组
  dataListInit.map(item => {
    item.list = []; // 模块展示内容数组
    item.contentType = 'row'; // 展示形式类型 row-平铺 table-表格
    // =>true: 订单信息 展示为表格
    if (item.key === 'orderList') {
      item.contentType = 'table';
    }
  });
  let [dataList, setDataList] = useState(dataListInit);
  return <></>;
};
export default DetailBase;

详情页常量

对于常量管理,一般会放到常量文件中。

3.1.2 数据重组

  • 请求详情数据,获得返回值。一般返回值都是嵌套对象的格式,所以可以将返回值的对象key值和设置的dataList中key一一对应;
  • 根据模块设置模块的list值,最终页面渲染使用的是每个模块的list对象。contentType类型为table时,可以直接将返回值赋值给该模块的list变量;contentType类型为row时,需要进行数据的重组。(注:之所以需要重组数据是因为要特殊处理时间戳、枚举值等特殊返回值,比如时间戳要展示为日期格式,枚举值根据返回值展示文字描述等);
/**
 * @description 详情页
 */
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
  ......
  /**
   * 用户信息-展示数据重组
   * @param {Object} data 需要获取的项的对象
   * @return {Object} 获得的值
   */
  const getUserData = data => {
    let list = [
      {
        name: '姓名',
        value: data.name,
      },
      {
        name: '年龄',
        value: data.age,
      },
      {
        name: '电话',
        value: data.phone,
      },
      {
        name: '收货地址',
        value: data.address,
      },
    ];
    return list;
  };

  /**
   * 快递信息-展示数据重组
   * @param {Object} data 需要获取的项的对象
   * @return {Object} 获得的值
   */
  const getPostData = data => {
    let list = [
      {
        name: '付款单号',
        value: data.postNum,
      },
      {
        name: '付款公司',
        value: data.postName,
      },
    ];
    return list;
  };

  /**
   * 支付信息-展示数据重组
   * @param {Object} data 需要获取的项的对象
   * @return {Object} 获得的值
   */
  const getPayData = data => {
    let list = [
      {
        name: '付款时间',
        value: data.payAt ? moment(data.payAt).format('YYYY-MM-DD HH:mm:ss') : '',
      },
      {
        name: '付款金额',
        value: data.payMoney,
      },
      {
        name: '操作时间',
        value: data.payOperateAt ? moment(data.payOperateAt).format('YYYY-MM-DD HH:mm:ss') : '',
      },
    ];
    return list;
  };

  /**
   * 获取列表项的实际值
   * @param {Object} item 需要获取的项的对象
   * @param {Object} res 接口请求数据
   * @return {Object} 获得的值
   */
  const getItemList = (item, data) => {
    let obj = {
      userInfo: getUserData(data),
      postInfo: getPostData(data),
      payInfo: getPayData(data),
    };
    return obj[item.key];
  };

  /**
   * 初始化数据
   */
  const initData = () => {
    // 请求接口获取返回值
    let res = {
      userInfo: {
        name: '张三',
        age: 30,
        phone: '12345678912',
        address: '北京市朝阳区',
      },
      payInfo: {
        payAt: 1641039600000,
        payMoney: 999,
        payOperateAt: 1641038400000,
      },
      orderList: [
        {
          name: '跑鞋·追光者',
          color: '白色',
          creatAt: 1641038400000,
          payAt: 1641039600000,
          haveFreight: 1,
        },
        {
          name: '运动裤·逐梦',
          color: '黑色',
          creatAt: 1641038400000,
          payAt: 1641039600000,
          haveFreight: 1,
        },
        {
          name: '外套·闪光者',
          color: '蓝色',
          creatAt: 1641038400000,
          payAt: 1641039600000,
          haveFreight: 1,
        },
      ],
      postInfo: {
        postName: '顺丰',
        postNum: '1111',
      },
    };
    let list = _.cloneDeep(dataListInit);
    // 数据重置
    list.map(item => {
      if (item.contentType === 'table') {
        item.list = res[item.key];
      } else {
        let data = res[item.key];
        item.list = getItemList(item, data);
      }
    });
    setDataList(list);
  };

  useEffect(() => {
    initData();
  }, []);

  return <></>;
};

export default DetailBase;

3.1.3 详情组件

因为是根据业务进行的功能设计,所以我把详情组件放到了业务组件下面。

  • 模块的展示,使用antd提供的Card卡片组件进行页面布局;Card卡片官网地址;
  • row平布类型的展示,使用antd提供的Row、Col栅格组件进行页面布局;Row、Col栅格组件官网地址;
  • table类型的展示,使用ante提供的Table组件进行页面布局;Table组件官网地址;
  • 组件通信,props传参为dataList数据数组对象;
  • 注:像边距mt/mb之类的样式设置,我们的项目里面是定义的全局样式,直接使用的。
/**
 * @description 公共业务组件-详情
 */
import React from 'react';
import PropTypes from 'prop-types';
import { Card, Row, Col, Table } from 'antd';

const CommonDetailBase = ({ ...props }) => {
  const { dataList } = props;

  /**
   * row类页面内容回显
   * @param {Object} data 展示内容对象
   * @return {Element} 展示内容
   */
  const dataRowContent = data => {
    const list = data.list ? data.list : [];
    return (
      <>
        {list.map((rowItem, rowIndex) => {
          return (
            <Col span={12} key={rowIndex}>
              <Card size='small'>
                <div>
                  {rowItem.name}:{rowItem.value}
                </div>
              </Card>
            </Col>
          );
        })}
      </>
    );
  };

  /**
   * Table类页面内容回显
   * @param {Object} item 展示内容对象
   * @return {Node} 展示内容
   */
  const dataTableContent = item => {
    let list = item.list ? item.list : [];
    return <Table dataSource={list} columns={item.columns} rowKey={record => record.id} pagination={false} size='small' />;
  };

  return (
    <div>
      <div className='view-content'>
        {dataList.map(item => {
          return (
            <Card type='inner' title={item.name} id={item.key} key={item.key} className='mb20'>
              {item.contentType === 'row' ? <Row gutter={[12, 12]}>{dataRowContent(item)}</Row> : null}
              {item.contentType === 'table' ? dataTableContent(item) : null}
              {item.moduleBottomName ? (
                <Button type='primary' onClick={() => item.moduleBottomView(item)} className='mt20'>
                  {item.moduleBottomName}
                </Button>
              ) : null}
            </Card>
          );
        })}
      </div>
    </div>
  );
};

CommonDetailBase.propTypes = {
  dataList: PropTypes.array, // 页面展示数组对象
};

CommonDetailBase.defaultProps = {
  dataList: [],
};

export default CommonDetailBase;

页面使用

/**
 * @description 详情页
 */
import React, { useState, useEffect } from 'react';
......
// 引入组件
import { CommonDetailBase } from '@/bundleComponents';
......
const DetailBase = () => {
  return <CommonDetailBase dataList={dataList} />;
};

export default DetailBase;

3.2 升级款详情页

所谓升级款,即在原来的基础上功能更丰富。比如我们的业务需求,模块下面会跟着操作按钮,页面底部会有操作按钮,页面带导航条。以及如果我们想组件功能更强,需要支持的情况更多,可以支持某个模块自定义展示。这个时候需要在原来的基础上进行功能扩展.

3.2.1 详情组件

详情组件已开发好了,新增功能只需要在原来的基础上新增代码逻辑即可

  • 导航条,使用antd提供的Affix固钉组件,Affix固钉官网地址;
    • affixTabs:导航条数据对象,数组类型
    • afffixIndex:当前选中导航变量,字符串类型
  • 模块可以使用自定义展示,在模块代码中加入children变量的判断,如果存在,则展示children内容,如果不存在,则按照组件中的展示;
  • 模块底部可以添加操作按钮,支持按钮组,根据moduleBottomList数组变量的值判断,如果有值,则循环展示按钮组,如果不存在,则不展示;
  • 数据项可以使用自定义展示,在数据项代码中加入children变量的判断,如果存在,则展示children内容,如果不存在,则按照组件中的展示;
  • 数据项左侧可以添加操作按钮,支持按钮组,根据colBtnList数组变量的值判断,如果有值,则循环展示按钮组,如果不存在,则不展示;
/**
 * @description 公共业务组件-详情
 */
import React, { useState } from 'react';
......
const CommonDetailBase = ({ ...props }) => {
  const { dataList, affixTabs } = props;

  /** @name 当前所在导航index值  */
  const [afffixIndex, setAfffixIndex] = useState(props.afffixIndex);

  /** @name 是否展示导航 */
  const hasAffixTabs = !!affixTabs;

  /**
   * 快速定位方法
   * @param {string} id 定位到的id值
   * @return {void} 无
   */
  const fastGo = id => {
    let element = document.getElementById(id);
    let view = document.querySelector('.view');
    view.scrollTo({
      top: element.offsetTop - 90,
    });
  };

  /**
   * 右侧锚点导航-切换
   * @param {Object} item 选择的导航标签
   * @return {void} 无
   */
  const tabChange = item => {
    setAfffixIndex(item.key);
    fastGo(item.key);
  };

  /**
   * row类页面内容回显
   * @param {Object} data 展示内容对象
   * @return {Element} 展示内容
   */
  const dataRowContent = data => {
    const list = data.list ? data.list : [];
    return (
      <>
        {list.map((rowItem, rowIndex) => {
          return (
            <Col span={12} key={rowIndex}>
              <Card size='small'>
                {rowItem.children ? (
                  <>{rowItem.children}</>
                ) : (
                  <div>
                    {rowItem.name}:{rowItem.value}
                    {rowItem.colBtnList &&
                      rowItem.colBtnList.map((colBtnItem, colBtnIndex) => {
                        return (
                          <Button className='ml10' type='primary' onClick={() => colBtnItem.colBtnCallback(colBtnItem, rowItem)} key={colBtnIndex}>
                            {colBtnItem.name}
                          </Button>
                        );
                      })}
                  </div>
                )}
              </Card>
            </Col>
          );
        })}
      </>
    );
  };

  /**
   * Table类页面内容回显
   * @param {Object} item 展示内容对象
   * @return {Node} 展示内容
   */
  const dataTableContent = item => {
    let list = item.list ? item.list : [];
    return <Table dataSource={list} columns={item.columns} rowKey={record => record.id} pagination={false} size='small' />;
  };

  return (
    <div className={style['detail-base']}>
      <div className='view-content' id='view'>
        {dataList.map(item => {
          return (
            <Card type='inner' title={item.name} id={item.key} key={item.key} className='mb20'>
              {item.children ? (
                <>{item.children}</>
              ) : (
                <>
                  {item.contentType === 'row' ? <Row gutter={[12, 12]}>{dataRowContent(item)}</Row> : null}
                  {item.contentType === 'table' ? dataTableContent(item) : null}
                </>
              )}
              {item.moduleBottomList &&
                item.moduleBottomList.map((moduleBtnItem, moduleBtnIndex) => {
                  return (
                    <Button className='mr10 mt20' type='primary' onClick={() => moduleBtnItem.moduleBtnCallback(moduleBtnItem, item)} key={moduleBtnIndex}>
                      {moduleBtnItem.name}
                    </Button>
                  );
                })}
            </Card>
          );
        })}
        {/* 右侧锚点导航 */}
        {hasAffixTabs ? (
          <Affix offsetTop={120} className='sider-affix'>
            <ul className='affix'>
              {affixTabs.map((item, index) => (
                <li key={index}>
                  <div className={classnames('affix-item', { current: afffixIndex === item.key })} onClick={() => tabChange(item)}>
                    {item.name}
                  </div>
                </li>
              ))}
            </ul>
          </Affix>
        ) : null}
      </div>
    </div>
  );
};

CommonDetailBase.propTypes = {
  dataList: PropTypes.array, // 页面展示数组对象
  affixTabs: PropTypes.array, // 导航数组对象
  afffixIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // 导航默认定位
};

CommonDetailBase.defaultProps = {
  dataList: [],
};

export default CommonDetailBase;

3.2.2 导航条

  • 导航条数据可以直接使用页面列表数据,因为定位的key和页面列表的key值做了一致性的处理;
  • 通过设置afffixIndex的值,可以控制当前导航固定的位置;
  • 当子组件的props传参比较复杂的时候,可以设置一个config对象,比如detailConfig包含了所有props,子组件使用时直接用...(扩展运算符)进行展开;
/**
 * @description 详情页
 */
import React, { useState, useEffect } from 'react';
......
import { CommonDetailBase } from '@/bundleComponents';
const DetailBase = () => {
  /** @name 页面导航  */
  const affixTabs = [];
  ......
  // 列表数据重组
  dataListInit.map(item => {
    ......
    // 设置导航条数据
    affixTabs.push(item);
  });
  ......
  /** @name 详情组件配置项  */
  const detailConfig = {
    afffixIndex: 'userInfo',
    affixTabs: affixTabs,
    dataList: dataList,
  };

  return (
    <div>
      <CommonDetailBase {...detailConfig} />
    </div>
  );
};
export default DetailBase;

3.2.3 模块下的操作按钮

  • moduleBottomList:模块下的按钮组数组变量,控制操作按钮组是否展示,当它有值时按钮展示,没值时按钮不展示;
  • moduleBottomCallback:操作按钮元素的操作回调函数,可以做一些操作处理;
/**
 * @description 详情页
 */
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
  ......
  let dataListInit = [
    ......
    {
      key: 'postInfo',
      name: '快递信息',
    },
    ......
  ];
  ......
  const initData = () => {
    let list = _.cloneDeep(dataListInit);
      list.map(item => {
      ......
      // =>true: 快递信息 表格项处理
      if (item.key === 'postInfo') {
        item.moduleBottomList = [
          {
            name: '快递详情',
            moduleBtnCallback: (_, data) => moduleBottomCallback(data, res),
          },
          {
            name: '快递详情2',
            moduleBtnCallback: (_, data) => moduleBottomCallback(data, res),
          },
        ];
      }
    })
  }
  ......
};
export default DetailBase;

3.2.4 数据项的操作按钮

  • colBtnList:数据项的操作按钮组,控制操作按钮是否展示,当它有值时按钮展示,没值时按钮不展示;
  • colBtnCallback:操作按钮元素的操作回调函数,可以做一些操作处理;
/**
 * @description 详情页
 */
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
  ......
  const getUserData = data => {
    let list = [
      ......
      {
        name: '收货地址',
        value: data.address,
        colBtnList: [
          {
            name: '地址详情',
            colBtnCallback: () => {
              window.open('https://juejin.cn/', '_blank');
            },
          },
        ],
      },
      .......
    ];
    return list;
  };
  ......
};
export default DetailBase;

3.2.5 模块为自定义展示

将需要自定义展示的模块对象的children值设置为需要展示的内容即可

/**
 * @description 详情页
 */
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
  ......
  /**
  * 支付模块展示
  * @param {Object} dafa 展示的数据对象
  */
  const getPayInfo = data => {
    return (
      <Row gutter={[8, 8]}>
        {data.list.map((item, index) => {
          return (
            <Col span={24} key={index}>
              {item.name}:{item.value}
            </Col>
          );
        })}
      </Row>
    );
  };
  ......
  const initData = () => {
    ......
    let list = _.cloneDeep(dataListInit);
    list.map(item => {
    ......
      // =>true: 支付信息 自定义展示
      if (item.key === 'payInfo') {
        item.children = getPayInfo(item);
      }
    });
    setDataList(list);
  };
  ......
};
export default DetailBase;

3.2.6 数据项为自定义展示

将需要自定义展示的模块下的数据项对象的children值设置为需要展示的内容即可

/**
 * @description 详情页
 */
import React, { useState, useEffect } from 'react';
......
const DetailBase = () => {
  ......
  /**
  * 图片类型展示
  * @param {Object} data 展示的数据对象
  */
  const getImageView = data => {
    return (
      <>
        头像:<Button type="link">编辑</Button>
        <Row gutter={(12, 12)}>
          <Col span={4}>
            <img style={{ width: '80px', height: '80px', display: 'block' }} src={data.headImage} />
          </Col>
        </Row>
      </>
    );
  };
  ......
  const getUserData = data => {
    let list = [
      ......
      {
        name: '头像',
        children: getImageView(data),
      },
    ];
    return list;
  };
  ......
};
export default DetailBase;

以上,一个功能相对全面的详情页组件就完成了。

3.3 完整代码

3.3.1 详情页组件

/**
 * @description 公共业务组件-详情
 */
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Button, Card, Row, Col, Table, Affix } from 'antd';
import style from './style';

const CommonDetailBase = ({ ...props }) => {
  const { dataList, affixTabs } = props;

  /** @name 当前所在导航index值  */
  const [afffixIndex, setAfffixIndex] = useState(props.afffixIndex);

  /** @name 是否展示导航 */
  const hasAffixTabs = !!affixTabs;

  /**
   * 快速定位方法
   * @param {string} id 定位到的id值
   * @return {void} 无
   */
  const fastGo = id => {
    let element = document.getElementById(id);
    let view = document.querySelector('.view');
    view.scrollTo({
      top: element.offsetTop - 90,
    });
  };

  /**
   * 右侧锚点导航-切换
   * @param {Object} item 选择的导航标签
   * @return {void} 无
   */
  const tabChange = item => {
    setAfffixIndex(item.key);
    fastGo(item.key);
  };

  /**
   * row类页面内容回显
   * @param {Object} data 展示内容对象
   * @return {Element} 展示内容
   */
  const dataRowContent = data => {
    const list = data.list ? data.list : [];
    return (
      <>
        {list.map((rowItem, rowIndex) => {
          return (
            <Col span={12} key={rowIndex}>
              <Card size='small'>
                {rowItem.children ? (
                  <>{rowItem.children}</>
                ) : (
                  <div>
                    {rowItem.name}:{rowItem.value}
                    {rowItem.colBtnList &&
                      rowItem.colBtnList.map((colBtnItem, colBtnIndex) => {
                        return (
                          <Button className='ml10' type='primary' onClick={() => colBtnItem.colBtnCallback(colBtnItem, rowItem)} key={colBtnIndex}>
                            {colBtnItem.name}
                          </Button>
                        );
                      })}
                  </div>
                )}
              </Card>
            </Col>
          );
        })}
      </>
    );
  };

  /**
   * Table类页面内容回显
   * @param {Object} item 展示内容对象
   * @return {Node} 展示内容
   */
  const dataTableContent = item => {
    let list = item.list ? item.list : [];
    return <Table dataSource={list} columns={item.columns} rowKey={record => record.id} pagination={false} size='small' />;
  };

  return (
    <div className={style['detail-base']}>
      <div className='view-content' id='view'>
        {dataList.map(item => {
          return (
            <Card type='inner' title={item.name} id={item.key} key={item.key} className='mb20'>
              {item.children ? (
                <>{item.children}</>
              ) : (
                <>
                  {item.contentType === 'row' ? <Row gutter={[12, 12]}>{dataRowContent(item)}</Row> : null}
                  {item.contentType === 'table' ? dataTableContent(item) : null}
                </>
              )}
              {item.moduleBottomList &&
                item.moduleBottomList.map((moduleBtnItem, moduleBtnIndex) => {
                  return (
                    <Button className='mr10 mt20' type='primary' onClick={() => moduleBtnItem.moduleBtnCallback(moduleBtnItem, item)} key={moduleBtnIndex}>
                      {moduleBtnItem.name}
                    </Button>
                  );
                })}
            </Card>
          );
        })}
        {/* 右侧锚点导航 */}
        {hasAffixTabs ? (
          <Affix offsetTop={120} className='sider-affix'>
            <ul className='affix'>
              {affixTabs.map((item, index) => (
                <li key={index}>
                  <div className={classnames('affix-item', { current: afffixIndex === item.key })} onClick={() => tabChange(item)}>
                    {item.name}
                  </div>
                </li>
              ))}
            </ul>
          </Affix>
        ) : null}
      </div>
    </div>
  );
};

CommonDetailBase.propTypes = {
  dataList: PropTypes.array, // 页面展示数组对象
  affixTabs: PropTypes.array, // 导航数组对象
  afffixIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // 导航默认定位
};

CommonDetailBase.defaultProps = {
  dataList: [],
};

export default CommonDetailBase;

组件样式

:local(.detail-base) {
  position: relative;
  padding-left: 20px;
  background: #fff;

  .view-content {
    padding-top: 20px;
    padding-right: 120px;
    padding-bottom: 80px;
  }

  .sider-affix {
    position: fixed;
    top: 50px;
    right: 10px;

    .affix {
      padding-left: 0;
      margin: 16px 0;
      font-size: 12px;
      list-style: none;
      border-left: 1px solid #f0f0f0;

      li {
        padding-left: 0;
        margin-left: 0;
        line-height: 2;
        list-style: none;
      }

      .affix-item {
        display: block;
        width: 110px;
        padding-left: 16px;
        margin-left: -1px;
        overflow: hidden;
        font-size: 16px;
        color: rgba(0, 0, 0, 0.85);
        text-overflow: ellipsis;
        white-space: nowrap;
        cursor: pointer;
        border-left: 1px solid transparent;

        &.current {
          color: #1890ff;
          border-color: #1890ff;
        }
      }
    }
  }
}

3.3.3 完整UI

「前端代码简洁之路」后台系统之详情页设计

四、总结

对后台系统代码简洁之路,仍在探索中,后续想实现列表页的操作表单项的设计,这样后台系统的基础的页面能快速完成搭建。

当然了,更好的方式是搭建低代码平台,但是现有的开发精力并不能支撑完成这种复杂的开发。所以先从基础出发,逐步升级自己的技能。文章来源地址https://www.toymoban.com/news/detail-456857.html

到了这里,关于「前端代码简洁之路」后台系统之详情页设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 低代码-详情页组件设计

    进表单初始化,只展示表单属性,隐藏通用、数据、事件tab项。 配置Properties: 我将此配置放置于Properties组件中,触发条件为: 监听到的选中组件(selectItem)为空就触发 做好上述初始化后,便要专注于表单属性的配置了,以下列出需要的详情页属性配置: 详情页标题 详情页

    2024年01月17日
    浏览(31)
  • 43--Django-项目实战-全栈开发-基于django+drf+vue+elementUI企业级项目开发流程-课程详情页面、搜索页面以及后台设计

    1.视频播放功能 下载第三方插件:vue-video-player 使用步骤: 第一步: cnpm install vue-video-player 第二步:在main.js中引入

    2024年02月09日
    浏览(54)
  • 前后端接口设计与配置中心系统<二十七>-------前端-管理后台设计实现2【导航架构模块设计与实现、基于react-router-dom实现路由模块、网络模块封装与service层实现】

    在上一次前后端接口设计与配置中心系统二十七-------前端-管理后台设计实现1【基于create-react-app搭建web工程、整合antd与less并搭建具有Ant Design风格的页面、页面结构设计与框架搭建】搭建了前端管理后台的基本框架,接着往下继续开撸,接下来则来搭建一下左侧菜单模块了。

    2023年04月09日
    浏览(44)
  • 一个简洁、干净的中后台管理模板

    大家好,我是 Java陈序员 。 今天,给大家介绍一个简洁、开源的中后台管理模板项目。 关注微信公众号:【Java陈序员】,获取 开源项目分享、AI副业分享、超200本经典计算机电子书籍等。 nova-admin —— 一个基于Vue3、Vite5、Typescript、Naive UI, 简洁干净后台管理模板。 nova-adm

    2024年04月27日
    浏览(27)
  • 【读书后台管理系统】—前端框架搭建(一)

    注:该项目是基于vue-element-admin下进行的 删除 src/views 下的源码,保留: dashboard:首页 error-page:异常页面 login:登录 redirect:重定向 对 src/router/index 进行相应修改 删除 src/router/modules 文件夹 删除 src/vendor 文件夹 通过 src/settings.js 进行全局配置: title:站点标题 showSettings:

    2024年02月07日
    浏览(31)
  • 尚融宝13-后台管理系统前端架构梳理

    目录 一、程序入口 (一)入口页面 index.html (二) 入口js脚本:src/main.js (三)顶层组件:src/App.vue (四)路由:src/router/index.js  查看源代码 这正是srb-admin/public/index.html    我们进入积分等级列表,查看源代码会发现仍然是index.html中的代码  那么它是怎么实现页面的不同

    2023年04月11日
    浏览(40)
  • Vue + ElementUI 实现后台管理系统模板 -- 前端篇

    使用场景分析: Home.vue 页面中,Header 部分有个折叠按钮,点击之后,可以折叠与展开 Aside 组件,这之间就设计到数据在组件间的共享。 使用 vuex 后,数据统一管理,当数据发生变化时,其所有引用的地方均会修改。 2、安装、模块化使用 vuex 1》安装 项目构建时,已经安装

    2024年04月11日
    浏览(42)
  • RuoYi-Vue-generator 代码生成模块 动态 多数据源切换 前端+后台

    需求场景: 若依框架的30张数据表 和 业务使用的数据表,同数据源,但分开的两个库,原生若依只支持主库的代码生成,故自己修改添加代码来实现 若依多数据源的使用 修改页面ruoyi-uisrcviewstoolgenimportTable.vue el-form 中新增 el-form-item 添加 data 添加方法 ruoyi-uisrcapitool

    2024年02月04日
    浏览(44)
  • 管理后台项目-05-SKU列表-上下架-详情抽屉效果-深度选择器

    目录 1-SKU列表 2-SKU上下架  3-SKU详情 当用户点击Sku管理,组件挂载的时候,我们需要获取sku列表信息;但是获取列表方法在分页列表改变页码和每页显示大小的时候也需要触发,我们封装为一个方法。 //sku列表的接口 /admin/product/list/{page}/{limit} export const reqSkuList = (page,limit)=

    2023年04月24日
    浏览(38)
  • Velocity 不用愁!Velocity 系统的前端工程化之路

    Velocity是一个基于Java的Web页面模版引擎。十多年前,Velocity将Java代码从Web页面中分离出来,使得开发者能够并行网页开发和Java开发。随着十年前后端分离的浪潮涌动,回首再面对这些基于Velocity的旧系统,无论是后端还是前端人员维护,都会存在诸多问题: (1)后端人员维

    2024年02月06日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包