Koa商城项目-轮播图模块(前端)

news2024/9/24 1:23:26

 前言

通过这次独自做前后端发现有很多需要提升的地方,很多细节处理不到位。下面简单看一下本人自己做的效果吧~~

Git地址

https://gitee.com/ah-ah-bao/koa_system

效果图

 前端代码

api/banner.ts

import request from "../utils/request";

export const getBannerList = (params: any = {}) => {
  return request.post("/banner/list", { ...params });
};
export const getBannerAdd = (params = {}) => {
  return request.post("/banner/add", params);
};
export const getBannerUpdate = (params = {}) => {
  return request.post("/banner/update", params);
};
export const getBannerDetail = (params = {}) => {
  return request.post("/banner/detail", params);
};
export const getBannerDelete = (params = {}) => {
  return request.post("/banner/delete", params);
};
export const getBannerUpload = (params = {}) => {
  return request.post("/banner/upload", params);
};

pages/BannerList/index.tsx

import { useEffect, useState } from 'react';
import { message, Table, Popconfirm } from 'antd';
import { getBannerList, getBannerDelete } from '../../api/banner'
import type { BannerData, pageGationBannerData } from '../../types/banner';
import { BannerColumnsList } from './columns'
import SearchForm from './SearchForm'
import BannerAddOrEdit from './BannerAddOrEdit';

const BannerList = () => {
  const [bannerList, setBannerList] = useState<BannerData[]>([]);
  const [columns, setColumns] = useState<any[]>([]);
  const [visible, setVisible] = useState<boolean>(false);
  const [selectedId, setSelectedId] = useState<number | null>(null);
  const [pagination, setPagination] = useState({
    page: 1,
    pageSize: 10,
    total: 0,
    showSizeChanger: true,
    hideOnSinglePage: false,
    pageSizeOptions: ['10', '50', '100', '200'],
    showTotal: (total: number) => `共 ${total} 条数据`,
  })
  const [queryParams, setQueryParams] = useState<pageGationBannerData>({
    page: 1,
    pageSize: 10,
    bannername: '',
    url: ''
  })

  const getTableColmuns = async () => {
    var columns = [];
    const bannerColumns: any = await BannerColumnsList();
    columns = [
      ...bannerColumns,
      {
        title: '操作',
        key: 'operation',
        align: 'center',
        width: 120,
        render: (_text: any, record: any) =>
          [
            <div className='banner_opertion'>
              <div key={`${record.id}-update`} onClick={() => handleUpdateClick(record.id)}>修改</div>
              <Popconfirm
                title="确定要删除这条记录吗?"
                onConfirm={() => {
                  try {
                    getBannerDelete({
                      id: record.id
                    }).then((res: any) => {
                      if (res.data.code === 200) {
                        message.success('删除成功');
                        fetchData();
                      } else {
                        message.error(res.data.message);
                      }
                    })
                  } catch (error) {
                    message.error('删除失败!');
                  }
                }
                }
                okText="确定"
                cancelText="取消"
              >
                <div key={`${record.id}-delete`}>删除</div>
              </Popconfirm>
            </div>
          ]
      }
    ];
    return columns; // 确保返回值 
  }

  const handleUpdateClick = (id: number) => {
    setSelectedId(id);
    setVisible(true);
  };
  
  const getBannerListData = async () => {
    try {
      const res = await getBannerList(queryParams);
      if (res.data.code === 200) {
        setBannerList(res.data.data);
        setPagination({
          ...pagination,
        })
      } else {
        message.error(res.data.message);
      }
    } catch (error) {
      message.error('获取数据失败!');
    }
  }

  const fetchData = async () => {
    try {
      await getBannerListData();
      const column = await getTableColmuns();
      setColumns(column);
    } catch (error) {
      message.error('获取数据失败!');
    }
  };

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

  const handleMessageSearch = (msg: any) => {
    setQueryParams({ ...queryParams, bannername: msg })
  };

  const getBannerAdd = (msg: any) => {
    setVisible(msg)
  }

  const handleModalClose = () => {
    setVisible(false);
    setSelectedId(null);
  };

  useEffect(() => {
    getBannerListData();
  }, [queryParams]);

  return (
    <>
      <SearchForm SumbitSearch={handleMessageSearch} SumbitAdd={getBannerAdd} />
      <BannerAddOrEdit visible={visible} onClose={handleModalClose} id={selectedId} onRefresh={fetchData} />
      <Table
        columns={columns}
        dataSource={bannerList}
        pagination={pagination}
        rowKey={(record: any) => `table_${record.id}`}
        scroll={{ x: 1200 }}
      />
    </>
  )
}
export default BannerList;

