React页面设计初体验

2023-11-19

1、定制路由

export default [
  //login
  {
    path: '/login',
    name: 'login',
    component: '../layouts/BlankLayout',
    routes: [
      { 
        path: '/login', 
        component: './Login/Index',
      }
    ]
  },
  // app
  {
    path: '/',
    component: '../layouts/BasicLayout',
    Routes: ['src/pages/Authorized'],
    authority: 'admin',
    routes: [
      {
        path: '/',
        redirect: '/system/user',
      },
      {
        path: '/system',
        name: 'system',
        routes: [
          {
            path: '/system/user',
            name: 'user',
            component: './System/User/Index',
          },
          {
            path: '/system/menu',
            name: 'menu',
            component: './System/Menu/Index',
          },
          {
            path: '/system/permission',
            name: 'permission',
            component: './System/Permission/Index',
          },
          {
            path: '/system/role',
            name: 'role',
            component: './System/Role/Index',
          },
        ],
      },
      {
        path: '/operation',
        name: 'operation',
        routes:[
          {
            path: '/operation/log',
            name: 'log',
            component: './Operation/Log/Index'
          },
          {
            path: '/operation/article',
            name: 'article',
            routes:[
              {
                path: '/operation/article',
                component: './Operation/Article/Index'
              },{
                path: '/operation/article/:id',
                name: 'edit',
                component: './Operation/Article/Edit'
              }
            ]
          },
          {
            path: '/operation/sqlmonitor',
            name: 'sqlmonitor',
            component: './Operation/SqlMonitor/Index'
          }
        ]
      },
      {
        component: '404',
      },
    ],
  },
];

2、定制API

/**
 * 这里就类似于Vue的api,这些api将在pages的各模块models引入
 * 也是与调用后端的URI相匹配的
 */
import { async } from "q";
// 引入request请求工具,Vue中同样也对request做了封装
import request, { download } from '@/utils/request';
import qs from 'qs';

// 这样定义可重用方便维护
const API_BASE = "/antdsp-api";
const USER_API = API_BASE + "/system/user";
const MENU_API = API_BASE + "/system/menu";
const ROLE_API = API_BASE + "/system/role";
const PERMISSION_API = API_BASE + "/system/permission";

export async function fetchAllUser(params){
    return request(`${USER_API}?${qs.stringify(params, { indices: false })}`);
}

/**
 * 添加用户
 *
 * URI = URI前缀(较固定,与后端mapping相匹配) + method(POST、PUT、DELETE、GET) + data
 * 最基本的必须有 URI前缀
 *
 * @param params 参数
 * @returns {Promise<void>}
 */
export async function addUser(params){
    return request(`${USER_API}`,{
        method: 'POST',
        data:{
            ...params
        }
    });
}

export async function updateUser(params){
    const userId = params.user.id;
    return request(`${USER_API}/${userId}`,{
        method: 'PUT',
        data:{
            ...params
        }
    });
}

export async function delUserById(params){
    return request(`${USER_API}/${params.id}`,{
        method: 'DELETE',
    });
}

export async function fetchUser(params){
    return request(`${USER_API}/${params.id}`,{
        method: 'GET'
    });
}

export async function fetchAllMenu(){
    return request(`${MENU_API}`)
}

export async function addMenu(params){

    return request(`${MENU_API}`,{
        method: 'POST',
        data:{
            ...params
        }
    });
}
export async function updateMenu(params){
    return request(`${MENU_API}`,{
        method: 'PUT',
        data:{
            ...params
        }
    });
}

export async function delMenuById(param){
    return request(`${MENU_API}/${param.id}`,{
        method: 'DELETE',
    })
}

export async function fetchAllRole(param){
    return request(`${ROLE_API}?${qs.stringify(param, { indices: false })}`)
}


export async function addRole(params){
    return request(`${ROLE_API}`,{
        method: 'POST',
        data: {
            ...params
        },
    })
}
export async function updateRole(params){
    return request(`${ROLE_API}`,{
        method: 'PUT',
        data: {
            ...params
        }
    });
}

export async function delRoleById(param){
    return request(`${ROLE_API}/${param.id}`,{
        method: 'DELETE'
    })
}

export async function fetchRoleById(param){
    return request(`${ROLE_API}/${param.id}`);
}

