用React给XXL-JOB开发一个新皮肤(四):实现用户管理模块

news2024/11/16 2:38:04

目录

  • 一. 简述
  • 二. 模块规划
    • 2.1. 页面规划
    • 2.2. 模型实体定义
  • 三. 模块实现
    • 3.1. 用户分页搜索
    • 3.2. Modal 配置
    • 3.3. 创建用户表单
    • 3.4. 修改用户表单
    • 3.5. 删除
  • 四. 结束语

一. 简述

上一篇文章我们实现登录页面和管理页面的 Layout 骨架,并对接登录和登出接口。这篇文章我们将实现用户管理的模块和相应的接口。最后效果如下:
在这里插入图片描述

二. 模块规划

在开发之前我们需要对 xxl-job管理系统的用户模块进行规划。

2.1. 页面规划

一般我们都是从前端页面需要使用什么组件;后端接口需要哪些?
在这里插入图片描述
前端使用的组件:表格、分页、下拉框、输入框和按钮,就是一个很普通的 CRUD 管理页面,比较简单;接口也是围绕这些功能的:分页查询接口、创建用户接口、编辑用户接口和删除接口。

2.2. 模型实体定义

接着我们需要定义下前后端交互会使用的到的请求和响应实体的定义。

首先是用户分页查询接口的请求和响应:

// UserPageQueryProp 用户分页查询请求参数定义
export interface UserPageQueryProp {
  page: number;      // 页码
  size: number;      // 页大小
  role: number;      // 角色 ID
  username?: string;  // 用户名称
}

// UserTableProp 用户分页查询返回参数定义
export interface UserTableProp {
  id: number;         // 用户ID
  username: string;   // 用户名称
  role: number;       // 角色
  permission: string; // 权限
}

这里需要注意的是虽然我们表格中只有用户名和角色名两个显示属性,但是考虑到在编辑的时候需要根据角色显示权限信息,这里在分页查询中返回用户的权限数据。但是如果在一些复杂的分页表格中,不建议这样操作!

接着是用户创建和编辑的请求的定义:

// UserTableProp 用户创建表单属性
export interface UserCreateFormProp {
  username: string;     // 用户名称
  password: string;     // 密码
  role: number;         // 角色
  permission: string[]; // 权限
}

// UserUpdateFormProp 用户创建表单属性
export interface UserUpdateFormProp extends UserCreateFormProp{
  id: number;           // 用户ID
}

三. 模块实现

从这个模块我们可以分为两个大部分和三个小组件组成。
在这里插入图片描述
其中功能部分我们可以使用 antdSpace 中嵌套表单组件实现;表格可以使用 Table 组件(这个组件自带分页功能)实现;最后创建和编辑按钮我们使用 Modal 组件中嵌套 Form 表单组件实现就可以了。下面我们按功能一个个实现这个用户模块。

这里我们在使用 TS 这个 Buff 的使用,大部分使用需要申明类型,尤其在使用不熟悉的 UI 组件库的时候,大家需要多读文章,多看组件定义文件或者源码。

3.1. 用户分页搜索

上面我们分析我们要使用组件,这里就不赘述了,直接上代码:

import {Button, Divider, Input, Select, Space, Table, Tag} from "antd";
import React, {useEffect, useState} from "react";
import {User} from "@/types";
import {ColumnsType} from "antd/es/table";
import {useRequest} from "ahooks";
import UserApi from "@/api/user.ts";
import {ClearOutlined, PlusOutlined, SearchOutlined} from "@ant-design/icons";