pages/BannerList/columns.tsx

import type { TableProps } from 'antd';
import type { BannerData } from '../../types/banner';
import { Image } from 'antd';
export const BannerColumnsList: () => Promise<TableProps<BannerData>['columns']> = async () => {
    return [
        {
            title: 'id',
            dataIndex: 'id',
            key: 'id',
            align: 'center',
        },
        {
            title: '轮播图名称',
            dataIndex: 'bannername',
            key: 'bannername',
            align: 'center',
        },
        {
            title: '图片',
            dataIndex: 'url',
            key: 'url',
            align: 'center',
            render: (url: string) => (
                <Image
                    width={80}
                    height={80}
                    src={url}
                />
            ),
        },
    ]
}

pages/BannerList/SearchForm.tsx

import type { FormProps } from 'antd';
import { Button, Form, Input, Row, Col } from 'antd';
import { SearchOutlined, PlusOutlined, RedoOutlined } from '@ant-design/icons';
import { useEffect } from 'react';
import './banner.sass'
type FieldType = {
    bannername?: string;
};

const SearchForm = (props: any) => {
    const [form] = Form.useForm();
    const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
        props.SumbitSearch(values.bannername);
    };
    const onReset = () => {
        form.resetFields();
        onFinish(form.getFieldsValue())
    };

    //新增轮播图弹窗
    const addBannerModal = () => {
        // 弹窗显示
        props.SumbitAdd(true)
    }
    useEffect(() => {
        form.setFieldsValue({
            bannername: ''
        })
    }, [])
    return (
        <>
            <Form
                name="basic"
                labelCol={{ span: 4 }}
                wrapperCol={{ span: 20 }}
                onFinish={onFinish}
                autoComplete="off"
                form={form}
            >
                <Row gutter={24}>
                    <Col span={12}>
                        <Form.Item<FieldType>
                            label="轮播图名称"
                            name="bannername"
                        >
                            <Input />
                        </Form.Item></Col>
                    <Col span={12}>
                        <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
                            <Button type="primary" htmlType="submit">
                                <SearchOutlined />  搜索
                            </Button>
                            <Button className='banner_button' onClick={onReset}>
                                <RedoOutlined />重置
                            </Button>
                            <Button className='banner_button' type="primary" onClick={addBannerModal}>
                                <PlusOutlined />  新增轮播图
                            </Button>
                        </Form.Item></Col>
                </Row>
            </Form>
        </>
    )
}
export default SearchForm;

pages/BannerList/BannerAddOrEdit.tsx

import React, { useState, useEffect } from 'react';
import { Modal, Button, message, Upload, Input, Form, Image } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import type { UploadProps } from 'antd';
import { getBannerAdd, getBannerUpdate, getBannerDetail } from '../../api/banner'
import { API_URL, getToken } from '../../../common'
import type { BannerAddOrEditProps, } from '../../types/banner'