export async function queryRoles(){
    return request(`${ROLE_API}/queryRoleNameAndIds`);
}

export async function queryRoleMenus(){
    return request(`${MENU_API}/route`);
}

export async function fetchAllLog(param){
    return request(`${API_BASE}/operation/log?${qs.stringify(param, { indices: false })}`);
}

export async function exportLog(params){
    return download(`${API_BASE}/operation/log/export?${qs.stringify(params, { indices: false })}`)
}

export async function fetchAllPermission(params){
  return request(`${PERMISSION_API}?${qs.stringify(params, { indices: false })}`);
}

3、定制API与页面中间层permission.js

​
import {
  fetchAllPermission
} from '@/services/system';// 引入API

export default {

  namespace: 'systempermission',

  // 有点类似Vue的data
  state: {
    PermissionList:{
      data:[],
      pagination:{}
    }
  },

  effects: {
    *fetchAll({payload} , {call , put}){
      const response = yield call(fetchAllPermission, payload);
      yield put({
        type:'reducersPermissionList',
        payload: response,
      })
    }
  },

  reducers: {
    reducersPermissionList(state , action){
      return {
        ...state,
        PermissionList:{
          ...action.payload
        }
      }
    }
  }
}

​

4、定制页面index.js

import { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import Block from '@/custom/Block';
import {
  Card,
  Form,
  Button,
  Table,
  Divider,
  Input,
  Select,
  Modal,
  message,
  Popconfirm,
  Checkbox
} from 'antd'; // 引入antd组件
import UnipicUpload from '@/custom/UnipicUpload';
import AntdspConfig from '@/AntdspConfig'

const FormItem = Form.Item;

const UserStatus = {
  NORMAL: '正常',
  FORBIDDEN: '异常',
};

@connect(({ systempermission, loading }) => ({
  systempermission,
  loading: loading.models.systempermission,
}))
/**
 * PureComponent的子组件一般由3大部分构成:数据、事件、render()渲染
 */
@Form.create()// 创建表单,antd语法
export default class extends PureComponent {
  state = {
    confirmLoading: false,
    visible: false,
    current: {},
    formValue: {
      page: 1,
      count: 10,
    },
  };

  componentDidMount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'systempermission/fetchAll',
      payload: {
        page: 1,
        count: 10,
      },
    });
  }

  // 条件查询事件
  handlerQueryOnClick = () => {
    const { form, dispatch } = this.props; // 从this.props取出form、dispatch
    form.validateFields((err, fieldsValue) => {
      let { formValue } = this.state.formValue;
      formValue = {
        ...fieldsValue,
        page: 1,
        count: 10,
      };
      this.setState({ formValue });// 更新state的formValue
      // 调用user.js里面的*fetchAll
      dispatch({
        type: 'systempermission/fetchAll',
        payload: {
          ...formValue,
        },
      });
    });
  };

  renderQueryForm = () => {
    const { getFieldDecorator } = this.props.form;

    return (
      <Block>
        <Form layout="inline">
          <FormItem label="登录名">
            {getFieldDecorator('loginname')(<Input placeholder={'请输入登录名'} />)}
          </FormItem>
          <FormItem label="状态">
            {getFieldDecorator('status')(
              <Select style={{ width: '120px' }} placeholder="请选择状态">
                <Select.Option value={'NORMAL'}>正常</Select.Option>
                <Select.Option value={'FORBIDDEN'}>异常</Select.Option>
              </Select>
            )}
          </FormItem>
          <FormItem>
            <Button type="primary" onClick={this.handlerQueryOnClick}>
              查询
            </Button>
          </FormItem>
        </Form>
      </Block>
    );
  };

  handlerTableOnChange = (pagination, filters, sorter) => {
    const { dispatch } = this.props;
    let { formValue } = this.state;

    formValue = {
      ...formValue,
      page: pagination.current,
      count: pagination.pageSize,
    };

    this.setState({
      formValue: {
        ...formValue,
      },
    });

    dispatch({
      type: 'systempermission/fetchAll',
      payload: {
        ...formValue,
      },
    });
  };
  // 显示弹窗
  handlerModalOnOk = e => {
    e.preventDefault();
  };

  // 删除事件

  // 渲染列表。数据、columns定义、return里面写html
  render() {
    const {
      systempermission: { PermissionList, detail },  // 命名空间:{ 对象1, 对象2, ......}
      loading,
    } = this.props;// 从user.js取出数据

    const columns = [
      {
        title: 'ID',
        dataIndex: 'id',
      },
      {
        title: '描述',
        dataIndex: 'desc',
      },
      {
        title: '创建时间',
        dataIndex: 'created',
      }
    ];

    return (
      <PageHeaderWrapper title="用户管理">
        <Card>
          {this.renderQueryForm()}

          <Block>
            <Button
              icon="plus"
              type="primary"
              onClick={(e) => {
                this.showModal(e , {});
              }}
            >
              新增
            </Button>
          </Block>
          <Table
            columns={columns}
            rowKey={'id'}
            dataSource={PermissionList.data}
            pagination={PermissionList.pagination}
            onChange={this.handlerTableOnChange}
            loading={loading}
          />
          <Modal
            title="编辑用户信息"
            centered={true}
            maskClosable={true}
            visible={this.state.visible}
            onCancel={() => {
              this.setState({ visible: false });
            }}
            onOk={this.handlerModalOnOk}
            confirmLoading={this.state.confirmLoading}
            destroyOnClose={true}
          >
            <EditUser
              wrappedComponentRef={formRef => (this.formRef = formRef)}
              detail={detail}
            />
          </Modal>
        </Card>
      </PageHeaderWrapper>
    );
  }
}