const UserPage = () => {

  // 定义列信息
  const columns: ColumnsType<User.UserTableProp> = [
    {
      title: '账号',
      key: 'username',
      dataIndex: 'username',
      align: 'center'
    },
    {
      title: '角色',
      key: 'role',
      dataIndex: 'role',
      align: 'center',
      render: (_, record) => record.role == 1 ? <Tag color="#f50">管理员</Tag> : <Tag color="#2db7f5">普通用户</Tag>
    },
    {
      title: '操作',
      key: 'active',
      align: 'center',
      width: 200,
      render: (_, record) => <Space>
        <Button type="primary" onClick={() => openEdit(record.id)}>编辑</Button>
        <Button type="primary" danger onClick={() => deleteUser(record.id)}>删除</Button>
      </Space>,
    },
  ]
  
  // 总条数
  const [total, setTotal] = useState<number>(0);
  const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
  // 用户数据
  const [datasource, setDatasource] = useState<User.UserTableProp[]>([]);
  // 分页查询属性
  const [pageQuery, setPageQuery] = useState<User.UserPageQueryProp>(defaultUserPageQuery());

  return <div>
    <Space>
      <Button type="primary" icon={<PlusOutlined />}>增加用户</Button>
      <Divider type="vertical"/>
      <div>角色:</div>
      <Select
        onChange={e => setPageQuery({...pageQuery, role: e})}
        placeholder="选择状态"
        defaultValue={-1}
        style={{width: 100}}
        options={[
          {value: -1, label: '全部'},
          {value: 1, label: '管理员'},
          {value: 0, label: '普通用户'}
        ]}
      />
      <div style={{marginLeft: 20}}>用户名称:</div>
      <Input
        allowClear
        placeholder="请输入搜索的用户名称"
        value={pageQuery.username}
        onChange={e => setPageQuery({...pageQuery, username: e.target.value})} />
      <Button danger type='primary' icon={<ClearOutlined />} onClick={clearSearch}>清空</Button>
      <Button type='primary' icon={<SearchOutlined />} onClick={() => loadUser.run(pageQuery)}>搜索</Button>
    </Space>
    <Table
      bordered
      size={'small'}
      columns={columns}
      loading={loadUser.loading}
      dataSource={datasource}
      style={{ marginTop: 10 }}
      rowKey={(record) => record.id}
      pagination={{
        onShowSizeChange: (current, size) => loadUser.run({...pageQuery, page: current, size: size}),
        onChange: (page, pageSize) => loadUser.run({...pageQuery, page: page, size: pageSize}),
        showTotal: () => `${total}`,
        showQuickJumper: true,
        showSizeChanger: true,
        pageSize: pageQuery.size,
        current: pageQuery.page,
        size: 'default',
        total: total,
      }}
      rowSelection={{
        type: 'checkbox',
        selectedRowKeys: selectedRowKeys,
        onChange: (selectedRowKeys: React.Key[]) => {
          setSelectedRowKeys([...selectedRowKeys.map(item => item as number)])
        }
      }}
    />
  </div>
}

export default UserPage;

这里我们需要注意一下几点:

  • 表格每一个行都需要一个 Key,默认是 React.Key,但是如果我们需要自定义的时候,可以使用rowKey={(record) => record.id}定义自己的 rowKey,这里的 record 就是定义表格属性模型:User.UserTableProp
  • 关于分页属性我们可以通过pagination属性进行设置,可以设置属性和方法可以在分页组件文章中看到
  • 最后一点就是关于表格行选中可以通过rowSelection属性设置;

接下来我们就需要对接分页查询的接口了,首先我们在 api/user.ts 中添加用户分页接口 api 定义:

/**
 * 用户分页
 * @param param
 * @constructor
 */
export const UserPage = (param: User.UserPageQueryProp): Promise<PageData<User.UserTableProp>> => {
  return https.request({
    url: '/user/pageList',
    method: 'post',
    data: param
  })
}

接着我们看一下如何使用这个api,并且了解下ahooks 中的useRequest中非常好用的地方。

// 加载用户列表
const loadUser = useRequest(UserApi.UserPage, {
  manual: true, // 手动调用
  onSuccess: ({records, total}) => { // 成功之后执行的操作
    setTotal(total);
    setDatasource(records);
  }
});

最后我们配合 useEffect使用,加载用户列表的接口会在加载用户管理页面的时候调用这个接口。

useEffect(() => {
  loadUser.run(pageQuery)
}, [])

这里我们介绍 ahooks 中的 useRequest这个工具 hooks