const BannerAdd: React.FC<BannerAddOrEditProps> = ({ visible, onClose, onRefresh, id }) => {
  const [localVisible, setLocalVisible] = useState(visible);
  const [title, setTitle] = useState<string>('');
  const [url, setUrl] = useState<string>('');
  const [bannername, setBannername] = useState<string>('')
  const [confirmLoading, setConfirmLoading] = useState<boolean>(false);
  const [form] = Form.useForm();

  useEffect(() => {
    setLocalVisible(visible);
    if (!id) {
      setBannername('')
      setUrl('')
    } else {
      getBannerDetail({
        id: id
      }).then((res: any) => {
        if (res.data.code === 200) {
          setTitle('修改轮播图')
          setBannername(res.data.data.bannername)
          setUrl(res.data.data.url)
          form.setFieldsValue({
            bannername: res.data.data.bannername,
            url: res.data.data.url,
          });
        }
      })
    }
  }, [visible]);


  useEffect(() => {
    if (id) {
      setTitle('修改轮播图')
    } else {
      setTitle('新增轮播图')
    }
  }, [id])

  const handleOk = () => {
    try {
      setConfirmLoading(true);
      if (id) {
        getBannerUpdate({
          id: id,
          bannername,
          url
        }).then((res: any) => {
          if (res.data.code === 200) {
            message.success(res.data.message);
            setConfirmLoading(false);
            setLocalVisible(false);
            onClose();
            onRefresh(); // 调用父组件传递的回调函数
          } else {
            message.error(res.data.message);
            setConfirmLoading(false);
          }
        })
      } else {
        getBannerAdd({
          bannername,
          url
        }).then((res: any) => {
          if (res.data.code === 200) {
            message.success(res.data.message);
            setConfirmLoading(false);
            setLocalVisible(false);
            onClose();
            onRefresh(); // 调用父组件传递的回调函数
          } else {
            message.error(res.data.message);
            setConfirmLoading(false);
          }
        })
      }
    } catch (err: any) {
      message.error(err);
    }
  };

  const handleCancel = () => {
    setLocalVisible(false);
    onClose();
  };

  const props: UploadProps = {
    name: 'file',
    action: API_URL + '/banner/upload',
    headers: {
      Authorization: getToken() || 'defaultTokenValue',
    },
    onChange(info) {
      if (info.file.status !== 'uploading') {
        // console.log(info.file, info.fileList);
      }
      if (info.file.status === 'done') {
        message.success(`${info.file.name} file uploaded successfully`);
        setUrl(info.file.response.data.url);
      } else if (info.file.status === 'error') {
        message.error(`${info.file.name} file upload failed.`);
      }
    },
  };

  return (
    <div>
      <Modal title={title} open={localVisible} onOk={handleOk} onCancel={handleCancel} confirmLoading={confirmLoading} footer={
        [
          <Button key="cancel" onClick={handleCancel}>取消</Button>,
          <Button key="confirm" type="primary" onClick={handleOk} loading={confirmLoading}>确认</Button>
        ]
      }>
        <Form layout="vertical" form={form}>
          <Form.Item label="轮播图名称" name="bannername" rules={[{ required: true, message: '请输入轮播图名称' }]}>
            <Input placeholder="请输入轮播图名称" value={bannername} onChange={(e) => setBannername(e.target.value)}></Input>
          </Form.Item>
          <Form.Item label="轮播图" name="url" rules={[{ required: true, message: '请上传轮播图' }]}>
            <Upload {...props} maxCount={1} showUploadList={false}>
              <Button icon={<UploadOutlined />}>上传</Button>
            </Upload>
          </Form.Item>
          {
            url ? <Form.Item label="轮播图预览" name="url">
              <Image width={200} src={url} />
            </Form.Item> : ''
          }
        </Form>
      </Modal>
    </div>
  )
}

export default BannerAdd;

 pages/BannerList/index.sass

.banner_button
    margin-left: 10px
.banner_opertion
    display: flex
    justify-content: space-evenly
    align-items: center
    cursor: pointer
    color: #26a0f1

完整代码

看本人git地址啦

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

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

相关文章

Mapreduce_wordcount自定义单词计数