@Form.create() // 弹窗组件
export class EditUser extends PureComponent {
  // 比对密码事件
  compareToPassword = (rule, value, callback) => {
    const form = this.props.form;
    if (value && value !== form.getFieldValue('password')) {
      callback('两次密码输入不一致');
    } else {
      callback();
    }
  };

  handlerUploadOnChange=(imageurl)=>{
    console.log(imageurl);
    this.setState({
      avatar: imageurl
    })
  }

  roleToNameId(roleNameId){

    let newRoleNameId=[];

    roleNameId.map((item=>{
      let nameid = {
        label: item.roleName,
        value: item.id,
      }
      newRoleNameId.push(nameid);
    }));
    return newRoleNameId;
  }

  render() {
    const { getFieldDecorator } = this.props.form;
    const { detail: { user , roleIds , roles} } = this.props;

    const flag = Object.keys(user).length == 0;

    const formItemLayout = {
      labelCol: {
        xs: { span: 4 },
        sm: { span: 4 },
      },
      wrapperCol: {
        xs: { span: 12 },
        sm: { span: 12 },
      },
    };

    return (
      <div>
        <Form layout="horizontal" {...formItemLayout}>
          <FormItem label="头像">
            {getFieldDecorator('avatar',{
              initialValue: user.avatar
            })(
              <UnipicUpload
                group={"user"}
                onChange={()=>{this.handlerUploadOnChange}}
                image={user.avatar}
              />
            )}
          </FormItem>
          <FormItem label="登录名">
            {getFieldDecorator('loginname', {
              initialValue: user.loginname,
              rules: [
                {
                  required: true,
                  message: '请输入登录名',
                },
              ],
            })(<Input placeholder="将会成为您唯一的登入名" disabled={!flag} />)}
          </FormItem>
          {flag ? (
            <div>
              <FormItem label="密 码">
                {getFieldDecorator('password', {
                  rules: [
                    {
                      required: true,
                      message: '请输入密码',
                    },
                  ],
                })(<Input.Password placeholder="请输入密码" />)}
              </FormItem>
              <FormItem label="确认密码">
                {getFieldDecorator('repassword', {
                  rules: [
                    {
                      required: true,
                      message: '请输入密码',
                    },
                    {
                      validator: this.compareToPassword,
                    },
                  ],
                })(<Input.Password placeholder="确认密码" />)}
              </FormItem>
            </div>
          ) : null}
          <FormItem label="真实姓名">
            {getFieldDecorator('realname', {
              initialValue: user.realname,
            })(<Input placeholder="真实姓名" />)}
          </FormItem>
          <FormItem label="Email">
            {getFieldDecorator('email', {
              initialValue: user.email,
            })(<Input placeholder="email" />)}
          </FormItem>
          <FormItem label="Q  Q">
            {getFieldDecorator('qq', {
              initialValue: user.qq,
            })(<Input placeholder="qq" />)}
          </FormItem>
          <FormItem label="选择角色">
            {
              getFieldDecorator('roleIds',{
                initialValue: flag ? [] : roleIds
              })(
                <Checkbox.Group options={this.roleToNameId(roles)} ></Checkbox.Group>
              )
            }
          </FormItem>
        </Form>
      </div>
    );
  }
}