在这里插入图片描述
支持的功能很多,这里我们现使用这里的 loading 返回值。在我们请求接口的时候如果遇到网络抖动之类的加载缓慢的情况,让表格出现一个加载状态的图标是非常友好了,不然用户也很懵逼。在上面antd 提供了加载属性loading={loadUser.loading}我们只需要将这个值的变化交给 useRequest 就可以,完全不需要我们手动控制。

接下来我们实现上面搜索的功能。
在这里插入图片描述
这里一个是下拉框一个是输入框,我们直接使用的是 antd 的组件,我们仅需要实现清空输入和搜索两个按钮事件就可以了。对于清空搜索的点击事件,我们只需要将下拉选项设置为默认值,输入框清空就可以了,代码如下 :

// 清空搜索
const clearSearch = () => {
  setPageQuery({...pageQuery, role: -1, username: ""})
}

对于搜索我们仅需要手动调用分页接口就可以了,代码如下:

<Button 
  type='primary' 
  icon={<SearchOutlined />} 
  onClick={() => loadUser.run(pageQuery)}
>搜索</Button>

3.2. Modal 配置

这个用户管理的部分需要用到创建用户和编辑用户两个功能,在xxl-job中都是都通过打开弹窗进行操的,我们这里也是使用相同的逻辑。这里我们使用 antdModal 组件。在使用这个组件的时候我们需要对 Modal 组件的打开和关闭进行一个统一的控制。

// UserCreateModelProp 创建用户弹窗属性
export interface UserCreateModalProp {
  visible: boolean;
  close: (isLoad: boolean) => void; // 关闭模态框
}

// UserUpdateFormProp 用户更新表单属性
export interface UserUpdateFormProp {
  id: number;           // 用户ID
  password: string;     // 密码
  role: number;         // 角色
  permission: string[]; // 权限
}

// 定义模态框的类型
export type ModalType = "create" | "update";

// UserModelProp 用户模态框汇总属性
export interface UserModalProp {
  createVisible: boolean;    // 创建用户模态框打开标识
  updateVisible: boolean;    // 编辑用户模态框打开标识
  userData?: UserTableProp;  // 编辑是存放被编辑用户信息
}

接着我们分别定义打开和关闭模态框的事件:

// 关闭模态框
const closeModal = (isLoad: boolean) => {
  // 在全局只能有一个弹窗打开,所以在关闭的时候把标识变量都设为 false 就可以了
  setUserModelProp({createVisible: false, updateVisible: false, userData: undefined})
  if(isLoad) {
    // 如果创建和编辑成功,我们需要重新加载表格数据显示最新的数据
    loadUser.run(pageQuery)
  }
}

// 打开模态框
const openModal = (types: User.ModalType, data?: User.UserTableProp) => {
  switch (types) {
    case "create":
      setUserModalProp({createVisible: true, updateVisible: false});
      break;
    case "update":
      setUserModalProp({updateVisible: true, createVisible: false, userData: data});
      break;
    default:
      break
  }
}

最后我们在创建和编辑按钮上使用这些事件就可以了:

<Button 
  type="primary" 
  icon={<PlusOutlined />} 
  onClick={() => openModal('create')}
  >增加用户</Button>

<Button 
  type="primary" 
  onClick={() => openModal('update', record)}
  >编辑</Button>

接着我们定一个模态框组件,在当前目录下创建 create.tsxupdate.tsx 文件,这两个文件分别是创建用户和编辑用户模态框组件(子组件)。

import {Modal} from "antd";
import React from "react";
import {User} from "@/types";

const CreateUserModal: React.FC<User.UserCreateModalProp> = ({visible, close}) => {

  const submitForm = () => {
    close(true)
  }

  return <Modal
    title="创建用户"
    open={visible}
    onOk={submitForm}
    onCancel={() => close(false)}
  >
    <h1>创建用户</h1>
  </Modal>
}

export default CreateUserModal;

编辑类似不做展示了

这两个子组件设置组件之间的传值问题,我们在User.UserCreateModalProp定义了创建用户模态框组件需要的参数:visible变量和 close函数。最后我们在 index.tsx 中使用这个子组件就可以了。

// 存放模态框状态值
const [userModalProp, setUserModalProp] = useState<User.UserModalProp>({createVisible: false, updateVisible: false});