自定义的wordcount 数据处理过程 加载jar包 查看后面的pom文件 以上为需要的jar包路径&#xff0c;将其导入至idea中 Map package com.hadoop;import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; im…

计算机毕业设计选题推荐-宠物管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

【彻底关闭win10 or 11系统自动更新】

直接上干货 win10或者win11自动更新后系统有bug&#xff0c;太闹心了&#xff0c;是时候选择一个稳定版本彻底关闭更新了 第一步&#xff1a; 在win11左下方搜索框输入&#xff1a;powershell&#xff0c; 选择「Windows PowerShell (管理员)」 第二步&#xff1a; 在 Wind…

FreeSWITCH

1概述 FreeSWITCH https://signalwire.com/freeswitch是一个开源的电话交换平台。官方给它的定义是–世界上第一个跨平台的、伸缩性极好的、免费的、多协议的电话软交换平台。由这个定义我们可以得出以下几点: FreeSWITCH是跨平台的。它能原生地运行于Windows、MaxOSX、Linux、…

智启万象 | Web 开发智能升级、简单易用

AI 的融入使 Web 的功能更加强大 同时也更加简单易用 一起回顾 2024 Google 开发者大会 了解 Web 开发 UI 功能和 OS 集成的最新创新 以及如何通过这些创新来打造卓越的应用体验 Web 不断的革新与发展&#xff0c;不仅为全球数十亿用户提供了丰富多样的在线体验&#xff0c;也为…

HCIP | 重发布实验

要求&#xff1a; 1.如图搭建网络拓扑&#xff0c;所有路由器各自创建一个环回接口&#xff0c;合理规划IP地址 2.R1-R2-R3-R4-R6之间使用OSPF协议&#xff0c;R4-R5-R6之间使用RIP协议 3.R1环回重发布方式引入OSPF网络 4.R4/R6上进行双点双向重发布 5.分析网络中出现路由…

人工智能在肿瘤亚型分类领域的研究进展|顶刊速递·24-08-13

小罗碎碎念 文献日推主题&#xff1a;人工智能在肿瘤亚型分类领域的研究进展 昨天晚上在研究鼻咽癌的病理学诊断指南&#xff0c;看到了下面这段话的时候&#xff0c;我问了自己一个问题——通过AI识别出肿瘤亚型的根本目的是什么&#xff1f;可以衔接哪些具体的下游任务&#…

「栈」实现LIFO栈(先进后出栈|堆栈|stack)的功能 / 手撕数据结构(C++)

概述 栈&#xff0c;是一种基本的数据结构&#xff0c;也是一种数据适配器。它在底层上以链表方法或动态数组方法实现。 队列的显著特点是他的添加元素与删除元素操作&#xff1a;先加入的元素总是被先弹出。 一个队列应该应该是这样的&#xff1a; --------------STACK----…

UE网络同步(一) —— 一个项目入门UE网络同步之概念解释

最近在学习UE网络同步&#xff0c;发现了一个非常好的教程&#xff0c;并且附带了项目文件&#xff0c;这里从这个小项目入手&#xff0c;理解UE的网络同步 教程链接&#xff1a;https://www.youtube.com/watch?vJOJP0CvpB8w 项目链接&#xff1a;https://github.com/awforsyt…

单片机内存映射

在一些桌面程序中&#xff0c;整个内存映射是通过虚拟内存来进行管理的&#xff0c;使用一种称为内存管理单元(MMU)的硬件结构来将程序的内存映射到物理RAM。在对于 RAM 紧缺的嵌入式系统中&#xff0c;是缺少 MMU 内存管理单元的。 因此在一些嵌入式系统中&#xff0c;比如常用…

Python 设计模式之适配者模式

文章目录 从电源适配器谈起实现适配器模式的两种方式object adapterclass adapter 从电源适配器谈起 适配者模式属于结构型设计模式&#xff0c;它的目的是使不兼容的两个对象能够相互工作。 常见的生活例子&#xff1a;去欧美国家旅游时&#xff0c;由于插口和电压不一样&am…