5、效果图:

如果对你有帮助帮忙点个赞哈

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React页面设计初体验 的相关文章

  • 声明指令 templateUrl 相对于 root

    我目前正在声明相对于当前窗口位置的 templateUrl cvApp directive personalDetails function return restrict A templateUrl Scripts app templat
  • 将声音添加到标记数组 - 谷歌地图 javascript

    我是新来的 所以我知道我没有任何可信度 我是一名艺术家 对编程很陌生 所以我理解是否没有人会承担这个任务 我发布这篇文章是因为这是一个简单的问题 S 这是创建多个标记的代码 主要来自谷歌开发者网站 它工作正常并为每个标记创建一个自定义图标
  • 如何全局公开 es6 模块

    我需要编写一个可在全局窗口上使用的模块 我使用 es6 创建模块 我定义的每个类都有它自己的文件 我正在使用 webpack 来 babelify 并捆绑这些类 我的模块的入口点也是包含要公开的全局的文件 我尝试了各种方法来实现这一点 包括
  • Fabric JS html 5 图像弯曲选项

    我想用html5工具制作图像曲线 我使用 Fabric js 作为 html5 画布工具 请指导我如何在杯子 玻璃 圆柱形或圆形产品等图像上制作弯曲图像 参考号图片如下 http vsdemo cwwws com Images Produc
  • 使用 MapBox GL JS 无需访问令牌

    有没有办法使用MapBox GL JS没有访问令牌 我在文档中找不到任何提示MapBox GL JS https docs mapbox com mapbox gl js api 然而 Uber建议是可以通过他们的图书馆 https ube
  • 运行玩笑测试时,Firebase 在signInAnonymously() 上抛出“auth/network-request-failed”

    我正在使用 firebase 为我的应用程序 使用 create react app 构建 编写一些测试 并且我正在尝试匿名登录我的测试数据库 但认证失败 然而 所有其他 Firebase 操作 CRUD 都工作得很好 我还可以从在浏览器中
  • 打字稿地图迭代失败

    我正在使用下面的函数来比较两个地图 有趣的是 for 循环内的代码永远不会被执行 所以 console log key val 代码永远不会被执行 当然 我确保我正在比较的映射不为空并且大小相同 以强制执行 for 循环内的代码 我犯了一个
  • 如何在 Angular 4 材料的 Stepper 中提交表单

    如何在角材料的步进器中提交表单数据 我正在遵循角材料的示例https material angular io components stepper examples https material angular io components
  • 如何从数组中删除空数组值(“”)?

    我有一个二维数组 是用 jQuery 从 html 表生成的 但有些值是空的 所以 被展示 如何删除空值 table tr th 1A th th 1B th th 1C th tr tr td 2A td td 2B td td 2C t
  • Redux 中的排队操作

    我目前遇到的情况是我需要连续运行 Redux Actions 我看过各种中间件 比如 redux promise 看起来不错如果您知道触发根操作 由于缺乏更好的术语 时的连续操作是什么 本质上 我想维护一个可以随时添加的操作队列 每个对象在
  • 如何防止 Ajax/javascript 结果在浏览器中缓存?

    如何防止浏览器缓存Ajax结果 我有事件触发的 Ajax 脚本 仅当浏览器数据被清除时才显示结果 在 IE6 和 Firefox 3 0 10 中测试 随机 URL 可以工作 但它是一种 hack HTTP 内置了应该可以工作的解决方案 尝
  • 如何从 Selectize 中删除项目?

    有什么方法可以从 Selectize 中删除项目吗 这是我的示例列表 AMNT QTY NA 当我经过时NA它应该删除特定项目 fn removeSelectorValue function value var selectize this
  • Javascript `new` 运算符和原型

    假设我们创建一个名为 Shape 的函数 并在其原型上添加属性 name 和方法 toString var Shape function Shape prototype name Shape Shape prototype toString
  • 使用 JavaScript 检测硬重新加载

    为了澄清 I am not试图区分刷新和重新加载 因此这不是重复的刷新与重新加载 https stackoverflow com questions 5004978 check if page gets reloaded or refres
  • 如何在闪亮的仪表板侧栏中手动展开子菜单

    我正在尝试手动展开闪亮仪表板侧边栏中的子菜单 这updateTabItems该功能仅适用于普通菜单 不适用于嵌套菜单 这是基本示例 修改自updateTabItems文档 来显示问题 如果我单击 切换选项卡 它会切换菜单 但不会展开具有子菜
  • 计算 DIV 元素的最大/最小高度

    问题 给定一个具有固定高度的 DIV 元素 其中包含未知数量的子元素 这些子元素的大小相对于其高度 计算 DIV 可以调整大小的最大 最小高度 而不违反其子元素的任何最大 最小值元素 Example求 DIV A 的最大 最小高度 Answ
  • 双向数据绑定(Angular)与单向数据流(React/Flux)

    上周 我一直在试图弄清楚如何双向数据绑定 Angular https docs angularjs org guide databinding and 单向数据流 React Flux https youtu be i 969noyAM是不
  • 脚本内的角度范围

    我们可以使用脚本标记内范围中定义的角度变量 如下所示 HTML 代码 div div JS CODE function AngularCtrl scope scope user name John 我只是得到 scope 未定义 有人可以帮
  • React 路由器重定向页面但组件未渲染

    我在路由更改时渲染组件时遇到一个奇怪的问题 我使用的版本 react 16 9 0 react dom 16 9 0 react router dom 5 1 0 这是我的路线配置 const Routes gt const isLogge
  • 如何使用ajax从服务器接收返回的数据?

    基本上我有一个带有用户名文本框和提交按钮的表单 现在我想要的是 当用户在文本框中输入文本时 它应该获取文本框值并将用户名发送到服务器 以便服务器可以检查该用户名是否被任何其他用户占用 我可以将文本值发送到服务器 但我不知道如何接收回一些数据