<CreateUserModal
  key="create"
  close={closeModal}  // 模态框关闭事件
  visible={userModalProp.createVisible} // 创建用户模态框打开状态标识变量
/>

效果如下:
在这里插入图片描述

3.3. 创建用户表单

这里我们接着实现创建用户表单和表单提交的相关部分,直接上代码:

推荐先看看 antdForm 组件的文章。

import {Checkbox, Divider, Empty, Form, Input, message, Modal, Radio, Row, Spin, Tag} from "antd";
import React, {useEffect, useState} from "react";
import {Group, User} from "@/types";
import {useRequest} from "ahooks";
import {GroupApi, UserApi} from "@/api/index.ts";
import styled from "@emotion/styled";

const CreateUserModal: React.FC<User.UserCreateModalProp> = ({visible, close}) => {

  // 表单
  const [form] = Form.useForm<User.UserCreateFormProp>();
  // 监听表单 role 的 value
  const roleValue = Form.useWatch('role', form);
  // 执行器列表
  const [groups, setGroups] = useState<Group.JobGroupListProp[]>([]);
  // 执行器请求
  const groupLoader = useRequest(GroupApi.GroupLists, {manual: true, onSuccess: (data) => {
    setGroups(data);
  }})
  // 创建用户请求
  const createLoader = useRequest(UserApi.CreateUser, {manual: true, onSuccess: () => {
      message.success('创建用户成功')
      close(true)
    }
  });

  const submitForm = () => {
    form.validateFields().then(value => {
      // console.log("submit => ", value)
      if (value.role == 1) {
        value.permission = []
      }
      createLoader.run(value);
    })
  }

  // 监听 visible 打开关闭标识
  useEffect(() => {
    if (visible) { // 当创建用户模态框打开,请求执行器列表接口并设置角色默认值为普通用户
      groupLoader.run();
      form.setFieldValue('role', 0)
    } else {
      // 关闭模态框的时候,将表单置为空并将执行器列表设置为空数组
      form.resetFields();
      setGroups([]);
    }
  }, [visible])

  return <Controller
    title="创建用户"
    maskClosable
    width={500}
    open={visible}
    onOk={submitForm}
    onCancel={() => close(false)}
  >
    <Spin tip="加载中......" spinning={createLoader.loading}>
      <Form 
        form={form} 
        layout="vertical" 
        name="form_create_modal">
        <Form.Item 
          name="username" 
          label="账号" 
          rules={[{ required: true, message: '请输入账号' }]}>
          <Input placeholder="请输入账号" />
        </Form.Item>
        <Form.Item 
          name="password" 
          label="密码" 
          rules={[{ required: true, message: '请输入密码' }]}>
          <Input.Password placeholder="请输入密码" />
        </Form.Item>
        <Form.Item name="role" label="角色">
          <Radio.Group>
            <Radio value={0}>普通用户</Radio>
            <Radio value={1}>管理员</Radio>
          </Radio.Group>
        </Form.Item>
        {
          roleValue === 0 && <Form.Item name="permission" label="权限">
            {groups.length > 0 ? <Checkbox.Group className="xxl-job-list">
              {groups.map(item =>
                <Row key={item.id}>
                  <Checkbox value={item.appName}>{item.title}
                    <Divider type="vertical" />
                    <Tag color="lime">{item.appName}</Tag>
                  </Checkbox>
                </Row>)}
            </Checkbox.Group> 
            : <Empty />
         }
         </Form.Item>
        }

      </Form>
    </Spin>
  </Controller>
}

const Controller = styled(Modal)`
  .ant-modal-body {
    padding-top: 24px;
    .xxl-job-list {
       flex-direction: column;
    }
  }
`

export default CreateUserModal;

这里我们通过 Modal 包裹表单组件,使用 useEffect监听 visible属性,当前模态框打开的时候,需要请求执行器列表,并设置角色默认值。

还需要注意的一个点是,当角色是管理员的时候,是不需要选择执行器的,所有在切换角色为管理员的时候,需要将之前选中的执行器清空;所以在最后提交用户数据的时候,设置下执行器就可以了。