2024年【四川省安全员B证】新版试题及四川省安全员B证模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 四川省安全员B证新版试题是安全生产模拟考试一点通总题库中生成的一套四川省安全员B证模拟考试&#xff0c;安全生产模拟考试一点通上四川省安全员B证作业手机同步练习。2024年【四川省安全员B证】新版试题及四川省安…

苍穹外卖项目DAY01

苍穹外卖项目Day01 1、软件开发整体介绍 1.1、软件开发流程 1.2、角色分工 项目经理&#xff1a;对整个项目负责&#xff0c;任务分配、把控进度产品经理&#xff1a;进行需求调研&#xff0c;输出需求调研文档、产品原型等UI设计师&#xff1a;根据产品原型输出界面效果图架…

第130天:内网安全-横向移动PTH哈希PTT 票据PTK密匙Kerberos密码喷射

环境搭建 这里这个环境继续上一篇文章搭建的环境 案例一&#xff1a;域横向移动-PTH-Mimikatz&NTLM 什么是pth&#xff1f; PTH Pass The Hash &#xff0c;通过密码散列值 ( 通常是 NTLM Hash) 来进行攻击。在域环境中&#xff0c;用户登录计算机时使用的域账号&…

Linux_Shell判断循环,函数实例,状态码-07

一&#xff1a;分支语句 结束语句等于开始语句得单词反着写 1.1 if语句 if 表达式 then逻辑处理 elif 表达式 then逻辑处理 else逻辑处理 fi 1.1.1 单支语句 1.1.2 双分支语句 1.1.3 多分支语句 1.2 case条件结构语句 1.2.1 case语法结构 case 表达式 in 值) ;; esac 1.2…

Redis14-缓存同步

目录 缓存同步策略 安装Canal 监听Canal 缓存同步策略 缓存数据同步的常见方式有三种&#xff1a; 1.设置有效期&#xff1a;给缓存设置有效期&#xff0c;到期后自动删除&#xff0c;再次查询时更新 优势&#xff1a;简单、方便缺点&#xff1a;时效性差&#xff0c;缓存…

【CPP】CPP的STL(前篇)

目录 12 STL(前篇)12.1 什么是STL12.2 string类12.2.1 什么是string12.2.2 string类的构造函数12.2.3 string类的析构函数12.2.4 string类的[]重载12.2.5 string类的迭代器的简单了解12.2.6 auto关键字12.2.7 范围for12.2.8 反向迭代器 -- reverse12.2.9 const迭代器12.2.10 CP…

superset定制化配置修改总结

1.需要想用iframe引入dashboard时&#xff0c; URL 参数可用于修改仪表板的呈现方式&#xff0c;standalone0 属性枚举描述standalone0仪表盘正常显示1顶部导航已隐藏2顶部导航 标题被隐藏3顶部导航 标题 顶级标签被隐藏show_filters0渲染没有过滤栏的仪表板1&#xff08;默…

深度学习 —— 个人学习笔记16(目标检测和边界框、目标检测数据集)

声明 本文章为个人学习使用&#xff0c;版面观感若有不适请谅解&#xff0c;文中知识仅代表个人观点&#xff0c;若出现错误&#xff0c;欢迎各位批评指正。 三十二、目标检测和边界框 import torch import matplotlib.pyplot as plt from matplotlib_inline import backend_…

Python爬虫开发:BeautifulSoup、Scrapy入门

在现代网络开发中&#xff0c;网络爬虫是一个非常重要的工具。它可以自动化地从网页中提取数据&#xff0c;并且可以用于各种用途&#xff0c;如数据收集、信息聚合和内容监控等。在Python中&#xff0c;有多个库可以用于爬虫开发&#xff0c;其中BeautifulSoup和Scrapy是两个非…