随机推荐

  • 第15课 微信小程序behavior组件间的数据共享:

    第15课 微信小程序behavior组件间的数据共享 先看看目录结构 我们先编写一下两my behavior的代码 这里是my behavior js的代码 behavior内还可以嵌套引入behavior my behavior js 引
  • 嵌入式学习:stm32学习路线推荐之思维导图

    从9月1日开始学习STM32后 对于STM32的一些个人总结 1 对于STM32和51的区别 对于 STM32来说 基本的大概都和51单片的内容相似 但是由于STM的引脚和寄存器的数量较多 所以需要一个更加完善的管理机制 导致了 时钟 的产
  • Caused by: org.attoparser.ParseException: Error resolving template [index], template might not exist

    仿牛客论坛 th replace index header 报错 index html中 th fragment header register html中 th replace index header 主要报错是两个 org thyme
  • 递归算法——八皇后问题 python

    研究了一下午的八皇后算法 可算是搞明白了 为了避免以后忘记 还是写个博客吧 可能会跟其他文章有相似之处 最终还是希望能好好学习算法 都是经过自己思考后亲自写的代码 虽然过程比较艰难 我写了很多注释 参考B站视频链接 2021第十二届蓝桥杯青
  • 前端移动Web第四天案例:阿里百秀首页-响应式布局(bootstrap框架)

    阿里百秀首页案例 技术选型 方案 我们采取响应式页面开发方案 技术 bootstrap框架 设计图 本设计图采用 1280px 设计尺寸 1 页面布局分析 2 屏幕划分分析 屏幕缩放发现 中屏幕 和 大屏幕布局 是一致的 因此我们列 定义为
  • Matlab实现蚂蚁群算法(附上多个完整仿真源码)

    蚂蚁群算法是一种模拟自然界中蚂蚁行为的优化算法 其具有全局搜索能力和适应性强的特点 被广泛应用于组合优化问题中 本文将介绍如何使用Matlab实现蚂蚁群算法 文章目录 1 蚂蚁群算法原理 2 Matlab实现蚂蚁群算法 3 代码实现 4 结
  • JS常用方法总结

    数组常用的方法 方法 语法 描述 增加方法 splice arr splice 起始下标 长度 添加的元素1 元素2 arr splice 3 0 0 1 xiaolv 从数组中添加或删除元素 push arr push xiaonan v
  • 数据库双服务器系统设计,面向数据库服务器的高可用性系统的设计与实现

    摘要 随着计算机技术的飞速发展 数据库的应用已遍及各行各业 它们往往维系着整个系统的生命 一旦数据库崩溃造成数据丢失或者暂停服务 那将给用户或企业带来不可估量的损失 尤其是银行系统与电子商务交易系统 一些不可预料的故障或停机造成的经济损失可
  • Java内存区域

    Java内存区域 深入理解Java虚拟机 第2版 Java虚拟机在执行Java程序的过程中会把它所管理的内存分为若干个不同的数据区域 Java虚拟机运行时数据区 其中蓝色部分为共享区域 浅色部分为各线程私有 程序计数器 一块较小的区域 可以
  • 每月摘录--2023年4月

    企业 04月07日 阿里云宣布自研大模型 通义千问 开始邀请用户测试体验 4月10日消息 此前3月29日凌晨 腾讯旗下的微信和QQ等业务曾出现崩溃状况 包括微信语音对话 朋友圈 微信支付 以及QQ文件传输 QQ空间和QQ邮箱在内的多个功能无
  • 基于RFID技术的电力计能表仓储管理系统—铨顺宏

    基于RFID技术的电力计能表仓储管理系统 1 应用背景 电力计量中心是电力行业的电能计量检测机构 承担辖区内电能计量器具安全生命周期管理的职能 包括采购 仓储 检测 配送 安装 运行监测等各个环节 随着城网改造和居民一户一表工作的深入进行
  • 数据库课程设计mysql编程_数据库课程设计[完整版].doc

    可编辑版 Word完美格式 HUNAN CITY UNIVERSITY 数据库系统课程设计 设计题目 宿舍管理信息系统 姓 名 学 号 专 业 信息与计算科学 指导教师 20年 12月1日 目 录 TOC o 1 3 h z HYPERLI
  • ctfshow-Log4j复现-log4j复现

    1 买VPS 打开mobax进行ssh连接 开两个终端 一个终端开启监听 另一个终端进入JNDIExploit 1 2 SNAPSHOT jar所在的目录jndiexploit执行下面命令 java jar JNDIExploit 1 2
  • JavaScript基础Day02:流程控制

    文章目录 1 顺序结构 2 分支结构 1 if语句 2 switch语句 3 循环结构 1 while语句 2 do while语句 3 for循环 1 顺序结构 2 分支结构 1 if语句 if 条件表达式 执行语句 if 条件表达式 成
  • Qt实现简易的浏览器

    一 Qt的webenginewidgets模块和MSVC2017编译环境的配置 webenginewidgets模块 该模块需要在安装Qt时勾选Qt WebEngine MSVC2017编译环境的配置 这里的MSVC选2017还是2015
  • XDOJ目录操作

    目录操作 类别 字符串处理 时间限制 1S 内存限制 256Kb 问题描述 在操作系统中 文件系统一般采用层次化的组织形式 由目录 或者文件夹 和文件构成 形成一棵树的形状 有一个特殊的目录被称为根目录 是整个文件系统形成的这棵树的根节点
  • Android自定义控件(四)---实战篇(详解onDraw)

    讲到这里 这个案例基本上快结束了 在绘制 onDraw 方法中 唯一的难点就是文字 基线的确定 这点请大家务必弄清楚 废话不多说 上码 首先 我们先不管基不基线的 先让文字显示出来再说 package com example mytextv
  • 深度学习虚拟环境在不同机器之间的迁移

    不同机器之间虚拟境的复制 假设有两台机器 都用的是anaconda配置虚拟环境的 且虚拟环境都在anaconda3 envs 目录下 那么复制虚拟环境可以直接将一台机器anaconda3 envs 目录下的虚拟环境 对应该目录下的一个文件夹
  • uniapp微信小程序引入threeJs并导入模型

    前言 我的需求是使用uniapp写微信小程序 在小程序中使用threeJs就行了 目前暂不考虑兼容app什么的 1 引入小程序版的threejs库实现 2 使用webview实现 推荐 重点 我的建议是使用这个库 https github
  • React页面设计初体验

    1 定制路由 export default login path login name login component layouts BlankLayout routes path login component Login Index