const submitForm = () => {
  form.validateFields().then(value => {
    if (value.role == 1) { // 当角色是管理员的时候,将执行器权限设置为空数据
      value.permission = []
    }
    createLoader.run(value);
  })
}

此外我们还通过 styled 修改了 Modal 组件的样式,主要是为了将多选框flex 布局从 row 改为 column

// 使用 styled 包裹 Modal 组件
const Controller = styled(Modal)`
  .ant-modal-body {
    padding-top: 24px;
    .xxl-job-list {
      flex-direction: column;
    }
  }
`

3.4. 修改用户表单

有了上面创建用户表单部分,我们在修改用户信息的时候,仅需要了解表单初始化的问题了;这里我们也是用使用Form.setFieldsValue方法进行初始化表单,代码代码:

useEffect(() => {
  if (visible && data) {
    groupLoader.run();
    form.setFieldsValue({id: data.id, username: data.username, role: data.role, permission: data.permission})
  } else {
    form.resetFields();
    setGroups([]);
  }
}, [visible])

这里还有一个不一样的地方是我们会设置一个隐藏的用户主键,方便我们后面执行更新的时候确定要被更新用户信息:

<Controller
    title="更新用户"
    maskClosable
    width={500}
    open={visible}
    onOk={submitForm}
    onCancel={() => close(false)}
  >
    <Spin tip="加载中......" spinning={updateLoader.loading}>
      <Form form={form} layout="vertical" name="form_update_modal">
        // 不显示主键,在我们提交数据的时候会反给form.validateFields().then(value => {})中
        <Form.Item name="id" label="主键" style={{display: 'none'}}><Input /></Form.Item>
        <Form.Item name="username" label="账号">
          <Input placeholder="请输入账号" readOnly />
        </Form.Item>
        <Form.Item name="password" label="密码">
          <Input.Password placeholder="请输入新密码,为空则不更新密码" />
        </Form.Item>
        <Form.Item name="role" label="角色">
          <Radio.Group>
            <Radio value={0}>普通用户</Radio>
            <Radio value={1}>管理员</Radio>
          </Radio.Group>
        </Form.Item>
        {
          roleValue === 0 && <Form.Item name="permission" label="权限">
            {groups.length > 0 ? <Checkbox.Group className="xxl-job-list">
              {groups.map(item =>
                <Row key={item.id}><Checkbox value={item.appName}>{item.title}<Divider type="vertical" /><Tag color="lime">{item.appName}</Tag></Checkbox></Row>)}
            </Checkbox.Group> : <Empty />}
                    </Form.Item>
        }

      </Form>
    </Spin>
  </Controller>

3.5. 删除

终于快要搞完了,现在我们就剩删除用户这个功能了。针对我们删除来说,一般我都需要弹出一个提示,询问用户是否确定删除这条数据。这里我们可以使用 antd 中的 删除Modal或者气泡提示就可以了。

在这里插入图片描述
这里功能简单,只需要调用组件,在其回调方法中调用删除接口就可以了。代码如下:

// 移除用户
const loadRemoveUser = useRequest(UserApi.RemoveUser, {
  manual: true,
  onSuccess: () => {
    loadUser.run(pageQuery);
    message.success('移除用户成功');
  }
})

// 删除用户
const deleteUser = (id: number) => {
  Modal.confirm({
    title: '你确认删除当前用户吗?',
    icon: <ExclamationCircleFilled />,
    content: '删除用户会导致无法登录和操作任务',
    okText: '确认',
    okType: 'danger',
    cancelText: '取消',
    onOk() {
      loadRemoveUser.run(id)
    },
    onCancel() {},
  });
}

最后在给删除按钮添加点击事件,并将用户的 ID 传给接口。

<Button type="link" danger onClick={() => deleteUser(record.id)}>删除</Button>

四. 结束语

这篇文章我们介绍了如何利用 antd 提供的组件,快速开发一个 CRUD 功能的管理模块,相信大家可以从中收获很多东西了;下一篇文章我们将介绍执行器管理的模块开发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1421438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【代码随想录】刷题笔记Day56

前言 26回了老家参加二姨的婚礼&#xff0c;还逛了几圈亲戚&#xff0c;回来就接家教的活&#xff0c;想到还要刷题开组会&#xff0c;回家注定是没法怎么休息啦&#xff0c;可恶 42. 接雨水 - 力扣&#xff08;LeetCode&#xff09; 暴力解法&#xff08;双指针优化&#xf…

SpringBoot中集成Minio高性能分布式存储文件服务入门

场景 若依前后端分离版手把手教你本地搭建环境并运行项目&#xff1a; 若依前后端分离版手把手教你本地搭建环境并运行项目-CSDN博客 参考上面搭建项目。 Minio Minio是基于Go语言编写的对象存储服务&#xff0c;适合于存储大容量非结构化的数据&#xff0c;例如图片、音频…

定制自己的linux

记得看目录哦&#xff01; 1. 基本介绍2. 思路分析3. 开始定制3.1 添加一块20G的硬盘3.2 重启&#xff0c;进行磁盘分区3.3 格式化分区3.4 创建目录&#xff0c;并挂载磁盘3.5 安装grub3.6 看是否安装上3.7 内核文件拷贝到磁盘3.8 修改grub2/grub.cfg3.9 创建目标主机的根文件系…

Spring Boot项目中集成连接池及部分细节说明

连接池 一&#xff0c;Connection连接二&#xff0c;数据库连接池三&#xff0c;集成数据库连接池1&#xff0c;Spring Boot默认连接池2&#xff0c;Druid连接池3&#xff0c;集成Druid&#xff08;原生版本&#xff09;3.1&#xff0c;引入依赖3.2&#xff0c;配置数据源3.3&a…

CSS 实现立体字效果

我们在工作中&#xff0c;很多场景会遇到立体文字&#xff0c;今天我们就来实现下&#xff0c;很简单&#xff0c;算是水文章吧&#xff1a; <h1>立体字体</h1>h1 {margin: 100px 0 0 100px;font-size: 5em;color: #fff;text-shadow: -1px 1px #bbb,-2px 2px #bbb…

设计模式——职责链模式(Chain of Responsibility Pattern)

概述 职责链模式(Chain of Responsibility Pattern)&#xff1a;避免请求发送者与接收者耦合在一起&#xff0c;让多个对象都有可能接收请求&#xff0c;将这些对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;直到有对象处理它为止。职责链模式是一种对象行为…

深度学习(9)--pydot库和graphviz库安装流程详解

目录 一.pydot库安装 二.graphviz库安装 一.pydot库安装 pydot的安装可直接在编译器安装相关包&#xff0c;以PyCharm举例&#xff1a; 如果搜索可用软件包显示为空&#xff0c;记得在此处把使用Conda软件包管理器”点亮 二.graphviz库安装 点击链接下载安装包graphviz-2.38…

DX-11A DC0.075A 型信号继电器 柜内安装,板前接线

DX-11信号继电器&#xff1b; DX-11A信号继电器&#xff1b; DX-11B信号继电器&#xff1b; DX-11C信号继电器&#xff1b; DX-11Q信号继电器&#xff1b; DX-11A/Q信号继电器&#xff1b; DX-11B/Q信号继电器&#xff1b; DX-11C/Q信号继电器&#xff1b; 一. 用途 DX-11/0.…

.net core 6 集成 elasticsearch 并 使用分词器

1、nuget包安装NEST、安装elasticsearch、kibana、ik分词器、拼音分词器 2、创建操作对象 //索引库 static string indexName "testparticper"; //es 操作对象 ElasticClient elasticClient new ElasticClient(new ConnectionSettings(new Uri("http://192.…

Python使用分治算法作归并排序

对于分治算法的一个较为常规的应用中&#xff0c;归并排序是一个使用分治算法的排序方式。给定一个随机排序的数组&#xff0c;我们要将其元素按照升序或者降序的方式进行排序&#xff0c;可以设想到这样的一种算法&#xff0c;如果一个数组的上半部分和下半部分已经排好序&…

(HAL)STM32F407ZGT6——10-4 高级定时器 PWM 输入模式实验

一、高级定时器简介 高级定时器的框图和通用定时器框图很类似&#xff0c;只是添加了其它的一些功能&#xff0c;如&#xff1a;重复计数器、带死区控制的互补输出通道、断路输入等。 高级定时器的时钟来自APB2, 而PCLK2 168Mhz, 我们设置PPRE2不分频, 因此高级定时器时钟 …

如何通过Hive/tez与Hadoop的整合快速实现大数据开发

一、Hive的功能 Hive是基于Hadoop的一个外围数据仓库分析组件&#xff0c;可以把Hive理解为一个数据仓库&#xff0c;但这和传统的数据库是有差别的。 传统数据库是面向业务存储&#xff0c;比如 OA、ERP 等系统使用的数据库&#xff0c;而数据仓库是为分析数据而设计的。同时…

05 MyBatis之表关系的声明+事务+SqlSession三件套的作用域

MyBatis 支持一对一&#xff0c;一对多&#xff0c;多对多查询。XML 文件和注解都能实现关系的操作。多对多实质就是一对多 1. 表关系的维护 1.1 One一对一 一对一查询和多表(两表)查询很相似, 都能查询两表的全部属性 区别是一对一可以在对象中嵌套对象, 呈现包含关系; 多表…

Kotlin快速入门系列9

Kotlin对象表达式和对象声明 对象表达式 有时&#xff0c;我们想要创建一个对当前类有些许修改的对象同时又不想重新声明一个子类。如果是Java&#xff0c;可以用匿名内部类的概念来解决这个问题。kotlin的对象表达式和对象声明就是为了实现这一点(创建一个对某个类做了轻微改…

Java 开发环境 全套包含IDEA

一、JDK配置 1.下载 JDK Builds from Oracle 去这边下载open JDK 2.JDK环境变量配置 按win&#xff0c;打开设置 找到环境变量编辑 这边输入的是你下载的那个JDK的bin的路径 检擦配置是否正确在cmd中输入 二、IDEA安装配置 1.下载&#xff08;社区版&#xff09; JetBrai…

干货 | 大模型在图数据分析、推荐系统和生物科学中的综合应用

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 图机器学习、推荐系统与大语言模型的融合正成为新的前沿热点。图机器学习通过利用图结构数据&#xff0c;能够有效地捕捉和分析复杂关系和模式。同时&#xff0c;推荐系统正逐步成为我们日常生活的一部分&#…

华为——NGFW Module安装在集群交换机上,二层双机负载分担部署,交换机重定向引流

NGFW Module安装在集群交换机上&#xff0c;二层双机负载分担部署&#xff0c;交换机重定向引流 业务需求 如图1所示&#xff0c;两台交换机集群组网&#xff0c;两块NGFW Module分别安装在两台交换机的1号槽位组成双机负载分担组网。NGFW Module工作在二层&#xff0c;也就是…

走进水稻种植教学基地可视化:科技与农业知识的完美结合

随着科技的不断发展&#xff0c;农业领域也在不断创新和进步。水稻种植教学基地可视化系统是一种基于现代信息技术手段的教学方式&#xff0c;通过虚拟现实、3D建模等技术&#xff0c;将水稻种植的全过程进行模拟和展示。这种教学方式打破了传统农业教学的局限性&#xff0c;使…

腾讯云部署vue+node项目

文章目录 一、安装宝塔二、vue项目部署三、node项目部署 前言: 关于项目部署,一开始也是找了很多资料,费了点时间,所以记录一下。希望能对各位有所帮助。 一、安装宝塔 1.首先在控制台,进入云服务器的终端界面 2.输入命令和密码获取权限,并且安装宝塔界面 yum install -y w…

关于在Tkinter + Pillow图片叠加中出现的问题

这段时间我一直在尝试对多图层图片进行一个叠加的操作&#xff0c;想用tkinter实现出来&#xff0c;先看错误 这里我其实已经选择了图片&#xff0c;但是发现是ValueError&#xff0c;我尝试断点检测但是也无动于衷&#xff0c;因为设置变量检测的时候发现变量并没有错误&…