main b16cb7b364a6 cached
62 files
172.8 MB
54.1k tokens
66 symbols
1 requests
Download .txt
Showing preview only (212K chars total). Download the full file or copy to clipboard to get everything.
Repository: datar5/yolov8-flask-vue-deploy
Branch: main
Commit: b16cb7b364a6
Files: 62
Total size: 172.8 MB

Directory structure:
gitextract_4lvw2n3m/

├── README.md
├── detection-backend/
│   ├── api.py
│   ├── app.py
│   ├── config.py
│   ├── demo.http
│   ├── extension.py
│   ├── modelPredict.py
│   ├── models.py
│   └── weights/
│       ├── yolov5nu_coco.pt
│       ├── yolov5su_coco.pt
│       ├── yolov8l_coco.pt
│       ├── yolov8m_coco.pt
│       ├── yolov8n_coco.pt
│       └── yolov8s_coco.pt
└── detection-fontend/
    ├── LICENSE
    ├── README.md
    ├── README_EN.md
    ├── auto-imports.d.ts
    ├── components.d.ts
    ├── index.html
    ├── package.json
    ├── public/
    │   ├── table.json
    │   └── template.xlsx
    ├── src/
    │   ├── App.vue
    │   ├── api/
    │   │   └── index.ts
    │   ├── assets/
    │   │   └── css/
    │   │       ├── color-dark.css
    │   │       ├── icon.css
    │   │       └── main.css
    │   ├── components/
    │   │   ├── header.vue
    │   │   ├── sidebar.vue
    │   │   └── tags.vue
    │   ├── main.ts
    │   ├── router/
    │   │   └── index.ts
    │   ├── store/
    │   │   ├── permiss.ts
    │   │   ├── sidebar.ts
    │   │   └── tags.ts
    │   ├── utils/
    │   │   └── request.ts
    │   ├── viewDetect/
    │   │   ├── ResultVue.vue
    │   │   ├── detectVue.vue
    │   │   └── uploadFile.vue
    │   ├── views/
    │   │   ├── 403.vue
    │   │   ├── 404.vue
    │   │   ├── charts.vue
    │   │   ├── dashboard.vue
    │   │   ├── donate.vue
    │   │   ├── editor.vue
    │   │   ├── export.vue
    │   │   ├── form.vue
    │   │   ├── home.vue
    │   │   ├── icon.vue
    │   │   ├── import.vue
    │   │   ├── login.vue
    │   │   ├── markdown.vue
    │   │   ├── permission.vue
    │   │   ├── table.vue
    │   │   ├── tabs.vue
    │   │   ├── upload.vue
    │   │   └── user.vue
    │   └── vite-env.d.ts
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# Yolov8-flask-vue(本科毕设)

这是一个基于ultralytics的一个部署到flask后端,然后vue作为前端所展示的一个通用的Yolo目标检测的展示页面,其实本质上类似于有着web页面外观的本地exe项目(因为数据库是个本地文件,放在sqlite上)

需要在pycharm等IDE安装好sqlite相关连接

基本上只要是ultralytics训练好的模型都可以运行,也就是说官方提供的COCO训练好的模型都可以在这个Yolo通用前端平台上使用

后端需要安装好环境才能运行,要注意先运行后端再是前端,因为前端的环境是很容易装好的

前端需要安装好Node.js到电脑上,然后只需要将,只需要在WebStorm上等随意的IDE点击 package.json 上的播放按钮就可以了

![img.png](img/img.png)


后端是flask,要安装很多的库,先安装好相应的库,IDE会提示python那些库没有安装上去的



这是毕设模型变体的训练过程,其中comet的Yolov5su当中因为VPN不稳定中间断了一些就不显示了

![img.png](img/mAP50.png)

个人毕设训练这些模型不方便公开,这里只是提供了一个通用的YOLO实现平台,可以将自己训练好的模型放入到上面去来实现目标检测展示功能

在这个毕设里面一共是200多张图片,6个yolo模型,拉下来需要一定时间

图片上传来自于本地或者是url地址,但url地址有着严格要求

图片上传后不能修改名称,除非删除再上传,同理,模型也是如此


### 展示效果

![img.png](img/show3.png)

![img.png](img/show.png)
![img.png](img/show2.png)
![img.png](img/show4.png)

### 个人环境

#### 开发技术及工具的选择:

Windows 11

Python 3.9.13

Pytorch 1.13.1+cu117

Jetbrains-IDEA系列2022

#### 开发环境:

数据库:使用的是sqlite本地的

硬件:电脑内存32G左右,GPU RTX A5000

Windows 11以上操作系统;

软件: Anaconda22.9.0 + python3.9.13 + Pytorch1.13.1+cu117 + Pycharm(或者是DataSpell) + WebStorm + Node.js 18.14.1 (x64)

### 低配部署环境:

硬件:电脑内存16G左右,GPU RTX 1050Ti

Windows 10以上操作系统;

软件: Pycharm(或者是DataSpell) + WebStorm + Node.js 18.14.1 (x64) + flask版本2.2左右

### 后续说明

本人现在忙着准备深大的研究生复试(❌),差一名,初试分数太低了(不到320)

备考二战,初试结束,今年11408是目前最难的一年,个人觉得408以后难度还会刷新

毕业论文已经完成

现在Yolov8库的更新情况不知道怎么样了,这个项目有半年没碰过了。。

后面会对这个项目细节相关进行说明


### 感谢 & 技术提供

ultralytics官网: https://github.com/ultralytics/ultralytics

![img.png](img/img9.png)

![img.png](img/img2.png)

实验数据记录: https://www.comet.com/site/lp/yolov5-with-comet/?utm_source=yolov5&utm_medium=partner&utm_campaign=partner_yolov5_2022&utm_content=github

![img.png](img/img6.png)

前端提供:
https://github.com/lin-xin/vue-manage-system





================================================
FILE: detection-backend/api.py
================================================
import os
import shutil

from flask.views import MethodView, request
from models import File, WModel, Object, Result
from extension import db
from config import save_path, \
    result_path, img_url_show, result_url_show, cache_save_model_path, weights_path


class FileApi(MethodView):
    """
    后端接口部分 RESTful API风格
    """

    def __init__(self):
        super(FileApi, self).__init__()

    def get(self, fid):
        """
        查询数据
        :param fid: 文件id
        :return: json
        """
        try:
            if not fid:
                files: [File] = File.query.all()
                results = [
                    {
                        'fid': file.fid,
                        'name': file.name,
                        'timestamp': file.timestamp,
                        'type': file.type,
                        'origin': file.origin,
                        'width': file.width,
                        'height': file.height
                    }
                    for file in files
                ]

            else:
                file = File.query.get(fid)
                results = {
                    'fid': file.fid,
                    'name': file.name,
                    'timestamp': file.timestamp,
                    'type': file.type,
                    'origin': file.origin,
                    'width': file.width,
                    'height': file.height
                }

            return {
                'status': 'success',
                'message': '数据查询成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据查询出现异常',
                'info': str(e)
            }

    def delete(self, fid):
        """
        删除操作
        :param fid: 文件id
        :return: json
        """
        try:
            file: File = File.query.get(fid)
            db.session.delete(file)
            db.session.commit()

            return {
                'status': 'success',
                'message': '数据删除成功',
            }
        except Exception as e:
            return {
                'status': 'error',
                'message': '数据删除出现异常',
                'info': str(e)
            }

    def post(self):  # 添加数据
        """
        添加操作
        :return: json
        """
        try:
            height = request.json.get("height")
            width = request.json.get("width")
            name = request.json.get("name")
            type = request.json.get("type")
            origin = request.json.get("origin")

            file = File()

            file.name = name
            file.height = height
            file.width = width
            file.type = type
            file.origin = origin

            db.session.add(file)
            db.session.commit()

            return {
                'status': 'success',
                'message': '数据添加成功'
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据添加出现异常',
                'info': str(e)
            }


class WModelApi(MethodView):
    """
    后端接口部分 RESTful API风格
    """

    def __init__(self):
        super(WModelApi, self).__init__()

    def get(self, wid):
        """
        查询数据
        :param wid: 模型的wid
        :return: json
        """
        try:
            if not wid:
                wmodels: [WModel] = WModel.query.all()
                results = [
                    {
                        'wid': wmodel.wid,
                        'model_name': wmodel.model_name,
                        'model_type': wmodel.model_type,
                        'model_dataset': wmodel.model_dataset,
                    }
                    for wmodel in wmodels
                ]

            else:
                wmodel: WModel = WModel.query.get(wid)
                results = {
                    'wid': wmodel.wid,
                    'model_name': wmodel.model_name,
                    'model_type': wmodel.model_type,
                    'model_dataset': wmodel.model_dataset,
                }

            return {
                'status': 'success',
                'message': '数据查询成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据查询出现异常',
                'info': str(e)
            }

    def delete(self, wid):
        """
        删除操作
        :param rid: 结果id
        :return: json
        """
        try:
            if wid > 6:
                wmodel: WModel = WModel.query.get(wid)

                delete_path = weights_path + wmodel.model_name + '.pt'
                os.remove(delete_path)

                db.session.delete(wmodel)
                db.session.commit()

                return {
                    'status': 'success',
                    'message': '数据删除成功',
                }

            else:
                return {
                    'status': 'error',
                    'message': '不允许删除前六个模型',
                }
        except Exception as e:
            return {
                'status': 'error',
                'message': '数据删除出现异常',
                'info': str(e)
            }

    def put(self, wid):
        """
        修改模型信息
        :param wid: 模型id
        :return: json
        """
        try:
            model: WModel = WModel.query.get(wid)
            model.model_name = request.json.get('model_name')
            model.model_type = request.json.get('model_type')
            model.model_dataset = request.json.get('model_dataset')

            db.session.commit()

            return {
                'status': 'success',
                'message': '数据修改成功'
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据修改出现异常',
                'info': str(e)
            }

    def post(self):  # 添加数据
        """
        添加操作
        :return: json
        """
        try:
            model_name = request.json.get('model_name')
            model_type = request.json.get('model_type')
            model_dataset = request.json.get('model_dataset')

            wmodel: WModel = WModel()
            wmodel.model_name = model_name
            wmodel.model_type = model_type
            wmodel.model_dataset = model_dataset

            new_path = weights_path + model_name + '.pt'
            shutil.copy(src=cache_save_model_path, dst=new_path)

            db.session.add(wmodel)
            db.session.commit()

            return {
                'status': 'success',
                'message': '数据添加成功'
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据添加出现异常',
                'info': str(e)
            }


class ResultApi(MethodView):
    """
    后端接口部分 RESTful API风格
    """

    def __init__(self):
        super(ResultApi, self).__init__()

    def get(self, rid):
        """
        查询数据
        :param rid: 结果rid
        :return: json
        """
        try:
            if not rid:
                n_results: [Result] = Result.query.all()
                results = [
                    {
                        'rid': result.rid,
                        'wid': result.wid,  # 首要默认为检测模型
                        'timestamp': result.timestamp,  # 时间戳
                        'type': result.type,  # 文件类型
                        'addition': result.addition,  # 额外说明
                        'mission_type': result.mission_type,  # 任务类型
                        'filename': result.filename,  # 被检测的源头文件
                        'num': result.num  # 一共有多少个检测物体
                    }
                    for result in n_results
                ]

            else:
                result: Result = Result.query.get(rid)
                results = {
                    'rid': result.rid,
                    'wid': result.wid,  # 同理
                    'timestamp': result.timestamp,
                    'type': result.type,
                    'addition': result.addition,
                    'mission_type': result.mission_type,
                    'filename': result.filename,
                    'num': result.num
                }

            return {
                'status': 'success',
                'message': '数据查询成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据查询出现异常',
                'info': str(e)
            }

    def delete(self, rid):
        """
        删除操作,前面是与 @app.route('/delete/result', methods=['DELETE']) 紧接着操作的,
        其对应的request.json格式是
            {
                "addition": "单独的检测任务",
                "filename": "b7a8e795-ff80fddf",
                "mission_type": "detection",
                "num": 23,
                "rid": 1,
                "timestamp": 1677256499,
                "type": "jpg",
                "wid": 1
            }
        前端从@app.route('/delete/result', methods=['DELETE'])相应完了之后将json的rid传入此函数中
        :param rid: 结果id
        :return: json
        """
        try:
            result: Result = Result.query.get(rid)
            path = result_path + result.filename \
                   + '_' + str(result.timestamp) + '.' + result.type
            os.remove(path)

            db.session.delete(result)
            db.session.commit()

            return {
                'status': 'success',
                'message': '数据删除成功',
            }
        except Exception as e:
            return {
                'status': 'error',
                'message': '数据删除出现异常',
                'info': str(e)
            }

    def put(self, rid):
        try:
            result: Result = Result.query.get(rid)
            result.addition = request.json.get('addition')
            result.mission_type = request.json.get('mission_type')

            db.session.commit()

            return {
                'status': 'success',
                'message': '数据修改成功',
            }
        except Exception as e:
            return {
                'status': 'error',
                'message': '数据修改出现异常',
                'info': str(e)
            }



    def post(self):  # 添加数据
        """
        添加操作, json来自 @app.route('/detect', methods=['POST']) 那里
        example: {
          "message": "检测成功",
           传入的json为
               {
                  "addition": "单独的检测任务",
                  "attr": "jpg",
                  "filename": "b7a8e795-ff80fddf",
                  "mission_type": "detection",
                  "num": 21,
                  "timestamp": 1677250829,
                  "wid": 2
               }
        }

        :return: json
        """
        try:
            form = request.json
            # print(form)

            result = Result()
            result.wid = form.get('wid')
            result.num = form.get('num')
            result.type = form.get('type')
            result.timestamp = form.get('timestamp')
            result.addition = form.get('addition')
            result.mission_type = form.get('mission_type')
            result.filename = form.get('filename')

            db.session.add(result)
            db.session.commit()

            return {
                'status': 'success',
                'message': '数据添加成功'
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据添加出现异常',
                'info': str(e)
            }


class ObjApi(MethodView):
    """
    后端接口部分 RESTful API风格
    """

    def __init__(self):
        super(ObjApi, self).__init__()

    def get(self, rid):
        """
        查询数据
        :param rid: 结果rid,外键查询
        :return: json
        """
        try:
            objects: [Object] = db.session.query(Object).filter(Object.rid == rid).all()  # 根据外键rid来查询
            results = [
                {
                    'oid': object.oid,
                    'rid': object.rid,
                    'cls': object.cls,
                    'x1': object.x1,
                    'y1': object.y1,
                    'x2': object.x2,
                    'y2': object.y2,
                    'conf': object.conf
                }
                for object in objects
            ]

            return {
                'status': 'success',
                'message': '数据查询成功',
                'length': len(results),
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据查询出现异常',
                'info': str(e)
            }

    def delete(self, rid):
        """
        删除操作,首先执行此函数,
        然后再执行@app.route('/delete/result', methods=['DELETE'])删除目标文件操作
        最后再将对应的result实例变量从数据库删除掉
        :param rid: 结果id
        :return: json
        """
        try:
            objects: [Object] = db.session.query(Object).filter(Object.rid == rid).all()  # 根据外键rid来查询
            for object in objects:
                db.session.delete(object)
            db.session.commit()

            return {
                'status': 'success',
                'message': '数据删除成功',
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据删除出现异常',
                'info': str(e)
            }

    def post(self):  # 添加数据
        """
        添加操作, json来自 @app.route('/detect', methods=['POST']) 那里
        example: {
          "message": "检测成功",
          "results": {
            "objects": [
                    .....
            ],
            "log": {
              "addition": "单独的检测任务",
              "attr": "jpg",
              "filename": "b7a8e795-ff80fddf",
              "mission_type": "detection",
              "num": 21,
              "timestamp": 1677250829,
              "wid": 2
              }
            },
          "status": "success"
        }

        :return: json
        """
        try:
            objects = request.json['results']["objects"]
            _about = request.json['results']['log']

            result: Result = db.session.query(Result).filter(Result.timestamp == _about["timestamp"]) \
                .filter(Result.filename == _about['filename']).first()

            for obj in objects:
                object = Object()

                object.rid = result.rid
                object.cls = obj['cls']
                object.x1 = obj['x1']
                object.y1 = obj['y1']
                object.x2 = obj['x2']
                object.y2 = obj['y2']
                object.conf = obj['conf']

                db.session.add(object)

            db.session.commit()

            return {
                'status': 'success',
                'message': '数据添加成功'
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数据添加出现异常',
                'info': str(e)
            }


class PageApi(MethodView):  # 分页接口
    def __init__(self):
        super(PageApi, self).__init__()

    def get(self):
        try:
            # print(request.args)
            curPage = int(request.args.get('curPage'))
            pageSize = int(request.args.get('pageSize'))
            tableName = request.args.get('tableName')
            # print(curPage, pageSize, tableName)
            results = []
            if tableName == 'file':
                resultsIteam = File.query.order_by(File.fid.asc()).paginate(
                    page=curPage, per_page=pageSize, error_out=True
                ).items
                results = [
                    {
                        'fid': file.fid,
                        'name': file.name,
                        'timestamp': file.timestamp,
                        'type': file.type,
                        'origin': file.origin,
                        'width': file.width,
                        'height': file.height,
                        'img_url': img_url_show + str(file.name) + '.'
                                   + str(file.type)
                    }
                    for file in resultsIteam
                ]

            elif tableName == 'model':
                resultsIteam = WModel.query.order_by(WModel.wid.asc()).paginate(
                    page=curPage, per_page=pageSize, error_out=True
                ).items
                results = [
                    {
                        'wid': wmodel.wid,
                        'model_name': wmodel.model_name,
                        'model_type': wmodel.model_type,
                        'model_dataset': wmodel.model_dataset,
                    }
                    for wmodel in resultsIteam
                ]

            elif tableName == 'result':
                resultsIteam = Result.query.order_by(Result.rid.asc()).paginate(
                    page=curPage, per_page=pageSize, error_out=True
                ).items
                results = [
                    {
                        'rid': result.rid,
                        'wid': result.wid,
                        'timestamp': result.timestamp,
                        'type': result.type,
                        'addition': result.addition,
                        'mission_type': result.mission_type,
                        'filename': result.filename,
                        'num': result.num,
                        'img_url': result_url_show + str(result.filename) + '_' + str(result.timestamp) + '.'
                                   + str(result.type)
                    }
                    for result in resultsIteam
                ]

            else:
                pass

            return {
                'status': 'success',
                'message': '分页查找成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '分页查找出现异常',
                'info': str(e)
            }


class ObjectPageApi(MethodView):
    def __init__(self):
        super(ObjectPageApi, self).__init__()

    def get(self, rid):
        """
        :param rid:
        Object检测子结果的分页实现
        :return:
        """
        try:
            curPage = int(request.args.get('curPage'))
            pageSize = int(request.args.get('pageSize'))
            # print(curPage, pageSize)
            resultsItem = Object.query \
                .filter(Object.rid == rid).order_by(Object.conf.desc()).paginate(
                page=curPage, per_page=pageSize, error_out=True
            ).items
            # print(resultsItem)
            results = [
                {
                    'oid': object.oid,
                    'rid': object.rid,
                    'cls': object.cls,
                    'x1': object.x1,
                    'y1': object.y1,
                    'x2': object.x2,
                    'y2': object.y2,
                    'conf': object.conf
                } for object in resultsItem
            ]

            return {
                'status': 'success',
                'message': '分页查找成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '分页查找出现异常',
                'info': str(e)
            }


class NumApi(MethodView):  # 分页显示数目的接口
    def __init__(self):
        super(NumApi, self).__init__()
        self.config = ['file', 'model', 'result']

    def get(self):
        """
        :param tableName:
        :return: 总数数字
        """
        try:
            results = 0
            # print(results)
            # curPage = request.args.get('curPage')
            # pageSize = request.args.get('pageSize')
            # print(request.args)
            tableName = request.args.get('tableName')
            rid = int(request.args.get('rid'))

            if tableName in self.config:
                if tableName == 'file':
                    results = File.query.count()
                elif tableName == 'model':
                    results = WModel.query.count()
                else:
                    results = Result.query.count()
            else:
                if rid > 0:
                    results = Object.query.filter(Object.rid == rid).count()

            return {
                'status': 'success',
                'message': '数量查找成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数量查找出现异常',
                'info': str(e)
            }


class DeblurSearchApi(MethodView):  # 分页实现模糊查找的接口
    def __init__(self):
        super(DeblurSearchApi, self).__init__()

    def get(self):
        try:
            results = []
            tableName = request.args.get('tableName')
            keyword = request.args.get('keyword')
            curPage = int(request.args.get('curPage'))
            pageSize = int(request.args.get('pageSize'))
            # print(tableName, keyword, curPage, pageSize)
            if tableName == 'file':
                resultsIteam = File.query.filter(File.name.like('%' + keyword + '%')) \
                    .order_by(File.fid.desc()).paginate(
                    page=curPage, per_page=pageSize, error_out=True
                ).items
                # print(len(resultsIteam))
                results = [
                    {
                        'fid': file.fid,
                        'name': file.name,
                        'timestamp': file.timestamp,
                        'type': file.type,
                        'origin': file.origin,
                        'width': file.width,
                        'height': file.height,
                        'img_url': img_url_show + str(file.name) + '.'
                                   + str(file.type)
                    }
                    for file in resultsIteam
                ]

            elif tableName == 'model':
                resultsIteam = WModel.query.filter(WModel.model_name.like('%' + keyword + '%')) \
                    .order_by(WModel.wid.asc()).paginate(
                    page=curPage, per_page=pageSize, error_out=True
                ).items
                results = [
                    {
                        'wid': wmodel.wid,
                        'model_name': wmodel.model_name,
                        'model_type': wmodel.model_type,
                        'model_dataset': wmodel.model_dataset,
                    }
                    for wmodel in resultsIteam
                ]

            elif tableName == 'result':
                resultsIteam = Result.query.filter(Result.filename.like('%' + keyword + '%')) \
                    .order_by(Result.rid.desc()).paginate(
                    page=curPage, per_page=pageSize, error_out=True
                ).items

                results = [
                    {
                        'rid': result.rid,
                        'wid': result.wid,
                        'timestamp': result.timestamp,
                        'type': result.type,
                        'addition': result.addition,
                        'mission_type': result.mission_type,
                        'filename': result.filename,
                        'num': result.num,
                        'img_url': result_url_show + str(result.filename) + '_' +
                                   str(result.timestamp) + '.' + str(result.type)
                    }
                    for result in resultsIteam
                ]

            else:
                pass

            return {
                'status': 'success',
                'message': '模糊查找成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '模糊查找出现异常',
                'info': str(e)
            }


class DeblurSearchNumApi(MethodView):
    def __init__(self):
        super(DeblurSearchNumApi, self).__init__()
        self.config = ['file', 'model', 'result']

    def get(self):
        try:
            tableName = request.args.get('tableName')
            # rid = int(request.args.get('rid'))
            keyword = request.args.get('keyword')
            results = 0

            if tableName in self.config:
                if tableName == 'file':
                    results = File.query.filter(File.name
                                                .like('%' + keyword + '%')).count()
                elif tableName == 'model':
                    results = WModel.query.filter(WModel.model_name
                                                  .like('%' + keyword + '%')).count()
                else:
                    results = Result.query.filter(Result.filename
                                                  .like('%' + keyword + '%')).count()
            else:
                pass

            return {
                'status': 'success',
                'message': '数量关键词模糊查找成功',
                'results': results
            }

        except Exception as e:
            return {
                'status': 'error',
                'message': '数量关键词模糊查找出现异常',
                'info': str(e)
            }


================================================
FILE: detection-backend/app.py
================================================
import datetime
import random
import time
from flask import Flask, request, make_response
from ultralytics import YOLO
import cv2

from config import save_path, \
    result_path, default_weights, default_model_json, weights_path, cache_save_model_path

from api import FileApi, ResultApi, ObjApi, WModelApi
from api import PageApi, ObjectPageApi, NumApi
from api import DeblurSearchApi, DeblurSearchNumApi

from models import File, WModel
from extension import db, cors

import os
import io
import sys
from PIL import Image
import requests

# 项目系统路径配置
curPath = os.path.dirname(os.path.abspath(__file__))
root_path = os.path.abspath(os.path.dirname(curPath) + os.path.sep + os.path.sep)
sys.path.append(root_path)

# 创建flask项目
app = Flask(__name__)

# flask项目常量初始化设置
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///detection.sqlite'
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["model"] = YOLO(default_weights)
app.config["model_json"] = default_model_json
app.config["cls"] = app.config["model"].names

# 爬虫伪造头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30 '
}

db.init_app(app)  # 注册实例化
cors.init_app(app)  # 注册跨域请求伪造相关

"""
对数据库的各个表相关操作添加相应路由规则
"""

file_view = FileApi.as_view('file_api')
app.add_url_rule('/files/', defaults={'fid': None},
                 view_func=file_view, methods=['GET'])  # 查询
app.add_url_rule('/files', view_func=file_view, methods=['POST'])  # 添加
app.add_url_rule('/files/<int:fid>', view_func=file_view,
                 methods=['GET', 'DELETE'])  # 查询,删除

wmodel_view = WModelApi.as_view('wmodel_api')
app.add_url_rule('/wmodel/', defaults={'wid': None},
                 view_func=wmodel_view, methods=['GET'])  # 查询
app.add_url_rule('/wmodel', view_func=wmodel_view, methods=['POST'])  # 添加
app.add_url_rule('/wmodel/<int:wid>', view_func=wmodel_view,
                 methods=['GET', 'PUT', 'DELETE'])  # 查询,修改,删除

result_view = ResultApi.as_view('result_api')
app.add_url_rule('/result/', defaults={'rid': None},
                 view_func=result_view, methods=['GET'])  # 查询
app.add_url_rule('/result', view_func=result_view, methods=['POST'])  # 添加
app.add_url_rule('/result/<int:rid>', view_func=result_view,
                 methods=['GET', 'DELETE', 'PUT'])  # 查询,删除,修改

obj_view = ObjApi.as_view('obj_api')
app.add_url_rule('/obj/', defaults={'rid': 1},
                 view_func=obj_view, methods=['GET'])  # 查询
app.add_url_rule('/obj', view_func=obj_view, methods=['POST'])  # 添加
app.add_url_rule('/obj/<int:rid>', view_func=obj_view,
                 methods=['GET', 'DELETE'])  # 查询,删除

"""
对数据库的各个表相关分页查找实现的路由规则
各个表相关总数输出实现路由规则
"""

page_view = PageApi.as_view('page_api')
app.add_url_rule('/page', view_func=page_view, methods=['GET'])

object_page_view = ObjectPageApi.as_view('objectPage_api')
app.add_url_rule('/objectPage/<int:rid>', view_func=object_page_view, methods=['GET'])

num_view = NumApi.as_view('num_api')
app.add_url_rule('/num', view_func=num_view, methods=['GET'])

"""
对数据库的各个表相关关键词实现模糊查找
各个表模糊查找相关总数输出实现路由规则
"""
deblur_view = DeblurSearchApi.as_view('deblurSearch_api')
app.add_url_rule('/deblurS', view_func=deblur_view, methods=['GET'])

deblur_view_num = DeblurSearchNumApi.as_view('deblurSearchNum_api')
app.add_url_rule('/deblurSNum', view_func=deblur_view_num, methods=['GET'])

"""
涉及到对数据库(相关消息传递)之外的功能
比如 显示图片,爬取图片,切换模型,图片检测,文件上传,文件删除等等
"""

# @app.route('/')
# def hello_world():  # put application's code here
#     return 'Hello World!'

"""
显示图片,上传图片相关操作
"""


@app.route('/img/<string:name>')
def show_img(name):
    img_url = save_path + name
    if request.method == 'GET':
        if name is None:
            pass
        else:
            image_data = open(img_url, "rb").read()
            response = make_response(image_data)
            response.headers['Content-Type'] = 'image/jpg'
            return response
    else:
        pass


@app.route('/upload/pic/', methods=['POST'])
def upload_load():
    """
    从本地上上传图片到后端服务器上
    :return: json
    """
    try:
        filename = request.files.get('file').filename
        name = filename.split(".")[0] + "_" + str(int(time.time()))
        type = filename.split('.')[-1]
        origin = 'localhost://' + filename

        data = request.files.get('file')

        new_path = save_path + name + '.' + type
        data.save(new_path, buffer_size=10000000000)

        img = Image.open(new_path)  # 为了获取图片的长宽信息

        width, height = img.width, img.height
        return {
            'status': 'success',
            'message': '图片上传成功',
            'code': 200,
            'results': {
                'name': name,
                'type': type,
                'origin': origin,
                'width': width,
                'height': height,
            }
        }

    except Exception as e:
        return {
            'status': 'error',
            'message': '图片上传出现异常',
            'info': str(e)
        }


@app.route('/upload/url', methods=['POST'])
def get_url_pic():
    """
    获取url上的图片
    :param url: 图片的url
    :return: json
    """
    try:
        url = request.json.get("url")
        res = requests.get(url, headers=headers)
        if res.status_code == 200:
            content = res.content
            file = url.split('/')[-1]
            name = file.split('.')[0] + '_' + str(int(time.time()))
            type = file.split('.')[-1]
            filePath = save_path + name + '.' + type

            # type = file.split('.')[-1]
            # name = file.split('.')[0]

            origin = url

            with open(filePath, 'wb') as f:
                f.write(content)
            f.close()

            img = Image.open(filePath)
            width, height = img.width, img.height

            return {
                'status': 'success',
                'message': '图片爬取成功',
                'code': 200,
                'results': {
                    'name': name,
                    'type': type,
                    'origin': origin,
                    'width': width,
                    'height': height,
                }
            }
        else:
            return {
                'status': 'fail',
                'message': '图片爬取失败',
                'code': res.status_code
            }

    except Exception as e:
        return {
            'status': 'error',
            'message': '图片爬取出现异常',
            'info': str(e)
        }


"""
模型相关操作,切换模型,显示某个模型细节,显示当前模型
"""


@app.route('/detail/model', methods=['GET'])
def show_model_detail():
    """
    查看某个模型的内部细节
    :param wid:
    :return:
    """
    try:
        args = request.args
        # print(model_json)
        path = weights_path + args.get('model_name') + '.pt'

        _cache_yolo: YOLO = YOLO(path)
        results = {
            'names': _cache_yolo.names,
            'params': _cache_yolo.model.yaml
        }
        return {
            'status': 'success',
            'message': '查看模型细节成功',
            'results': results
        }
    except Exception as e:
        return {
            'status': 'error',
            'message': '查看模型细节失败',
            'info': str(e)
        }


@app.route('/current', methods=['GET'])
def show_current_model():
    """
    查看当前模型函数
    :return: json
    """
    try:
        return {
            'status': 'success',
            'message': '查看当前模型成功',
            'results': app.config["model_json"]
        }

    except Exception as e:
        return {
            'status': 'error',
            'message': '查看当前模型失败',
            'info': str(e)
        }


@app.route('/switch', methods=["PUT"])
def switch_model():
    """
    前端找wmodel类的参数 -> 获得参数 -> 切换模型
    切换模型,首先参数要从前端request哪里获得
    request.json格式是
    {
      "model_dataset": "bdd",
      "model_name": "yolov8l_bdd",
      "model_type": "yolov8",
      "wid": 1
    }

    :return: json
    """
    try:
        model_json = request.json
        name = model_json['model_name']

        if name is None:
            return {
                'status': 'error',
                'message': '切换模型失败',
                'info': '模型名字为空'
            }

        path = weights_path + model_json['model_name'] + '.pt'

        app.config["model"]: YOLO = YOLO(path)
        # print(app.config["model"].model.args)

        # print(app.config['model'])
        app.config["model_json"] = model_json
        app.config["cls"] = app.config["model"].names

        return {
            'status': 'success',
            'message': '切换模型成功',
        }

    except Exception as e:
        return {
            'status': 'error',
            'message': '切换模型失败',
            'info': str(e)
        }


@app.route('/upload/model/', methods=["POST"])
def save_model_cache():
    try:
        data = request.files.get('file')
        filename = request.files.get('file').filename
        type = filename.split('.')[-1]

        if type != 'pt':
            return {
            'status': 'error',
            'message': '传送的不是pt文件',
            }

        data.save(cache_save_model_path, buffer_size=10000000000)
        return {
            'status': 'success',
            'message': '模型暂缓成功',
        }

    except Exception as e:
        return {
            'status': 'error',
            'message': '模型暂缓出现异常',
            'info': str(e)
        }


"""
检测图片任务相关
"""


@app.route('/resultImg/<string:name>')
def show_result_img(name):
    """
    返回结果相关
    :param name: 结果文件名称
    :return:
    """
    img_url = result_path + name
    if request.method == 'GET':
        if name is None:
            pass
        else:
            image_data = open(img_url, "rb").read()
            response = make_response(image_data)
            response.headers['Content-Type'] = 'image/jpg'
            return response
    else:
        pass


@app.route('/detect', methods=['POST'])
def detection():
    """
    实现检测的功能
    返回的json参数 与POST http://127.0.0.1:5000/result
    以及 POST http://127.0.0.1:5000/obj 配合使用

    前端找File类的参数 ->前端找现存文件 ->  绘制图片 -> 保存文件 -> 获得参数 -> 接下来添加数据库对应记录
                                         └─> 绘制失败则报错
    与 POST http://127.0.0.1:5000/result
    以及 POST http://127.0.0.1:5000/obj  结合使用

    最后前端获得检测结果将结果保存到后端上(result,objection表)
    request.json格式是
    {
    "fid": 2,
    "height": 720,
    "name": "b7a8e795-ff80fddf",
    "origin": "localhost://b7a8e795-ff80fddf.jpg",
    "timestamp": "Fri, 24 Feb 2023 19:04:44 GMT",
    "type": "jpg",
    "width": 1280
    }

    :return: json
    """
    try:
        detection_json = request.json

        filename = detection_json['name']
        type = detection_json['type']

        model = app.config["model"]
        cls = app.config["cls"]
        wid = app.config["model_json"]['wid']
        # print(cls)

        path = save_path + filename + '.' + type
        # print(path)
        results = model(path)

        length = 0
        info = []
        for result in results:
            boxes = result.boxes.data.cpu().numpy().tolist()
            length = len(boxes)

            # pic_data = []
            for j in range(length):
                pic_data = {
                    'x1': boxes[j][0],
                    'y1': boxes[j][1],
                    'x2': boxes[j][2],
                    'y2': boxes[j][3],
                    'conf': boxes[j][-2],
                    'cls': cls[int(boxes[j][-1])]
                }
                info.append(pic_data)

        res_plotted = results[0].plot(show_conf=True, line_width=2, font_size=3)
        timestamp = int(time.time())

        img_path = result_path + filename + '_' + str(timestamp) + '.' + type
        # print(res_plotted)
        cv2.imwrite(img_path, res_plotted)

        return {
            'status': 'success',
            'message': '检测成功',
            'results': {
                'log': {
                    'wid': wid,
                    'addition': '单独的检测任务',
                    'timestamp': timestamp,
                    'mission_type': 'detection',
                    'filename': filename,
                    'type': type,
                    'num': length
                },
                'objects': info
            }
        }

    except Exception as e:
        return {
            'status': 'error',
            'message': '检测失败',
            'info': str(e)
        }


@app.route('/delete/result', methods=['DELETE'])
def delete_result():
    """
    这里的函数是实现删除结果图片文件,与 DELETE http://127.0.0.1:5000/result/<int:rid>
                               以及 DELETE http://127.0.0.1:5000/obj/<int:rid>  结合使用
    前端返回参数 ->前端找现存文件 ->  删除图片 -> 删除完成 -> 接下来删除数据库对应记录
                                   └─> 删除失败则报错
    request.json格式是
    {
        "addition": "单独的检测任务",
        "filename": "b7a8e795-ff80fddf",
        "mission_type": "detection",
        "num": 23,
        "rid": 1,
        "timestamp": 1677256499,
        "type": "jpg",
        "wid": 1
    }

    :return: json
    """
    try:
        result_json = request.json
        file = result_json['filename'] + '_' + str(result_json['timestamp']) + '.' + result_json['type']
        path = result_path + file
        os.remove(path)

        return {
            'status': 'success',
            'message': '删除记录图片成功',
        }

    except Exception as e:
        return {
            'status': 'error',
            'message': '删除记录图片失败',
            'info': str(e)
        }


@app.route('/delete/file', methods=['DELETE'])
def delete_file():
    """
    这里的函数是实现删除结果图片文件,与 DELETE   结合使用
    前端返回参数 ->前端找现存文件 ->  删除图片 -> 删除完成 -> 接下来删除数据库对应记录
                                   └─> 删除失败则报错

    request.json格式是,来自前端上面的
    {
          type: response.results.type,
          name: response.results.name,
    }
    :return: json
    """
    try:
        type = request.json.get("type")
        name = request.json.get("name")

        file_path = save_path + name + '.' + type
        # print(file_path)
        os.remove(file_path)
        # print(request.data)
        # print(request.json.get("type"), request.json.get('name'))
        # print(request.json)
        return {
            'status': 'success',
            'message': '删除图片成功',
        }

    except Exception as e:
        return {
            'status': 'error',
            'message': '删除图片失败',
            'info': str(e)
        }
    pass


# @app.route('/delete/model', methods=['DELETE'])
# def delete_model():
#     """
#     这里的函数是实现删除结果图片文件,与 DELETE http://127.0.0.1:5000/result/<int:rid> 结合使用
#     前端返回参数 ->前端找现存文件 ->  删除图片 -> 删除完成 -> 接下来删除数据库对应记录
#                                    └─> 删除失败则报错
#     request.json格式是
#
#
#     :return: json
#     """
#     pass


"""
指令相关
"""


@app.cli.command('init')
def init():
    db.drop_all()  # 注销所有的数据库
    db.create_all()  # 创建所有数据库
    File.init_db()
    WModel.init_db()


if __name__ == '__main__':
    app.run(debug=True)


================================================
FILE: detection-backend/config.py
================================================
save_path = '.\\pic\\save\\'
cache_save_model_path = '.\\save\\cache.pt'
result_path = '.\\pic\\result\\'
weights_path = '.\\weights\\'

cache_save_path = '.\\cache\\pic\\'
cache_model_path = '.\\cache\\model\\'

img_url_show = 'http://localhost:5000/img/'
result_url_show = 'http://localhost:5000/resultImg/'

default_weights = '.\\weights\\yolov8m_coco.pt'
default_model_json = {
    "model_dataset": "bdd",
    "model_name": "yolov8m_bdd",
    "model_type": "yolov8",
    "wid": 2
}

fixed_model_type = [
    'yolov5', 'yolov8', 'deepRFT'
]


================================================
FILE: detection-backend/demo.http
================================================
###
GET http://127.0.0.1:5000/files/2

###
GET http://127.0.0.1:5000/wmodel

### 下面是从切换模型,检测开始记录数据,查询数据,删除数据的步骤
###
###
PUT http://127.0.0.1:5000/switch
Content-Type: application/json

    {
      "model_dataset": "bdd",
      "model_name": "yolov8l_bdd",
      "model_type": "yolov8",
      "wid": 1
    }

###
GET http://127.0.0.1:5000/files/113

###
POST http://127.0.0.1:5000/detect
Content-Type: application/json

{
    "fid": 113,
    "height": 720,
    "name": "b7bcc17a-72df68f9",
    "origin": "localhost://b7bcc17a-72df68f9.jpg",
    "timestamp": "Tue, 28 Feb 2023 09:32:58 GMT",
    "type": "jpg",
    "width": 1280
}

###
POST http://127.0.0.1:5000/result
Content-Type: application/json

{
      "addition": "单独的检测任务",
      "filename": "b7bcc17a-72df68f9",
      "mission_type": "detection",
      "num": 26,
      "timestamp": 1677658144,
      "type": "jpg",
      "wid": 1
    }



###
POST http://127.0.0.1:5000/obj
Content-Type: application/json

{
  "message": "检测成功",
  "results": {
    "log": {
      "addition": "单独的检测任务",
      "filename": "b7bcc17a-72df68f9",
      "mission_type": "detection",
      "num": 26,
      "timestamp": 1677658144,
      "type": "jpg",
      "wid": 1
    },
    "objects": [
      {
        "cls": "car",
        "conf": 0.9426229596138,
        "x1": 21.0,
        "x2": 335.0,
        "y1": 502.0,
        "y2": 651.0
      },
      {
        "cls": "bus",
        "conf": 0.9366695284843445,
        "x1": 819.0,
        "x2": 1019.0,
        "y1": 451.0,
        "y2": 611.0
      },
      {
        "cls": "car",
        "conf": 0.9037463665008545,
        "x1": 588.0,
        "x2": 695.0,
        "y1": 518.0,
        "y2": 608.0
      },
      {
        "cls": "car",
        "conf": 0.8302534222602844,
        "x1": 757.0,
        "x2": 812.0,
        "y1": 527.0,
        "y2": 567.0
      },
      {
        "cls": "car",
        "conf": 0.8223326802253723,
        "x1": 363.0,
        "x2": 419.0,
        "y1": 518.0,
        "y2": 555.0
      },
      {
        "cls": "car",
        "conf": 0.7934474945068359,
        "x1": 414.0,
        "x2": 448.0,
        "y1": 521.0,
        "y2": 550.0
      },
      {
        "cls": "car",
        "conf": 0.7931928038597107,
        "x1": 690.0,
        "x2": 723.0,
        "y1": 531.0,
        "y2": 560.0
      },
      {
        "cls": "car",
        "conf": 0.7816309332847595,
        "x1": 445.0,
        "x2": 468.0,
        "y1": 526.0,
        "y2": 548.0
      },
      {
        "cls": "traffic light",
        "conf": 0.7765074372291565,
        "x1": 548.0,
        "x2": 564.0,
        "y1": 409.0,
        "y2": 452.0
      },
      {
        "cls": "car",
        "conf": 0.7473191618919373,
        "x1": 545.0,
        "x2": 577.0,
        "y1": 527.0,
        "y2": 552.0
      },
      {
        "cls": "car",
        "conf": 0.7085943818092346,
        "x1": 18.0,
        "x2": 102.0,
        "y1": 496.0,
        "y2": 548.0
      },
      {
        "cls": "car",
        "conf": 0.7016028761863708,
        "x1": 723.0,
        "x2": 748.0,
        "y1": 531.0,
        "y2": 553.0
      },
      {
        "cls": "car",
        "conf": 0.64445960521698,
        "x1": 744.0,
        "x2": 765.0,
        "y1": 532.0,
        "y2": 557.0
      },
      {
        "cls": "car",
        "conf": 0.6331145763397217,
        "x1": 70.0,
        "x2": 132.0,
        "y1": 507.0,
        "y2": 536.0
      },
      {
        "cls": "traffic light",
        "conf": 0.6008689999580383,
        "x1": 1094.0,
        "x2": 1119.0,
        "y1": 489.0,
        "y2": 509.0
      },
      {
        "cls": "car",
        "conf": 0.5753867030143738,
        "x1": 523.0,
        "x2": 539.0,
        "y1": 528.0,
        "y2": 543.0
      },
      {
        "cls": "car",
        "conf": 0.5730618238449097,
        "x1": 2.0,
        "x2": 75.0,
        "y1": 499.0,
        "y2": 555.0
      },
      {
        "cls": "car",
        "conf": 0.5328835248947144,
        "x1": 587.0,
        "x2": 604.0,
        "y1": 526.0,
        "y2": 543.0
      },
      {
        "cls": "car",
        "conf": 0.5161319971084595,
        "x1": 462.0,
        "x2": 494.0,
        "y1": 524.0,
        "y2": 547.0
      },
      {
        "cls": "traffic light",
        "conf": 0.5084584951400757,
        "x1": 1078.0,
        "x2": 1095.0,
        "y1": 431.0,
        "y2": 471.0
      },
      {
        "cls": "car",
        "conf": 0.5038861632347107,
        "x1": 302.0,
        "x2": 337.0,
        "y1": 521.0,
        "y2": 564.0
      },
      {
        "cls": "car",
        "conf": 0.41518062353134155,
        "x1": 682.0,
        "x2": 699.0,
        "y1": 528.0,
        "y2": 547.0
      },
      {
        "cls": "car",
        "conf": 0.36542969942092896,
        "x1": 573.0,
        "x2": 589.0,
        "y1": 525.0,
        "y2": 539.0
      },
      {
        "cls": "car",
        "conf": 0.35099053382873535,
        "x1": 307.0,
        "x2": 361.0,
        "y1": 512.0,
        "y2": 543.0
      },
      {
        "cls": "car",
        "conf": 0.31251752376556396,
        "x1": 504.0,
        "x2": 527.0,
        "y1": 528.0,
        "y2": 545.0
      },
      {
        "cls": "traffic sign",
        "conf": 0.2699459195137024,
        "x1": 613.0,
        "x2": 634.0,
        "y1": 473.0,
        "y2": 492.0
      }
    ]
  },
  "status": "success"
}


###
GET  http://127.0.0.1:5000/obj/1

###
DELETE http://127.0.0.1:5000/obj/1

###
GET http://127.0.0.1:5000/result/

###
GET http://127.0.0.1:5000/result/1

###
DELETE http://127.0.0.1:5000/delete/result
Content-Type: application/json

 {
      "addition": "单独的检测任务",
      "filename": "b7a8e795-ff80fddf",
      "mission_type": "detection",
      "num": 23,
      "rid": 1,
      "timestamp": 1677258002,
      "type": "jpg",
      "wid": 1
    }

###
DELETE http://127.0.0.1:5000/result/1

####分页函数测试
GET http://127.0.0.1:5000/page/
Content-Type: application/json

{
  "curPage": 1,
  "pageSize": 2,
  "tableName": "file"
}

###
GET http://127.0.0.1:5000/objectPage/1
Content-Type: application/json

{
    "curPage": 3,
    "pageSize": 5
}

###
GET http://127.0.0.1:5000/num/
Content-Type: application/json

{
  "rid": 1,
  "tableName": "model"
}

####模糊查找测试

GET http://127.0.0.1:5000/deblurS/
Content-Type: application/json

{
    "keyword": "b",
  "tableName": "result"
}


###查看当前模型参数测试
GET http://127.0.0.1:5000/current

================================================
FILE: detection-backend/extension.py
================================================
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS


db = SQLAlchemy()
cors = CORS(resources={r'/*':{'origins':'*'}})


================================================
FILE: detection-backend/modelPredict.py
================================================
from ultralytics import YOLO

model_path = './weights/yolov8n_bdd.pt'
video_path = 'video/save/2.mp4'

model = YOLO(model_path)
results = model(video_path, show=True, save=True)



================================================
FILE: detection-backend/models.py
================================================
import os

from sqlalchemy import ForeignKey
from config import cache_save_path
from PIL import Image

from extension import db
import datetime

fixed_model_type = [
    'yolov5', 'yolov8'
]


class File(db.Model):
    __tablename__ = 'file_table'
    fid = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), nullable=False)
    timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
    type = db.Column(db.String(255), nullable=False)  # 文件类型
    origin = db.Column(db.String(255), nullable=False)  # 来源
    width = db.Column(db.Integer, nullable=False)
    height = db.Column(db.Integer, nullable=False)

    @staticmethod
    def init_db():
        count: int = 1
        files = os.listdir(cache_save_path)

        # rets = [
        #     (1, 'b6f77fda-02b6e75b', 'jpg', 'localhost://b6f77fda-02b6e75b.jpg', 1280, 720),
        #     (2, 'b7a8e795-ff80fddf', 'jpg', 'localhost://b7a8e795-ff80fddf.jpg', 1280, 720),
        # ]

        rets = []
        for filename in files:
            filename = cache_save_path + filename
            # print(filename)
            img = Image.open(filename)
            rets.append([
                count, filename.split('\\')[-1].split('.')[0], filename.split('.')[-1],
                'localhost://' + filename.split('\\')[-1], img.width, img.height
            ])
            count += 1
        # print(rets)

        for ret in rets:
            file = File()
            file.fid = ret[0]
            file.name = ret[1]
            file.type = ret[2]
            file.origin = ret[3]
            file.width = ret[4]
            file.height = ret[5]
            db.session.add(file)
        #
        db.session.commit()


class WModel(db.Model):
    __tablename__ = 'model_table'
    wid = db.Column(db.Integer, primary_key=True, autoincrement=True)
    model_name = db.Column(db.String(255), nullable=False)  # 名称
    model_type = db.Column(db.String(255), nullable=False)  # 类型
    model_dataset = db.Column(db.String(255), nullable=False)  # 适用数据集

    @staticmethod
    def init_db():
        rets = [
            (1, 'yolov8l_coco', 'yolov8', 'coco'),
            (2, 'yolov8m_coco', 'yolov8', 'coco'),
            (3, 'yolov8s_coco', 'yolov8', 'coco'),
            (4, 'yolov8n_coco', 'yolov8', 'coco'),
            (5, 'yolov5su_coco', 'yolov5', 'coco'),
            (6, 'yolov5nu_coco', 'yolov5', 'coco')
        ]

        for ret in rets:
            wmodel = WModel()
            wmodel.wid = ret[0]
            wmodel.model_name = ret[1]
            wmodel.model_type = ret[2]
            wmodel.model_dataset = ret[3]
            db.session.add(wmodel)

        db.session.commit()


class Result(db.Model):
    __tablename__ = 'result_table'
    rid = db.Column(db.Integer, primary_key=True, autoincrement=True)
    wid = db.Column(db.Integer, ForeignKey("model_table.wid", ondelete='SET NULL'))  # 首要默认为检测模型
    timestamp = db.Column(db.Integer, nullable=False)  # 时间戳
    type = db.Column(db.String(255), nullable=True)  # 文件类型
    addition = db.Column(db.String(255), nullable=True)  # 额外说明
    mission_type = db.Column(db.String(255), nullable=False)  # 任务类型
    filename = db.Column(db.String(255), nullable=False)  # 被检测的源头文件
    num = db.Column(db.Integer, nullable=False)  # 一共有多少个检测物体


class Object(db.Model):
    __tablename__ = 'obj_table'
    oid = db.Column(db.Integer, primary_key=True, autoincrement=True)
    rid = db.Column(db.Integer, ForeignKey("result_table.rid", ondelete='SET NULL'))  # 和检测完成好的文件相关联, 外键
    cls = db.Column(db.String(255), nullable=False)  # 类别名称
    x1 = db.Column(db.Float, nullable=False)  # 边框的坐标值
    y1 = db.Column(db.Float, nullable=False)
    x2 = db.Column(db.Float, nullable=False)
    y2 = db.Column(db.Float, nullable=False)
    conf = db.Column(db.Float, nullable=False)  # 置信度


================================================
FILE: detection-backend/weights/yolov5su_coco.pt
================================================
[File too large to display: 17.7 MB]

================================================
FILE: detection-backend/weights/yolov8l_coco.pt
================================================
[File too large to display: 83.7 MB]

================================================
FILE: detection-backend/weights/yolov8m_coco.pt
================================================
[File too large to display: 49.7 MB]

================================================
FILE: detection-backend/weights/yolov8s_coco.pt
================================================
[File too large to display: 21.5 MB]

================================================
FILE: detection-fontend/LICENSE
================================================
MIT License

Copyright (c) 2016-2023 vue-manage-system

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: detection-fontend/README.md
================================================
# vue-manage-system

<a href="https://github.com/vuejs/vue">
    <img src="https://img.shields.io/badge/vue-3.1.2-brightgreen.svg" alt="vue">
  </a>
  <a href="https://github.com/vuejs/pinia">
    <img src="https://img.shields.io/badge/pinia-2.0.14-brightgreen.svg" alt="pinia">
  </a>
  <a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
    <img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
  </a>
  <a href="https://github.com/lin-xin/vue-manage-system/releases">
    <img src="https://img.shields.io/github/release/lin-xin/vue-manage-system.svg" alt="GitHub release">
  </a>
  <a href="https://lin-xin.gitee.io/example/work/#/donate">
    <img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
  </a>

基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上地址](https://lin-xin.gitee.io/example/work/)

> Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0)

[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)

## 赞助商

### 好问

[<img src="https://static.bestqa.net/logo/bestqa_haowen.png" width="220" height="100">](https://www.bestqa.net/home/index.html)

专业问卷服务,一对一客服,按需定制 

## 支持作者

请作者喝杯咖啡吧!(微信号:linxin_20)

![微信扫一扫](https://lin-xin.gitee.io/images/weixin.jpg)

## 前言

该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript,引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。

## 功能

-   [x] Element Plus
-   [x] vite 3
-   [x] pinia
-   [x] typescript
-   [x] 登录/注销
-   [x] Dashboard
-   [x] 表格
-   [x] Tab 选项卡
-   [x] 表单
-   [x] 图表 :bar_chart:
-   [x] 富文本/markdown编辑器
-   [x] 图片拖拽/裁剪上传
-   [x] 权限管理
-   [x] 三级菜单
-   [x] 自定义图标


## 安装步骤
> 因为使用vite3,node版本需要 14.18+

```
git clone https://github.com/lin-xin/vue-manage-system.git      // 把模板下载到本地
cd vue-manage-system    // 进入模板目录
npm install         // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn

// 运行
npm run dev

// 执行构建命令,生成的dist文件夹放在服务器下即可访问
npm run build
```

## 组件使用说明与演示

### vue-schart

vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://github.com/lin-xin/vue-schart#/) 

<p><a href="https://www.npmjs.com/package/vue-schart"><img src="https://img.shields.io/npm/dm/vue-schart.svg" alt="Downloads"></a></p>

```html
<template>
    <div>
        <schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Schart from "vue-schart"; // 导入Schart组件
const options = ref({
    type: "bar",
    title: {
        text: "最近一周各品类销售图",
    },
    labels: ["周一", "周二", "周三", "周四", "周五"],
    datasets: [
        {
            label: "家电",
            data: [234, 278, 270, 190, 230],
        },
        {
            label: "百货",
            data: [164, 178, 190, 135, 160],
        },
        {
            label: "食品",
            data: [144, 198, 150, 235, 120],
        },
    ],
})
</script>
<style>
    .wrapper {
        width: 7rem;
        height: 5rem;
    }
</style>
```

## 项目截图

### 登录

![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)

### 首页

![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)

## License

[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)


================================================
FILE: detection-fontend/README_EN.md
================================================
# vue-manage-system

<a href="https://github.com/vuejs/vue">
    <img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
  </a>
  <a href="https://github.com/ElemeFE/element">
    <img src="https://img.shields.io/badge/element--ui-2.8.2-brightgreen.svg" alt="element-ui">
  </a>
  <a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
    <img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
  </a>
  <a href="https://github.com/lin-xin/vue-manage-system/releases">
    <img src="https://img.shields.io/github/release/lin-xin/vue-manage-system.svg" alt="GitHub release">
  </a>
  <a href="https://lin-xin.gitee.io/example/work/#/donate">
    <img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
  </a>

The web management system solution based on Vue3 and ElementPlus。[live demo](https://lin-xin.gitee.io/example/work/)

Please check the version of vue2 in [tag V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0)

## Donation

![WeChat](https://lin-xin.gitee.io/images/weixin.jpg)

## Preface

The scheme as a set of multi-function background frame templates, suitable for most of the WEB management system development. Convenient development fast simple good components based on Vue3 and ElementPlus. Color separation of color style, support manual switch themes, and it is convenient to use a custom theme color.

## Function

-   [x] Element-UI
-   [x] Login/Logout
-   [x] Dashboard
-   [x] Table
-   [x] Tabs
-   [x] From
-   [x] Chart :bar_chart:
-   [x] Editor
-   [x] Markdown
-   [x] Upload pictures by clipping or dragging
-   [x] Permission
-   [x] Three level menu
-   [x] Custom icon

## Installation steps

    git clone https://github.com/lin-xin/vue-manage-system.git		// Clone templates
    cd vue-manage-system											// Enter template directory
    npm install													// Installation dependency

## Local development

    npm run dev

## Constructing production

    // Constructing project
    npm run build

## Component description and presentation

### vue-schart

Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/vue-schart#/)

```html
<template>
    <div>
        <schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
    </div>
</template>
<script setup>
import { ref } from 'vue';
import Schart from "vue-schart"; // 导入Schart组件
const options = ref({
    type: "bar",
    title: {
        text: "最近一周各品类销售图",
    },
    labels: ["周一", "周二", "周三", "周四", "周五"],
    datasets: [
        {
            label: "家电",
            data: [234, 278, 270, 190, 230],
        },
        {
            label: "百货",
            data: [164, 178, 190, 135, 160],
        },
        {
            label: "食品",
            data: [144, 198, 150, 235, 120],
        },
    ],
})
</script>
<style>
    .wrapper {
        width: 7rem;
        height: 5rem;
    }
</style>
```

## Screenshot

### Default theme

![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)

### Login

![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)

## License

[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)


================================================
FILE: detection-fontend/auto-imports.d.ts
================================================
// Generated by 'unplugin-auto-import'
export {}
declare global {

}


================================================
FILE: detection-fontend/components.d.ts
================================================
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'

export {}

declare module '@vue/runtime-core' {
  export interface GlobalComponents {
    ElAvatar: typeof import('element-plus/es')['ElAvatar']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCard: typeof import('element-plus/es')['ElCard']
    ElCascader: typeof import('element-plus/es')['ElCascader']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
    ElCol: typeof import('element-plus/es')['ElCol']
    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
    ElDialog: typeof import('element-plus/es')['ElDialog']
    ElDropdown: typeof import('element-plus/es')['ElDropdown']
    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
    ElForm: typeof import('element-plus/es')['ElForm']
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElImage: typeof import('element-plus/es')['ElImage']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElLink: typeof import('element-plus/es')['ElLink']
    ElMenu: typeof import('element-plus/es')['ElMenu']
    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElPagination: typeof import('element-plus/es')['ElPagination']
    ElProgress: typeof import('element-plus/es')['ElProgress']
    ElRadio: typeof import('element-plus/es')['ElRadio']
    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
    ElRow: typeof import('element-plus/es')['ElRow']
    ElSelect: typeof import('element-plus/es')['ElSelect']
    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
    ElSwitch: typeof import('element-plus/es')['ElSwitch']
    ElTable: typeof import('element-plus/es')['ElTable']
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
    ElTabPane: typeof import('element-plus/es')['ElTabPane']
    ElTabs: typeof import('element-plus/es')['ElTabs']
    ElTag: typeof import('element-plus/es')['ElTag']
    ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
    ElTooltip: typeof import('element-plus/es')['ElTooltip']
    ElUpload: typeof import('element-plus/es')['ElUpload']
    Header: typeof import('./src/components/header.vue')['default']
    RouterLink: typeof import('vue-router')['RouterLink']
    RouterView: typeof import('vue-router')['RouterView']
    Sidebar: typeof import('./src/components/sidebar.vue')['default']
    Tags: typeof import('./src/components/tags.vue')['default']
  }
}


================================================
FILE: detection-fontend/index.html
================================================
<!DOCTYPE html>
<html lang="">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>vue-manage-system</title>
  <link rel="stylesheet" href="https://at.alicdn.com/t/font_830376_qzecyukz0s.css">
</head>

<body>
  <noscript>
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
      Please enable it to continue.</strong>
  </noscript>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
  <!-- built files will be auto injected -->
</body>

</html>

================================================
FILE: detection-fontend/package.json
================================================
{
	"name": "vue-manage-system",
	"version": "5.3.0",
	"private": true,
	"scripts": {
		"dev": "vite",
		"build": "vue-tsc --noEmit && vite build",
		"serve": "vite preview"
	},
	"dependencies": {
		"@element-plus/icons-vue": "^2.0.9",
		"axios": "^0.27.2",
		"element-plus": "^2.2.14",
		"md-editor-v3": "^2.2.1",
		"pinia": "^2.0.20",
		"vue": "^3.2.37",
		"vue-cropperjs": "^5.0.0",
		"vue-router": "^4.1.3",
		"vue-schart": "^2.0.0",
		"wangeditor": "^4.7.15",
		"xlsx": "^0.18.5"
	},
	"devDependencies": {
		"@vitejs/plugin-vue": "^3.0.0",
		"@vue/compiler-sfc": "^3.1.2",
		"typescript": "^4.6.4",
		"unplugin-auto-import": "^0.11.2",
		"unplugin-vue-components": "^0.22.4",
		"vite": "^3.0.0",
		"vite-plugin-vue-setup-extend": "^0.4.0",
		"vue-tsc": "^0.38.4"
	},
	"browserslist": [
		"> 1%",
		"last 2 versions",
		"not dead"
	]
}


================================================
FILE: detection-fontend/public/table.json
================================================
{
    "list": [{
            "id": 1,
            "name": "张三",
            "money": 123,
            "address": "广东省东莞市长安镇",
            "state": "成功",
            "date": "2019-11-1",
            "thumb": "https://lin-xin.gitee.io/images/post/wms.png"
        },
        {
            "id": 2,
            "name": "李四",
            "money": 456,
            "address": "广东省广州市白云区",
            "state": "成功",
            "date": "2019-10-11",
            "thumb": "https://lin-xin.gitee.io/images/post/node3.png"
        },
        {
            "id": 3,
            "name": "王五",
            "money": 789,
            "address": "湖南省长沙市",
            "state": "失败",
            "date": "2019-11-11",
            "thumb": "https://lin-xin.gitee.io/images/post/parcel.png"
        },
        {
            "id": 4,
            "name": "赵六",
            "money": 1011,
            "address": "福建省厦门市鼓浪屿",
            "state": "成功",
            "date": "2019-10-20",
            "thumb": "https://lin-xin.gitee.io/images/post/notice.png"
        }
    ],
    "pageTotal": 4
}

================================================
FILE: detection-fontend/src/App.vue
================================================
<template>
    <el-config-provider :locale="zhCn">
        <router-view />
    </el-config-provider>
</template>

<script setup lang="ts">
import { ElConfigProvider } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
</script>
<style>
@import './assets/css/main.css';
@import './assets/css/color-dark.css';
</style>


================================================
FILE: detection-fontend/src/api/index.ts
================================================
import request from '../utils/request';

export const fetchData = () => {
    return request({
        url: './table.json',
        method: 'get'
    });
};


================================================
FILE: detection-fontend/src/assets/css/color-dark.css
================================================
.header{
    background-color: #242f42;
}
.login-wrap{
    background: #324157;
}
.plugins-tips{
    background: #eef1f6;
}
.plugins-tips a{
    color: #20a0ff;
}

.tags-li.active {
    border: 1px solid #409EFF;
    background-color: #409EFF;
}
.message-title{
    color: #20a0ff;
}
.collapse-btn:hover{
    background: rgb(40,52,70);
}

================================================
FILE: detection-fontend/src/assets/css/icon.css
================================================
[class*=" el-icon-lx"],
[class^=el-icon-lx] {
    font-family: lx-iconfont !important;
}

================================================
FILE: detection-fontend/src/assets/css/main.css
================================================
* {
    margin: 0;
    padding: 0;
}

html,
body,
#app,
.wrapper {
    width: 100%;
    height: 100%;
    overflow: hidden;
}

body {
    font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
}

a {
    text-decoration: none
}


.content-box {
    position: absolute;
    left: 250px;
    right: 0;
    top: 70px;
    bottom: 0;
    padding-bottom: 30px;
    -webkit-transition: left .3s ease-in-out;
    transition: left .3s ease-in-out;
    background: #f0f0f0;
}

.content {
    width: auto;
    height: 100%;
    padding: 10px;
    overflow-y: scroll;
    box-sizing: border-box;
}

.content-collapse {
    left: 65px;
}

.container {
    padding: 30px;
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 5px;
}

.crumbs {
    margin: 10px 0;
}

.el-table th {
    background-color: #f5f7fa !important;
}

.pagination {
    margin: 20px 0;
    text-align: right;
}

.plugins-tips {
    padding: 20px 10px;
    margin-bottom: 20px;
}

.el-button+.el-tooltip {
    margin-left: 10px;
}

.el-table tr:hover {
    background: #f6faff;
}

.mgb20 {
    margin-bottom: 20px;
}

.move-enter-active,
.move-leave-active {
    transition: opacity .1s ease;
}

.move-enter-from,
.move-leave-to {
    opacity: 0;
}

/*BaseForm*/

.form-box {
    width: 600px;
}

.form-box .line {
    text-align: center;
}

.el-time-panel__content::after,
.el-time-panel__content::before {
    margin-top: -7px;
}

.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
    padding-bottom: 0;
}


[class*=" el-icon-"], [class^=el-icon-] {
    speak: none;
    font-style: normal;
    font-weight: 400;
    font-variant: normal;
    text-transform: none;
    line-height: 1;
    vertical-align: baseline;
    display: inline-block;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
.el-sub-menu [class^=el-icon-] {
    vertical-align: middle;
    margin-right: 5px;
    width: 24px;
    text-align: center;
    font-size: 18px;
}

[hidden]{
    display: none !important;
}

================================================
FILE: detection-fontend/src/components/header.vue
================================================
<template>
	<div class="header">
		<!-- 折叠按钮 -->
		<div class="collapse-btn" @click="collapseChage">
			<el-icon v-if="sidebar.collapse"><Expand /></el-icon>
			<el-icon v-else><Fold /></el-icon>
		</div>
		<div class="logo">Yolo通用实现平台</div>
		<div class="header-right">
			<div class="header-user-con">
				<!-- 消息中心 -->
<!--				<div class="btn-bell" @click="router.push('/tabs')">-->
<!--					<el-tooltip-->
<!--						effect="dark"-->
<!--						:content="message ? `有${message}条未读消息` : `消息中心`"-->
<!--						placement="bottom"-->
<!--					>-->
<!--						<i class="el-icon-lx-notice"></i>-->
<!--					</el-tooltip>-->
<!--					<span class="btn-bell-badge" v-if="message"></span>-->
<!--				</div>-->
				<!-- 用户头像 -->
				<el-avatar class="user-avator" :size="30" :src="imgurl" />
				<!-- 用户名下拉菜单 -->
				<el-dropdown class="user-name" trigger="click" @command="handleCommand">
					<span class="el-dropdown-link">
						{{ username }}
						<el-icon class="el-icon--right">
							<arrow-down />
						</el-icon>
					</span>
					<template #dropdown>
						<el-dropdown-menu>
							<a href="https://github.com/datar5/yolov8-flask-vue-deploy" target="_blank">
								<el-dropdown-item>项目仓库</el-dropdown-item>
							</a>
							<el-dropdown-item command="user">个人中心</el-dropdown-item>
							<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
						</el-dropdown-menu>
					</template>
				</el-dropdown>
			</div>
		</div>
	</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { useSidebarStore } from '../store/sidebar';
import { useRouter } from 'vue-router';
// import imgurl from '../assets/img/img.jpg';
import imgurl from '../assets/img/img.png';

const username: string | null = localStorage.getItem('ms_username');
const message: number = 2;

const sidebar = useSidebarStore();
// 侧边栏折叠
const collapseChage = () => {
	sidebar.handleCollapse();
};

onMounted(() => {
	if (document.body.clientWidth < 1500) {
		collapseChage();
	}
});

// 用户名下拉菜单选择事件
const router = useRouter();
const handleCommand = (command: string) => {
	if (command == 'loginout') {
		localStorage.removeItem('ms_username');
		router.push('/login');
	} else if (command == 'user') {
		router.push('/user');
	}
};
</script>
<style scoped>
.header {
	position: relative;
	box-sizing: border-box;
	width: 100%;
	height: 70px;
	font-size: 22px;
	color: #fff;
}
.collapse-btn {
	display: flex;
	justify-content: center;
	align-items: center;
	height: 100%;
	float: left;
	padding: 0 21px;
	cursor: pointer;
}
.header .logo {
	float: left;
	width: 250px;
	line-height: 70px;
}
.header-right {
	float: right;
	padding-right: 50px;
}
.header-user-con {
	display: flex;
	height: 70px;
	align-items: center;
}
.btn-fullscreen {
	transform: rotate(45deg);
	margin-right: 5px;
	font-size: 24px;
}
.btn-bell,
.btn-fullscreen {
	position: relative;
	width: 30px;
	height: 30px;
	text-align: center;
	border-radius: 15px;
	cursor: pointer;
	display: flex;
	align-items: center;
}
.btn-bell-badge {
	position: absolute;
	right: 4px;
	top: 0px;
	width: 8px;
	height: 8px;
	border-radius: 4px;
	background: #f56c6c;
	color: #fff;
}
.btn-bell .el-icon-lx-notice {
	color: #fff;
}
.user-name {
	margin-left: 10px;
}
.user-avator {
	margin-left: 20px;
}
.el-dropdown-link {
	color: #fff;
	cursor: pointer;
	display: flex;
	align-items: center;
}
.el-dropdown-menu__item {
	text-align: center;
}
</style>


================================================
FILE: detection-fontend/src/components/sidebar.vue
================================================
<template>
    <div class="sidebar">
        <el-menu
            class="sidebar-el-menu"
            :default-active="onRoutes"
            :collapse="sidebar.collapse"
            background-color="#324157"
            text-color="#bfcbd9"
            active-text-color="#20a0ff"
            unique-opened
            router
        >
            <template v-for="item in items">
                <template v-if="item.subs">
                    <el-sub-menu :index="item.index" :key="item.index" v-permiss="item.permiss">
                        <template #title>
                            <el-icon>
                                <component :is="item.icon"></component>
                            </el-icon>
                            <span>{{ item.title }}</span>
                        </template>
                        <template v-for="subItem in item.subs">
                            <el-sub-menu
                                v-if="subItem.subs"
                                :index="subItem.index"
                                :key="subItem.index"
                                v-permiss="item.permiss"
                            >
                                <template #title>{{ subItem.title }}</template>
                                <el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
                                    {{ threeItem.title }}
                                </el-menu-item>
                            </el-sub-menu>
                            <el-menu-item v-else :index="subItem.index" v-permiss="item.permiss">
                                {{ subItem.title }}
                            </el-menu-item>
                        </template>
                    </el-sub-menu>
                </template>
                <template v-else>
                    <el-menu-item :index="item.index" :key="item.index" v-permiss="item.permiss">
                        <el-icon>
                            <component :is="item.icon"></component>
                        </el-icon>
                        <template #title>{{ item.title }}</template>
                    </el-menu-item>
                </template>
            </template>
        </el-menu>
    </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { useSidebarStore } from '../store/sidebar';
import { useRoute } from 'vue-router';

const items = [
    // {
    //     icon: 'Odometer',
    //     index: '/dashboard',
    //     title: '系统首页',
    //     permiss: '1',
    // },
    // {
    //     icon: 'Calendar',
    //     index: '1',
    //     title: '表格相关',
    //     permiss: '2',
    //     subs: [
    //         {
    //             index: '/table',
    //             title: '常用表格',
    //             permiss: '2',
    //         },
    //         {
    //             index: '/import',
    //             title: '导入Excel',
    //             permiss: '2',
    //         },
    //         {
    //             index: '/export',
    //             title: '导出Excel',
    //             permiss: '2',
    //         },
    //     ],
    // },
    // {
    //     icon: 'DocumentCopy',
    //     index: '/tabs',
    //     title: 'tab选项卡',
    //     permiss: '3',
    // },
    // {
    //     icon: 'Edit',
    //     index: '3',
    //     title: '表单相关',
    //     permiss: '4',
    //     subs: [
    //         {
    //             index: '/form',
    //             title: '基本表单',
    //             permiss: '5',
    //         },
    //         {
    //             index: '/upload',
    //             title: '文件上传',
    //             permiss: '6',
    //         },
    //         {
    //             index: '4',
    //             title: '三级菜单',
    //             permiss: '7',
    //             subs: [
    //                 {
    //                     index: '/editor',
    //                     title: '富文本编辑器',
    //                     permiss: '8',
    //                 },
    //                 {
    //                     index: '/markdown',
    //                     title: 'markdown编辑器',
    //                     permiss: '9',
    //                 },
    //             ],
    //         },
    //     ],
    // },
    // {
    //     icon: 'Setting',
    //     index: '/icon',
    //     title: '自定义图标',
    //     permiss: '10',
    // },
    // {
    //     icon: 'PieChart',
    //     index: '/charts',
    //     title: 'schart图表',
    //     permiss: '11',
    // },
    // {
    //     icon: 'Warning',
    //     index: '/permission',
    //     title: '权限管理',
    //     permiss: '13',
    // },
    // {
    //     icon: 'CoffeeCup',
    //     index: '/donate',
    //     title: '支持作者',
    //     permiss: '14',
    // },
    {
      icon: 'UploadFilled',
      index: '/uploadFile',
      title: '图片管理',
      permiss: '15'
    },
    {
      icon: 'Search',
      index: '/detection',
      title: '目标检测',
      permiss: '16'
    },
    {
      icon: 'Notebook',
      index: '/resultManager',
      title: '记录管理',
      permiss: '1'
    }
];

const route = useRoute();
const onRoutes = computed(() => {
    return route.path;
});

const sidebar = useSidebarStore();
</script>

<style scoped>
.sidebar {
    display: block;
    position: absolute;
    left: 0;
    top: 70px;
    bottom: 0;
    overflow-y: scroll;
}
.sidebar::-webkit-scrollbar {
    width: 0;
}
.sidebar-el-menu:not(.el-menu--collapse) {
    width: 250px;
}
.sidebar > ul {
    height: 100%;
}
</style>


================================================
FILE: detection-fontend/src/components/tags.vue
================================================
<template>
	<div class="tags" v-if="tags.show">
		<ul>
			<li
				class="tags-li"
				v-for="(item, index) in tags.list"
				:class="{ active: isActive(item.path) }"
				:key="index"
			>
				<router-link :to="item.path" class="tags-li-title">{{ item.title }}</router-link>
				<el-icon @click="closeTags(index)"><Close /></el-icon>
			</li>
		</ul>
		<div class="tags-close-box">
			<el-dropdown @command="handleTags">
				<el-button size="small" type="primary">
					标签选项
					<el-icon class="el-icon--right">
						<arrow-down />
					</el-icon>
				</el-button>
				<template #dropdown>
					<el-dropdown-menu size="small">
						<el-dropdown-item command="other">关闭其他</el-dropdown-item>
						<el-dropdown-item command="all">关闭所有</el-dropdown-item>
					</el-dropdown-menu>
				</template>
			</el-dropdown>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useTagsStore } from '../store/tags';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';

const route = useRoute();
const router = useRouter();
const isActive = (path: string) => {
	return path === route.fullPath;
};

const tags = useTagsStore();
// 关闭单个标签
const closeTags = (index: number) => {
	const delItem = tags.list[index];
	tags.delTagsItem(index);
	const item = tags.list[index] ? tags.list[index] : tags.list[index - 1];
	if (item) {
		delItem.path === route.fullPath && router.push(item.path);
	} else {
		router.push('/');
	}
};

// 设置标签
const setTags = (route: any) => {
	const isExist = tags.list.some(item => {
		return item.path === route.fullPath;
	});
	if (!isExist) {
		if (tags.list.length >= 8) tags.delTagsItem(0);
		tags.setTagsItem({
			name: route.name,
			title: route.meta.title,
			path: route.fullPath
		});
	}
};
setTags(route);
onBeforeRouteUpdate(to => {
	setTags(to);
});

// 关闭全部标签
const closeAll = () => {
	tags.clearTags();
	router.push('/');
};
// 关闭其他标签
const closeOther = () => {
	const curItem = tags.list.filter(item => {
		return item.path === route.fullPath;
	});
	tags.closeTagsOther(curItem);
};
const handleTags = (command: string) => {
	command === 'other' ? closeOther() : closeAll();
};

// 关闭当前页面的标签页
// tags.closeCurrentTag({
//     $router: router,
//     $route: route
// });
</script>

<style>
.tags {
	position: relative;
	height: 30px;
	overflow: hidden;
	background: #fff;
	padding-right: 120px;
	box-shadow: 0 5px 10px #ddd;
}

.tags ul {
	box-sizing: border-box;
	width: 100%;
	height: 100%;
}

.tags-li {
	display: flex;
	align-items: center;
	float: left;
	margin: 3px 5px 2px 3px;
	border-radius: 3px;
	font-size: 12px;
	overflow: hidden;
	cursor: pointer;
	height: 23px;
	border: 1px solid #e9eaec;
	background: #fff;
	padding: 0 5px 0 12px;
	color: #666;
	-webkit-transition: all 0.3s ease-in;
	-moz-transition: all 0.3s ease-in;
	transition: all 0.3s ease-in;
}

.tags-li:not(.active):hover {
	background: #f8f8f8;
}

.tags-li.active {
	color: #fff;
}

.tags-li-title {
	float: left;
	max-width: 80px;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	margin-right: 5px;
	color: #666;
}

.tags-li.active .tags-li-title {
	color: #fff;
}

.tags-close-box {
	position: absolute;
	right: 0;
	top: 0;
	box-sizing: border-box;
	padding-top: 1px;
	text-align: center;
	width: 110px;
	height: 30px;
	background: #fff;
	box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
	z-index: 10;
}
</style>


================================================
FILE: detection-fontend/src/main.ts
================================================
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import App from './App.vue';
import router from './router';
import { usePermissStore } from './store/permiss';
import 'element-plus/dist/index.css';
import './assets/css/icon.css';

import axios from "axios";

axios.defaults.baseURL = "http://localhost:5000"
axios.defaults.headers["Content-Type"] =  "application/json;charset=UTF-8"


const app = createApp(App);
app.use(createPinia());
app.use(router);

// 注册elementplus图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component);
}
// 自定义权限指令
const permiss = usePermissStore();
app.directive('permiss', {
    mounted(el, binding) {
        if (!permiss.key.includes(String(binding.value))) {
            el['hidden'] = true;
        }
    },
});

app.mount('#app');


================================================
FILE: detection-fontend/src/router/index.ts
================================================
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { usePermissStore } from '../store/permiss';
import Home from '../views/home.vue';

const routes: RouteRecordRaw[] = [
    {
        path: '/',
        redirect: '/detection',
    },
    {
        path: '/',
        name: 'Home',
        component: Home,
        children: [
            // {
            //     path: '/dashboard',
            //     name: 'dashboard',
            //     meta: {
            //         title: '系统首页',
            //         permiss: '1',
            //     },
            //     component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
            // },
            {
                path: '/table',
                name: 'basetable',
                meta: {
                    title: '表格',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "table" */ '../views/table.vue'),
            },
            {
                path: '/charts',
                name: 'basecharts',
                meta: {
                    title: '图表',
                    permiss: '11',
                },
                component: () => import(/* webpackChunkName: "charts" */ '../views/charts.vue'),
            },
            {
                path: '/form',
                name: 'baseform',
                meta: {
                    title: '表单',
                    permiss: '5',
                },
                component: () => import(/* webpackChunkName: "form" */ '../views/form.vue'),
            },
            {
                path: '/tabs',
                name: 'tabs',
                meta: {
                    title: 'tab标签',
                    permiss: '3',
                },
                component: () => import(/* webpackChunkName: "tabs" */ '../views/tabs.vue'),
            },
            {
                path: '/donate',
                name: 'donate',
                meta: {
                    title: '鼓励作者',
                    permiss: '14',
                },
                component: () => import(/* webpackChunkName: "donate" */ '../views/donate.vue'),
            },
            {
                path: '/permission',
                name: 'permission',
                meta: {
                    title: '权限管理',
                    permiss: '13',
                },
                component: () => import(/* webpackChunkName: "permission" */ '../views/permission.vue'),
            },
            {
                path: '/upload',
                name: 'upload',
                meta: {
                    title: '上传插件',
                    permiss: '6',
                },
                component: () => import(/* webpackChunkName: "upload" */ '../views/upload.vue'),
            },
            {
                path: '/icon',
                name: 'icon',
                meta: {
                    title: '自定义图标',
                    permiss: '10',
                },
                component: () => import(/* webpackChunkName: "icon" */ '../views/icon.vue'),
            },
            {
                path: '/user',
                name: 'user',
                meta: {
                    title: '个人中心',
                },
                component: () => import(/* webpackChunkName: "user" */ '../views/user.vue'),
            },
            {
                path: '/editor',
                name: 'editor',
                meta: {
                    title: '富文本编辑器',
                    permiss: '8',
                },
                component: () => import(/* webpackChunkName: "editor" */ '../views/editor.vue'),
            },
            {
                path: '/markdown',
                name: 'markdown',
                meta: {
                    title: 'markdown编辑器',
                    permiss: '9',
                },
                component: () => import(/* webpackChunkName: "markdown" */ '../views/markdown.vue'),
            },
            {
                path: '/export',
                name: 'export',
                meta: {
                    title: '导出Excel',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "export" */ '../views/export.vue'),
            },
            {
                path: '/import',
                name: 'import',
                meta: {
                    title: '导入Excel',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "import" */ '../views/import.vue'),
            },
            {
                path: '/uploadFile',
                name: 'uploadFile',
                meta: {
                    title: '上传文件',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "import" */ '../viewDetect/uploadFile.vue'),
            },
            {
                path: '/detection',
                name: 'detection',
                meta: {
                    title: '目标检测',
                    permiss: '1',
                },
                component: () => import(/* webpackChunkName: "import" */ '../viewDetect/detectVue.vue'),
            },
            {
                path: '/resultManager',
                name: 'resultManager',
                meta: {
                    title: '记录管理',
                    permiss: '3',
                },
                component: () => import(/* webpackChunkName: "import" */ '../viewDetect/ResultVue.vue'),
            },
        ],
    },
    {
        path: '/login',
        name: 'Login',
        meta: {
            title: '登录',
        },
        component: () => import(/* webpackChunkName: "login" */ '../views/login.vue'),
    },
    {
        path: '/403',
        name: '403',
        meta: {
            title: '没有权限',
        },
        component: () => import(/* webpackChunkName: "403" */ '../views/403.vue'),
    },
];

const router = createRouter({
    history: createWebHashHistory(),
    routes,
});

router.beforeEach((to, from, next) => {
    document.title = `${to.meta.title} | vue-manage-system`;
    const role = localStorage.getItem('ms_username');
    const permiss = usePermissStore();
    if (!role && to.path !== '/login') {
        next('/login');
    } else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
        // 如果没有权限,则进入403
        next('/403');
    } else {
        next();
    }
});

export default router;


================================================
FILE: detection-fontend/src/store/permiss.ts
================================================
import { defineStore } from 'pinia';

interface ObjectList {
	[key: string]: string[];
}

export const usePermissStore = defineStore('permiss', {
	state: () => {
		const keys = localStorage.getItem('ms_keys');
		return {
			key: keys ? JSON.parse(keys) : <string[]>[],
			defaultList: <ObjectList>{
				admin: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'],
				user: ['1', '2', '3', '11', '13', '14', '15']
			}
		};
	},
	actions: {
		handleSet(val: string[]) {
			this.key = val;
		}
	}
});


================================================
FILE: detection-fontend/src/store/sidebar.ts
================================================
import { defineStore } from 'pinia';

export const useSidebarStore = defineStore('sidebar', {
	state: () => {
		return {
			collapse: false
		};
	},
	getters: {},
	actions: {
		handleCollapse() {
			this.collapse = !this.collapse;
		}
	}
});


================================================
FILE: detection-fontend/src/store/tags.ts
================================================
import { defineStore } from 'pinia';

interface ListItem {
	name: string;
	path: string;
	title: string;
}

export const useTagsStore = defineStore('tags', {
	state: () => {
		return {
			list: <ListItem[]>[]
		};
	},
	getters: {
		show: state => {
			return state.list.length > 0;
		},
		nameList: state => {
			return state.list.map(item => item.name);
		}
	},
	actions: {
		delTagsItem(index: number) {
			this.list.splice(index, 1);
		},
		setTagsItem(data: ListItem) {
			this.list.push(data);
		},
		clearTags() {
			this.list = [];
		},
		closeTagsOther(data: ListItem[]) {
			this.list = data;
		},
		closeCurrentTag(data: any) {
			for (let i = 0, len = this.list.length; i < len; i++) {
				const item = this.list[i];
				if (item.path === data.$route.fullPath) {
					if (i < len - 1) {
						data.$router.push(this.list[i + 1].path);
					} else if (i > 0) {
						data.$router.push(this.list[i - 1].path);
					} else {
						data.$router.push('/');
					}
					this.list.splice(i, 1);
					break;
				}
			}
		}
	}
});


================================================
FILE: detection-fontend/src/utils/request.ts
================================================
import axios, {AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig} from 'axios';

const service:AxiosInstance = axios.create({
    timeout: 5000
});

service.interceptors.request.use(
    (config: AxiosRequestConfig) => {
        return config;
    },
    (error: AxiosError) => {
        console.log(error);
        return Promise.reject();
    }
);

service.interceptors.response.use(
    (response: AxiosResponse) => {
        if (response.status === 200) {
            return response;
        } else {
            Promise.reject();
        }
    },
    (error: AxiosError) => {
        console.log(error);
        return Promise.reject();
    }
);

export default service;


================================================
FILE: detection-fontend/src/viewDetect/ResultVue.vue
================================================
<template>
  <div class="container">
    <div>
      <div class="content-title">模型信息</div>
      <div class="handle-box">
        <el-input v-model="model_query.keyword" placeholder="模型名称" class="handle-input mr10"></el-input>
        <el-button type="primary" :icon="Search " @click="handleModelSearch">搜索</el-button>

        <el-button type="success"  @click="handleModelAll">
          <el-icon><List /></el-icon>
          <span>列出模型</span>
        </el-button>
      </div>


      <el-table :data="modelData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
        <el-table-column prop="wid" label="模型ID" width="55" align="center"></el-table-column>
        <el-table-column prop="model_name" label="文件名"></el-table-column>


        <el-table-column prop="model_type" label="模型类别"></el-table-column>
        <el-table-column prop="model_dataset" label="对应数据集"></el-table-column>


<!--        <el-table-column label="操作" width="300" align="center">-->
<!--          <template #default="scope">-->
<!--            <el-button text :icon="Search" class="green" @click="handleModelDetail(scope.$index, scope.row)" v-permiss="15">-->
<!--              查看-->
<!--            </el-button>-->
<!--          </template>-->
<!--        </el-table-column>-->
      </el-table>

      <div class="pagination">
        <el-pagination
            background
            layout="total, sizes, prev, pager, next, jumper"
            v-model:current-page="model_query.curPage"
            v-model:page-size="model_query.pageSize"

            :page-sizes="[5, 10]"
            :total="modelTotal"
            @current-change="handleModelPageChange"
            @size-change="handleModelSizeChange"

        ></el-pagination>
      </div>
    </div>


    <div class="content-title">图片查看</div>
    <div class="handle-box">
      <el-input v-model="query.keyword" placeholder="文件名称" class="handle-input mr10"></el-input>
      <el-button type="primary" :icon="Search " @click="handleSearch">搜索</el-button>

<!--      <el-button type="primary"  @click="getData">-->
<!--        <el-icon><Refresh /></el-icon>-->
<!--        <span>刷新页面</span>-->
<!--      </el-button>-->

      <el-button type="success"  @click="handleAll">
        <el-icon><List /></el-icon>
        <span>列出全部</span>
      </el-button>
    </div>

    <el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
      <el-table-column prop="fid" label="文件ID" width="55" align="center"></el-table-column>
      <el-table-column prop="name" label="文件名"></el-table-column>

      <el-table-column label="图片(查看大图)" align="center">
        <template #default="scope">
          <el-image
              class="table-td-thumb"
              :src="scope.row.img_url"
              :z-index="10"
              :preview-src-list="[scope.row.img_url]"
              preview-teleported
          >
          </el-image>
        </template>
      </el-table-column>

      <el-table-column label="时间">
        <template #default="scope">{{ scope.row.timestamp }}</template>
      </el-table-column>

      <el-table-column prop="type" label="文件类别"></el-table-column>
      <el-table-column prop="origin" label="文件来源"></el-table-column>
      <el-table-column prop="width" label="宽度"></el-table-column>
      <el-table-column prop="height" label="高度"></el-table-column>

    </el-table>

    <div class="pagination">
      <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          v-model:current-page="query.curPage"
          v-model:page-size="query.pageSize"

          :page-sizes="[5, 10]"
          :total="pageTotal"
          @current-change="handlePageChange"
          @size-change="handleSizeChange"

      ></el-pagination>
    </div>

    <div class="content-title">记录管理</div>

    <div class="handle-box">
      <el-input v-model="result_query.keyword" placeholder="结果名称" class="handle-input mr10"></el-input>
      <el-button type="primary" :icon="Search " @click="handleResultSearch">搜索</el-button>

      <el-button type="success"  @click="handleResultAll">
        <el-icon><List /></el-icon>
        <span>列出结果</span>
      </el-button>
    </div>

    <el-table :data="resultData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
      <el-table-column prop="rid" label="结果ID" width="55" align="center"></el-table-column>
      <el-table-column prop="wid" label="模型ID"></el-table-column>

      <el-table-column label="图片(查看大图)" align="center">
        <template #default="scope">
          <el-image
              class="table-td-thumb"
              :src="scope.row.img_url"
              :z-index="10"
              :preview-src-list="[scope.row.img_url]"
              preview-teleported
          >
          </el-image>
        </template>
      </el-table-column>

      <el-table-column label="时间">
        <template #default="scope">{{ dateForm(scope.row.timestamp) }}</template>
      </el-table-column>

      <el-table-column prop="type" label="文件类别"></el-table-column>
      <el-table-column prop="addition" label="额外说明"></el-table-column>
      <el-table-column prop="mission_type" label="任务类型"></el-table-column>
      <el-table-column prop="filename" label="文件来源"></el-table-column>
      <el-table-column prop="num" label="检测数目"></el-table-column>

      <el-table-column label="操作" width="300" align="center">
        <template #default="scope">
          <el-button text :icon="TopLeft" class="primary" @click="handleResultDetail(scope.row)" v-permiss="15">
            查看
          </el-button>
          <el-button text :icon="Edit" @click="handleResultEdit(scope.row)" v-permiss="15">
            编辑
          </el-button>
          <el-button text :icon="Delete" class="red" @click="handleResultDelete(scope.row)" v-permiss="16">
            删除
          </el-button>
        </template>
      </el-table-column>

    </el-table>

    <div class="pagination">
      <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          v-model:current-page="result_query.curPage"
          v-model:page-size="result_query.pageSize"

          :page-sizes="[5, 10]"
          :total="resultTotal"
          @current-change="handleResultPageChange"
          @size-change="handleResultSizeChange"

      ></el-pagination>
    </div>

    <el-dialog title="结果编辑" v-model="resultEditVisible" width="30%">
      <el-form label-width="100px">
        <el-form-item label="结果ID">
          <span>{{resultEditForm.rid}}</span>
        </el-form-item>

        <el-form-item label="模型ID">
          <span>{{resultEditForm.wid}}</span>
        </el-form-item>

        <el-form-item label="时间">
          <span>{{ dateForm(resultEditForm.timestamp) }}</span>
        </el-form-item>

        <el-form-item label="文件类别">
          <span>{{resultEditForm.type}}</span>
        </el-form-item>


        <el-form-item label="额外说明">
          <el-input v-model="resultEditForm.addition"></el-input>
        </el-form-item>

        <el-form-item label="任务类别">
          <el-input v-model="resultEditForm.mission_type"></el-input>
        </el-form-item>

        <el-form-item label="检测数目">
          <span>{{ resultEditForm.num }}</span>
        </el-form-item>

      </el-form>
      <template #footer>
          <span class="dialog-footer">
            <el-button @click="submitEdit" type="primary">提交</el-button>
            <el-button @click="resultEditVisible = false" >退出</el-button>
          </span>
      </template>
    </el-dialog>

    <el-dialog title="结果细节" v-model="objDetail" width="70%" class="content-title">

      <div class="content-title">原图显示</div>
      <div class="demo-image__lazy">
        <el-image v-for="url in [obj_query.img_url]"  :key="url" :src="url" lazy />
      </div>

      <div class="content-title">任务信息</div>
      <el-descriptions :column="3" border>
        <el-descriptions-item
            label="时间"
            label-align="right"
            align="center"
            label-class-name="my-label"
            class-name="my-content"
            width="200px">
          {{dateForm(resultParams.timestamp)}}
        </el-descriptions-item>

        <el-descriptions-item label="文件类别" label-align="right" align="center" width="100px">
          {{resultParams.type}}
        </el-descriptions-item>
        <el-descriptions-item label="额外说明" label-align="right" align="center">
          {{resultParams.addition}}
        </el-descriptions-item>

        <el-descriptions-item label="任务类型" label-align="right" align="center">
          <el-tag size="large" type="success">
          {{resultParams.mission_type}}
          </el-tag>
        </el-descriptions-item>

        <el-descriptions-item label="文件来源" label-align="right" align="center">
          {{resultParams.filename}}
        </el-descriptions-item>
      </el-descriptions>

      <div class="content-title">检测模型</div>

      <el-descriptions :column="3" border>
        <el-descriptions-item
            label="模型ID"
            label-align="right"
            align="center"
            label-class-name="my-label"
            class-name="my-content"
            width="150px">
          {{model_params.wid}}
        </el-descriptions-item>

        <el-descriptions-item label="文件名" label-align="right" align="center">
          {{model_params.model_name}}
        </el-descriptions-item>
        <el-descriptions-item label="模型类别" label-align="right" align="center">
          <el-tag size="middle" type="primary">{{model_params.model_type}}</el-tag>
        </el-descriptions-item>

        <el-descriptions-item label="对应数据集" label-align="right" align="center">
          {{model_params.model_dataset}}
        </el-descriptions-item>
      </el-descriptions>

      <div class="content-title">物体细节</div>

      <el-table :data="objData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
        <el-table-column prop="oid" label="物体ID"></el-table-column>
        <el-table-column prop="rid" label="结果ID"></el-table-column>
        <el-table-column prop="cls" label="类别"></el-table-column>
        <el-table-column prop="conf" label="置信度"></el-table-column>
        <el-table-column prop="x1" label="左上角横坐标"></el-table-column>
        <el-table-column prop="y1" label="左上角纵坐标"></el-table-column>
        <el-table-column prop="x2" label="右下角横坐标"></el-table-column>
        <el-table-column prop="y2" label="右下角纵坐标"></el-table-column>
      </el-table>

      <div class="pagination">
        <el-pagination
            background
            layout="total, sizes, prev, pager, next, jumper"
            v-model:current-page="obj_query.curPage"
            v-model:page-size="obj_query.pageSize"

            :page-sizes="[5, 10, 20]"
            :total="objTotal"
            @current-change="handleObjPageChange"
            @size-change="handleObjSizeChange"

        ></el-pagination>
      </div>

      <template #footer>
          <span class="dialog-footer">
            <el-button @click="objDetail = false" >退出</el-button>
          </span>
      </template>
    </el-dialog>

  </div>

</template>

<script lang="ts" setup>
import {ref, reactive, onMounted} from 'vue';
import {ElMessage, ElMessageBox, FormRules} from 'element-plus';
import {Delete, Edit, Search, Plus, TopLeft} from '@element-plus/icons-vue';
import axios from "axios";
import {compileScript} from "@vue/compiler-sfc";
// import { fetchData } from '../api/index';

interface TableItem { // 定义图片的泛型

  // id: number;
  // name: string;
  // money: string;
  // state: string;
  // date: string;
  // address: string;
  fid: number;
  height: number;
  width: number;
  origin: string;
  timestamp:string;
  type: string;
  name: string;
  img_url: string;
}

interface ModelItem{ // 定义model的接口
  wid: number,
  model_name: string,
  model_type: string,
  model_dataset: string
}

interface ResultItem{ // 定义model的接口
  rid: number,
  wid: number,
  timestamp: number
  type: string,
  addtion: string,
  mission_type: string,
  filename: string,
  num: number
}

interface ObjItem{ // 定义model的接口
  oid: number,
  rid: number,
  cls: string,
  x1: number,
  y1: number,
  x2: number,
  y2: number,
  conf: number
}

// 下面定义的是file表相关的数据
const query = reactive({
  curPage: 1,
  pageSize: 5,
  tableName: "file",
  keyword: ""
});

const tableData = ref<TableItem[]>([]);  // 表格数据

const pageTotal = ref(0);

// 下面定义的是model表相关的数据
const model_query = reactive({
  curPage: 1,
  pageSize: 5,
  tableName: "model",
  keyword: ""
})

const modelData = ref<ModelItem[]>([])
const modelTotal = ref(0)

// 下面定义的是result表的相关的数据
const result_query = reactive({
  curPage: 1,
  pageSize: 5,
  tableName: "result",
  keyword: ""
})

const resultData = ref<ResultItem[]>([])
const resultTotal = ref(0)


// 获取file表格数据
const getData = () => {
  // console.log(query)

  if(query.keyword.length == 0){
    axios({
      method: 'GET',
      url: '/page',
      params: {
        curPage: query.curPage,
        pageSize: query.pageSize,
        tableName: "file"
      },
    }).then(res=>{
      tableData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/num',
      params: {
        tableName: "file",
        rid: -2
      },
    }).then(res=>{
      pageTotal.value= res.data.results
    })
  }
  else{
    axios({
      method: 'GET',
      url: '/deblurS',
      params: {
        curPage: query.curPage,
        pageSize: query.pageSize,
        keyword: query.keyword,
        tableName: "file"
      },
    }).then(res=>{
      // console.log(res)
      tableData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/deblurSNum',
      params: {
        keyword: query.keyword,
        tableName: "file"
      },
    }).then(res=>{
      pageTotal.value= res.data.results
    })
  }
};

// 获得model表格的数据
const getModelData = () => {
  if(model_query.keyword.length == 0){
    axios({
      method: 'GET',
      url: '/page',
      params: {
        curPage: model_query.curPage,
        pageSize: model_query.pageSize,
        tableName: "model"
      },
    }).then(res=>{
      modelData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/num',
      params: {
        tableName: "model",
        rid: -2
      },
    }).then(res=>{
      modelTotal.value= res.data.results
    })
  }
  else{
    axios({
      method: 'GET',
      url: '/deblurS',
      params: {
        curPage: model_query.curPage,
        pageSize: model_query.pageSize,
        keyword: model_query.keyword,
        tableName: "model"
      },
    }).then(res=>{
      // console.log(res)
      modelData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/deblurSNum',
      params: {
        keyword: model_query.keyword,
        tableName: "model"
      },
    }).then(res=>{
      modelTotal.value= res.data.results
    })
  }
}

const getResultData = () => {
  if(result_query.keyword.length == 0){
    axios({
      method: 'GET',
      url: '/page',
      params: {
        curPage: result_query.curPage,
        pageSize: result_query.pageSize,
        tableName: "result"
      },
    }).then(res=>{
      resultData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/num',
      params: {
        tableName: "result",
        rid: -2
      },
    }).then(res=>{
      resultTotal.value= res.data.results
    })
  }
  else{
    axios({
      method: 'GET',
      url: '/deblurS',
      params: {
        curPage: result_query.curPage,
        pageSize: result_query.pageSize,
        keyword: result_query.keyword,
        tableName: "result"
      },
    }).then(res=>{
      // console.log(res)
      resultData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/deblurSNum',
      params: {
        keyword: result_query.keyword,
        tableName: "result"
      },
    }).then(res=>{
      resultTotal.value= res.data.results
    })
  }
}


onMounted(() => {
  getData()
  getModelData()
  getResultData()
})

// 下面的分页get得到数据操作是针对的file表
// 模糊查找操作
const handleSearch = () => {
  query.curPage = 1;
  getData();
};
// 列出全部
const handleAll = () => {
  query.keyword = '';
  query.curPage = 1
  getData();
}

// 分页导航
const handlePageChange = (val: number) => {
  query.curPage = val;
  getData();
};

const handleSizeChange  = (val: number) => {
  query.pageSize = val;
  getData();
};

// 下面的分页get得到数据操作是针对的model表
// 模糊查找操作
const handleModelSearch = () => {
  model_query.curPage = 1;
  getModelData();
};

// 列出全部
const handleModelAll = () => {
  model_query.keyword = '';
  model_query.curPage = 1
  getModelData();
}

// 分页导航
const handleModelPageChange = (val: number) => {
  model_query.curPage = val;
  getModelData();
};

const handleModelSizeChange  = (val: number) => {
  model_query.pageSize = val;
  getModelData();
};


// 下面的分页get得到数据操作是针对的result表

const dateForm = (originVal: number) => {  // 时间戳转日期
      var now = new Date(originVal * 1000)
      var y = now.getFullYear()
      var m = now.getMonth() + 1
      var d = now.getDate()
      return  y + "-" + (m < 10 ? "0" + m : m) + "-" +
          (d < 10 ? "0" + d : d) + " " + now.toTimeString().substr(0, 8);
}


const handleResultSearch = () => {
  result_query.curPage = 1;
  getResultData();
};

// 列出全部
const handleResultAll = () => {
  result_query.keyword = '';
  result_query.curPage = 1
  getResultData();
}

// 分页导航
const handleResultPageChange = (val: number) => {
  result_query.curPage = val;
  getResultData();
};

const handleResultSizeChange  = (val: number) => {
  result_query.pageSize = val;
  getResultData();
};

/***
 记录相关操作
 ***/
const resultEditVisible = ref(false)
const resultEditForm = reactive({
  rid: -1,
  wid: -1,
  timestamp: '',
  type: '',
  addition: '',
  mission_type: '',
  filename: '',
  num: -1
})

const handleResultEdit = (row: any) => {
  for(var key in row){
    resultEditForm[key] = row[key]
  }
  resultEditVisible.value = true
}

const submitEdit = () => {
  resultEditVisible.value = false
  axios({
    method: 'PUT',
    url: '/result/' + resultEditForm.rid,
    data: resultEditForm
  }).then(res=>{
    console.log(res)
    if(res.data.status == 'success'){
      ElMessage.success('修改成功')
      getResultData()
    }
  })
}


/**
 * 显示物体信息细节
 */

// 下面定义的是obj表的相关数据
const obj_query = reactive({
  curPage: 1,
  pageSize: 5,
  tableName: "obj",
  rid: 1
})

const model_params = reactive({
  model_dataset: null,
  model_name: null,
  model_type: null,
  wid: -1
})

const resultParams = reactive({
  timestamp: "",
  type: "",
  addition: "",
  mission_type: "",
  filename: ""
})

const objData = ref<ObjItem[]>([])
const objTotal = ref(0)
const objDetail = ref(false)

const handleResultDetail = (row: any) => {
  objDetail.value = true
  for(var key in row){
    obj_query[key] = row[key]
    resultParams[key] = row[key]
  }
  axios({
    method: 'GET',
    url: '/wmodel/' + row.wid
  }).then(res=>{
    for(var key in res.data.results){
      model_params[key] = res.data.results[key]
    }
  })
  getResultDetail(row)
  objTotal.value = row.num
  // console.log(row.rid)
  // console.log({
  //   'curPage': obj_query.curPage,
  //   'pageSize': obj_query.pageSize
  // })
}

const getResultDetail = () => {
  axios({
    method: 'GET',
    url: '/objectPage/' + obj_query.rid,
    params:{
      'curPage': obj_query.curPage,
      'pageSize': obj_query.pageSize
    }
  }).then(res=>{
    objData.value = res.data.results
    for(var i = 0;i < objData.value.length;i++){
      objData.value[i].conf = objData.value[i].conf.toFixed(3)
      // console.log('114514')
    }
  })
}

const handleObjPageChange = (val: number) => {
  obj_query.curPage = val;
  getResultDetail()
};

const handleObjSizeChange  = (val: number) => {
  obj_query.pageSize = val;
  getResultDetail()
};

const handleResultDelete = (row: any) =>{
  ElMessageBox.confirm('确定要删除记录吗?', '提示', {
    type: 'warning'
  }).then(()=>{
    axios({
      method: 'DELETE',
      url: '/obj/' + row.rid
    }).then(res=>{
      if(res.data.status == 'success'){
        axios({
          method: 'DELETE',
          url: '/result/' + row.rid  // 删除的文件操作和删除的数据操作是耦合的
        }).then(res=>{
          if(res.data.status == 'success'){
            ElMessage.success('记录删除成功')
            getResultData()
          }
          else{
            ElMessage.error('记录删除出现异常')
          }
        })
      }
      else{
        ElMessage.error('记录删除出现异常')
      }
    })
  })
}

// 查看模型的内部细节
const handleModelDetail = (index: number, row: any) =>{

}



</script>

<style scoped>
.content-title {
  font-weight: 400;
  line-height: 50px;
  margin: 10px 0;
  font-size: 22px;
  color: #1f2f3d;
}
.upload-demo {
  width: 360px;
}

.handle-box {
  margin-bottom: 20px;
}

.handle-select {
  width: 120px;
}

.handle-input {
  width: 300px;
}
.table {
  width: 100%;
  font-size: 14px;
}
.red {
  color: red;
}

.primary{
  color: cornflowerblue;
}

.green{
  color: green;
}

.blue{
  color: aquamarine;
}

.mr10 {
  margin-right: 10px;
}
.table-td-thumb {
  display: block;
  margin: auto;
  width: 40px;
  height: 40px;
}

</style>


================================================
FILE: detection-fontend/src/viewDetect/detectVue.vue
================================================
<template>
  <div class="container">
    <div class="content-title">模型本地上传</div>
    <div class="plugins-tips">
      <!--      <a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>-->
      <p style="line-height: 30px">
        建议上传的文件格式为ultralytics可兼容的,文件名中途是不可修改的,除非删除再上传一次
      </p>

      <p style="line-height: 30px">
        首先先上传对应的pt文件,然后进行缓存,最后将填写的信息提交到终端
      </p>

      <p style="line-height: 30px">
        如果重复提交多次到缓存里,则最新的一次会覆盖掉之前的
      </p>

      <p style="line-height: 30px">
        注意填写模型名称的时候不要于现有的模型名称重复
      </p>
    </div>
    <div class="form-box">

      <el-form ref="formRef" :model="AddModelForm" label-width="80px" :rules="rules">
        <el-form-item label="模型名称" prop="model_name" label-width="150px">
          <el-input v-model="AddModelForm.model_name"></el-input>
        </el-form-item>

        <el-form-item label="模型种类" prop="model_type" label-width="150px">
          <el-select v-model="AddModelForm.model_type" placeholder="请选择">
            <el-option key="yolov5" label="yolov5" value="yolov5"></el-option>
            <el-option key="yolov8" label="yolov8" value="yolov8"></el-option>
          </el-select>
        </el-form-item>

        <el-form-item label="对应数据集名称" prop="model_dataset" label-width="150px">
          <el-input v-model="AddModelForm.model_dataset"></el-input>
        </el-form-item>


        <el-form-item label="上传文件" label-width="150px">
          <el-upload
              class="upload-demo"
              drag
              :limit="1"
              :show-file-list="true"
              action="http://localhost:5000/upload/model/"
              multiple
              :on-success="handleUploadCache"
          >
            <!--   action是将图片file文件直接上传到后端服务器上,要想将图片附带的数据信息上传必须要实现额外函数 -->
            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
            <div class="el-upload__text">
              将文件拖到此处,或
              <em>点击上传</em>
            </div>
          </el-upload>
        </el-form-item>

        <el-form-item label-width="150px" label="操作">
          <el-button type="primary" @click="handleModelPost(AddModelForm)">表单提交</el-button>
          <el-button type="warning" @click="handleModelFormReset">重置表单</el-button>
        </el-form-item>

      </el-form>
    </div>

    <div>
      <div class="content-title">当前模型</div>

      <el-descriptions :column="3" border title="检测模型">
        <el-descriptions-item
            label="模型ID"
            label-align="right"
            align="center"
            label-class-name="my-label"
            class-name="my-content"
            width="150px">
          {{model_params.wid}}
        </el-descriptions-item>

        <el-descriptions-item label="文件名" label-align="right" align="center">
          {{model_params.model_name}}
        </el-descriptions-item>
        <el-descriptions-item label="模型类别" label-align="right" align="center">
          <el-tag size="middle" type="primary">{{model_params.model_type}}</el-tag>
        </el-descriptions-item>

        <el-descriptions-item label="对应数据集" label-align="right" align="center">
          {{model_params.model_dataset}}
        </el-descriptions-item>
      </el-descriptions>

    </div>

    <div>
      <div class="content-title">模型管理</div>
      <div class="handle-box">
        <el-input v-model="model_query.keyword" placeholder="模型名称" class="handle-input mr10"></el-input>
        <el-button type="primary" :icon="Search " @click="handleModelSearch">搜索</el-button>

        <el-button type="success"  @click="handleModelAll">
          <el-icon><List /></el-icon>
          <span>列出模型</span>
        </el-button>
      </div>


      <el-table :data="modelData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
        <el-table-column prop="wid" label="模型ID" width="55" align="center"></el-table-column>
        <el-table-column prop="model_name" label="文件名"></el-table-column>


        <el-table-column prop="model_type" label="模型类别"></el-table-column>
        <el-table-column prop="model_dataset" label="对应数据集"></el-table-column>


        <el-table-column label="操作" width="300" align="center">
          <template #default="scope">
<!--            <el-button text :icon="Search" class="green" @click="handleModelDetail(scope.$index, scope.row)" v-permiss="15">-->
<!--              查看-->
<!--            </el-button>-->
            <el-button text :icon="TopLeft" class="primary" @click="handleModelChoose(scope.$index, scope.row)" v-permiss="15">
              选中
            </el-button>
            <el-button text :icon="Edit" @click="handleModelEdit(scope.$index, scope.row)" v-permiss="15">
              编辑
            </el-button>
            <el-button text :icon="Delete" class="red" @click="handleModelDelete(scope.row)" v-permiss="16">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <div class="pagination">
        <el-pagination
            background
            layout="total, sizes, prev, pager, next, jumper"
            v-model:current-page="model_query.curPage"
            v-model:page-size="model_query.pageSize"

            :page-sizes="[5, 10]"
            :total="modelTotal"
            @current-change="handleModelPageChange"
            @size-change="handleModelSizeChange"

        ></el-pagination>
      </div>
    </div>


      <div class="content-title">图片检测</div>

      <div class="plugins-tips">
        <!--      <a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>-->
        <p style="line-height: 20px">
          点击检测即可得到该图片在当前模型的检测结果,但需要等待几秒钟
        </p>
      </div>

      <div class="handle-box">
        <el-input v-model="query.keyword" placeholder="文件名称" class="handle-input mr10"></el-input>
        <el-button type="primary" :icon="Search " @click="handleSearch">搜索</el-button>

<!--        <el-button type="primary"  @click="getData">-->
<!--          <el-icon><Refresh /></el-icon>-->
<!--          <span>刷新页面</span>-->
<!--        </el-button>-->

        <el-button type="success"  @click="handleAll">
          <el-icon><List /></el-icon>
          <span>列出全部</span>
        </el-button>
      </div>

      <el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
        <el-table-column prop="fid" label="文件ID" width="55" align="center"></el-table-column>
        <el-table-column prop="name" label="文件名"></el-table-column>

        <el-table-column label="图片(查看大图)" align="center">
          <template #default="scope">
            <el-image
                class="table-td-thumb"
                :src="scope.row.img_url"
                :z-index="10"
                :preview-src-list="[scope.row.img_url]"
                preview-teleported
            >
            </el-image>
          </template>
        </el-table-column>

        <el-table-column label="时间">
          <template #default="scope">{{ scope.row.timestamp }}</template>
        </el-table-column>

        <el-table-column prop="type" label="文件类别"></el-table-column>
        <el-table-column prop="origin" label="文件来源"></el-table-column>
        <el-table-column prop="width" label="宽度"></el-table-column>
        <el-table-column prop="height" label="高度"></el-table-column>

        <!--      <el-table-column prop="date" label="注册时间"></el-table-column>-->
        <el-table-column label="操作" width="220" align="center">
          <template #default="scope">
            <el-button text :icon="Search" class="green" @click="detection(scope.$index, scope.row)" v-permiss="16">
              检测
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <div class="pagination">
        <el-pagination
            background
            layout="total, sizes, prev, pager, next, jumper"
            v-model:current-page="query.curPage"
            v-model:page-size="query.pageSize"

            :page-sizes="[5, 10]"
            :total="pageTotal"
            @current-change="handlePageChange"
            @size-change="handleSizeChange"

        ></el-pagination>
      </div>

      <el-dialog title="编辑" v-model="editVisible" width="30%">
        <el-form label-width="100px">
<!--          <el-form-item label="用户名">-->
<!--            <el-input v-model="form.name"></el-input>-->
<!--          </el-form-item>-->
<!--          <el-form-item label="地址">-->
<!--            <el-input v-model="form.address"></el-input>-->
<!--          </el-form-item>-->
          <el-form-item label="模型ID">
            <span>{{form.wid}}</span>
          </el-form-item>

          <el-form-item label="文件名">
            <span>{{form.mdoel_name}}</span>
          </el-form-item>

          <el-form-item label="模型种类" prop="region">
            <el-select v-model="form.model_type" placeholder="请选择">
              <el-option key="yolov5" label="yolov5" value="yolov5"></el-option>
              <el-option key="yolov8" label="yolov8" value="yolov8"></el-option>
            </el-select>
          </el-form-item>

          <el-form-item label="对应数据集">
            <el-input v-model="form.model_dataset"></el-input>
          </el-form-item>
        </el-form>
        <template #footer>
          <span class="dialog-footer">
            <el-button @click="editVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveModelEdit">确 定</el-button>
          </span>
        </template>
      </el-dialog>


      <el-dialog title="模型细节" v-model="detailVisible" width="30%">
      <el-form label-width="100px">
        <!--          <el-form-item label="用户名">-->
        <!--            <el-input v-model="form.name"></el-input>-->
        <!--          </el-form-item>-->
        <!--          <el-form-item label="地址">-->
        <!--            <el-input v-model="form.address"></el-input>-->
        <!--          </el-form-item>-->


      </el-form>
      <template #footer>
          <span class="dialog-footer">
            <el-button @click="detailVisible = false" >退出</el-button>
          </span>
      </template>
    </el-dialog>
  </div>

</template>

<script lang="ts" setup>
import {ref, reactive, onMounted} from 'vue';
import {ElMessage, ElMessageBox, FormRules} from 'element-plus';
import {Delete, Edit, Search, Plus, TopLeft} from '@element-plus/icons-vue';
import axios from "axios";
import {compileScript} from "@vue/compiler-sfc";
// import { fetchData } from '../api/index';

interface TableItem { // 定义图片的接口

  // id: number;
  // name: string;
  // money: string;
  // state: string;
  // date: string;
  // address: string;
  fid: number;
  height: number;
  width: number;
  origin: string;
  timestamp:string;
  type: string;
  name: string;
  img_url: string;
}

interface ModelItem{ // 定义model的接口
  wid: number,
  model_name: string,
  model_type: string,
  model_dataset: string
}

// 下面定义的是file表相关的数据
const query = reactive({
  curPage: 1,
  pageSize: 10,
  tableName: "file",
  keyword: ""
});

const tableData = ref<TableItem[]>([]);  // 表格数据

const pageTotal = ref(0);

// 下面定义的是model表相关的数据
const model_query = reactive({
  curPage: 1,
  pageSize: 5,
  tableName: "model",
  keyword: ""
})

const modelData = ref<ModelItem[]>([])
const modelTotal = ref(0)

// 下面是定义当前后端使用那个model的表相关数据信息
const model_params = reactive({
  "model_dataset": "null",
  "model_name": "null",
  "model_type": "null",
  "wid": -1
})

const getCurModel = () => { // 获取后端当前使用的模型信息
  axios({
    method: 'GET',
    url: '/current',
  }).then(res=>{
    // console.log(res)
    model_params.model_dataset =  res.data.results.model_dataset
    model_params.model_name = res.data.results.model_name
    model_params.model_type = res.data.results.model_type
    model_params.wid = res.data.results.wid
    // console.log(model_params)
  })
}

// 获取file表格数据
const getData = () => {
  // console.log(query)

  if(query.keyword.length == 0){
    axios({
      method: 'GET',
      url: '/page',
      params: {
        curPage: query.curPage,
        pageSize: query.pageSize,
        tableName: "file"
      },
    }).then(res=>{
      tableData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/num',
      params: {
        tableName: "file",
        rid: -2
      },
    }).then(res=>{
      pageTotal.value= res.data.results
    })
  }
  else{
    axios({
      method: 'GET',
      url: '/deblurS',
      params: {
        curPage: query.curPage,
        pageSize: query.pageSize,
        keyword: query.keyword,
        tableName: "file"
      },
    }).then(res=>{
      // console.log(res)
      tableData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/deblurSNum',
      params: {
        keyword: query.keyword,
        tableName: "file"
      },
    }).then(res=>{
      pageTotal.value= res.data.results
    })
  }
};

// 获得model表格的数据
const getModelData = () => {
  if(model_query.keyword.length == 0){
    axios({
      method: 'GET',
      url: '/page',
      params: {
        curPage: model_query.curPage,
        pageSize: model_query.pageSize,
        tableName: "model"
      },
    }).then(res=>{
      modelData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/num',
      params: {
        tableName: "model",
        rid: -2
      },
    }).then(res=>{
      modelTotal.value= res.data.results
    })
  }
  else{
    axios({
      method: 'GET',
      url: '/deblurS',
      params: {
        curPage: model_query.curPage,
        pageSize: model_query.pageSize,
        keyword: model_query.keyword,
        tableName: "model"
      },
    }).then(res=>{
      // console.log(res)
      modelData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/deblurSNum',
      params: {
        keyword: model_query.keyword,
        tableName: "model"
      },
    }).then(res=>{
      modelTotal.value= res.data.results
    })
  }
}


onMounted(() => {
  getData()
  getModelData()
  getCurModel()
})

// 下面的分页get得到数据操作是针对的file表
// 模糊查找操作
const handleSearch = () => {
  query.curPage = 1;
  getData();
};
// 列出全部
const handleAll = () => {
  query.keyword = '';
  query.curPage = 1
  getData();
}

// 分页导航
const handlePageChange = (val: number) => {
  query.curPage = val;
  getData();
};

const handleSizeChange  = (val: number) => {
  query.pageSize = val;
  getData();
};

// 下面的分页get得到数据操作是针对的model表
// 模糊查找操作
const handleModelSearch = () => {
  model_query.curPage = 1;
  getModelData();
};

// 列出全部
const handleModelAll = () => {
  model_query.keyword = '';
  model_query.curPage = 1
  getModelData();
}

// 分页导航
const handleModelPageChange = (val: number) => {
  model_query.curPage = val;
  getModelData();
};

const handleModelSizeChange  = (val: number) => {
  model_query.pageSize = val;
  getModelData();
};

const handleUploadCache = (response: any) =>{
  if(response.status == 'success'){
    ElMessage.success('上传暂缓PT文件成功')
  }
  else{
    ElMessage.error('上传暂缓文件PT文件失败')
  }
}

/***
 模型相关操作
 ***/

//模型表格编辑时弹窗和保存
const editVisible = ref(false);
let form = reactive({
  wid: -1,
  mdoel_name: '',
  model_type: 'yolov8',
  model_dataset: '',
});

let idx: number = -1;
const handleModelEdit = (index: number, row: any) => {
  idx = index;
  form.model_type = row.model_type;
  form.model_dataset = row.model_dataset
  form.mdoel_name = row.model_name
  form.wid = row.wid;
  editVisible.value = true;
};

const saveModelEdit = () => {
  axios({
    method: 'PUT',
    url: '/wmodel/' + form.wid,
    data: {
      'model_name': form.mdoel_name,
      'model_type': form.model_type,
      'model_dataset': form.model_dataset
    },
  }).then(res=>{
    if(res.data.status == 'success'){
      editVisible.value = false
      alert("模型信息修改成功")
      getModelData()
    }
    // console.log(tableData)
  })
}

const handleModelChoose = (index: number, row: any) => {
    axios(
        {
          method: 'PUT',
          url: '/switch',
          data: row
        }
    ).then(res=>{
      if(res.data.status == 'success'){
        ElMessage.success('切换模型成功');
        getCurModel()
      }
    })
}

//上传添加新模型相关操作
const AddModelForm = reactive({
  model_name: '',
  model_type: 'yolov8',
  model_dataset: ''
})

// 查看模型的内部细节
const detailVisible = ref(false)

const handleModelDetail = (index: number, row: any) =>{
  detailVisible.value = true
  // console.log(row)
  axios({
    method: 'GET',
    url: '/detail/model',
    params: row
  }).then(res => {
    console.log(res)
    if(res.data.status == 'success'){

    }
  })
}

// 上传模型相关信息

const handleModelPost = (form: any) => {
  // console.log("114514")
  // console.log(form)
  if(form.model_dataset.length == 0 || form.model_name.length < 3){
    ElMessage.error('提交失败,请检查是否符合表格要求')
  }
  else{
    axios({
      method: 'POST',
      url: '/wmodel', // 这里的后端请求url功能和文件复制是耦合的,也就是 复制pt -> 创建pt信息
      data: {
        'model_name': form.model_name,
        'model_dataset': form.model_dataset,
        'model_type': form.model_type
      }
    }).then(res => {
      if(res.data.status == 'success'){
        ElMessage.success('模型上传成功')
        getModelData()
      }
      else{
        ElMessage.error('模型上传失败')
      }
    })
  }
}

const handleModelFormReset = () => {
  AddModelForm.model_name = ''
  AddModelForm.model_type = 'yolov8'
  AddModelForm.model_dataset = ''
}

const rules = reactive<FormRules>({
  model_name: [
    {required: true, message: '请输入模型名称', trigger: 'blur' },
    {min: 3, message: '长度至少为3', trigger: 'blur'}
  ],
  model_dataset:[
    { required: true, message: '请输入数据集名称', trigger: 'blur' },
  ]
})

// 删除操作
const handleModelDelete = (scope: any) => {
  // console.log(scope)
  if(scope.wid <= 6){
    ElMessage.error('系统不允许删除前六个固定模型')
  }
  else{

    if(scope.wid != model_params.wid){
      ElMessageBox.confirm('确定要删除吗?', '提示', {
        type: 'warning'
      }).then(()=>{
        axios({
          method: 'delete',
          url: '/wmodel/' + scope.wid  // 这里的后端请求url功能和文件删除是耦合的,也就是 删除pt -> 删除pt信息
        }).then(res => {
          if(res.data.status == 'success'){
            ElMessage.success('删除模型成功')
            getModelData()
          }else{
            ElMessage.error('删除模型失败')
          }
        })
      })
    }
    else{
      ElMessage.error('删除的模型是当前后端所使用的模型')
    }
  }
}


/**
 * 实现图片检测相关操作
 */
const detection = (index: number, row: any) => {
  console.log(row)
  axios({ // 首先执行检测任务,生成结果图片
    method: 'POST',
    url: '/detect',
    data: row
  }).then(res1=>{
    // console.log(res1)
    if(res1.data.status == 'success'){ // 然后再把result得到的json传入到数据库中
      axios({
        method: 'POST',
        url: '/result',
        data: res1.data.results.log
      }).then(res2=>{
        // console.log(res2)
        if(res2.data.status == 'success'){ // 最后再把obj信息传入到obj中
          axios({
            method: 'POST',
            url: '/obj',
            data: res1.data // 在/detect后端哪里得到的json信息
          }).then(res3 => {
            // console.log(res3)
              if(res3.data.status == 'success'){
                ElMessage.success('目标图片检测成功,保存的文件名称是:\n'
                    + res1.data.results.log.filename + '_' +
                    res1.data.results.log.timestamp + '.' + res1.data.results.log.type);
              }
          })
        }
      })
    }
    else{
      ElMessage.error('检测中途出现异常')
    }
  })
}

</script>

<style scoped>
.content-title {
  font-weight: 400;
  line-height: 50px;
  margin: 10px 0;
  font-size: 22px;
  color: #1f2f3d;
}
.upload-demo {
  width: 360px;
}

.handle-box {
  margin-bottom: 20px;
}

.handle-select {
  width: 120px;
}

.handle-input {
  width: 300px;
}
.table {
  width: 100%;
  font-size: 14px;
}
.red {
  color: red;
}

.primary{
  color: cornflowerblue;
}

.green{
  color: green;
}

.blue{
  color: aquamarine;
}

.mr10 {
  margin-right: 10px;
}
.table-td-thumb {
  display: block;
  margin: auto;
  width: 40px;
  height: 40px;
}

.my-label {
  background: var(--el-color-success-light-9);
}
.my-content {
  background: var(--el-color-danger-light-9);
}

</style>


================================================
FILE: detection-fontend/src/viewDetect/uploadFile.vue
================================================
<template>
  <div class="container">

    <div>
      <div class="content-title">文件本地上传</div>
      <div class="plugins-tips">
        <!--      <a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>-->
        <p style="line-height: 20px">
          建议上传的图片格式为JPG & PNG
        </p>
      </div>

      <el-upload
          class="upload-demo"
          drag
          :limit="1"
          :show-file-list="true"
          action="http://localhost:5000/upload/pic/"
          multiple
          :on-success="upload"
      >
        <!--   action是将图片file文件直接上传到后端服务器上,要想将图片附带的数据信息上传必须要实现额外函数 -->
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">
          将文件拖到此处,或
          <em>点击上传</em>
        </div>
      </el-upload>
    </div>


    <div>
      <div class="content-title">URL地址上传</div>

      <div class="plugins-tips">
        <!--      <a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>-->
        <p style="line-height: 30px">
          参照的格式如下:
        </p>
        <p style="line-height: 30px">
          https://n.sinaimg.cn/sinakd20220516s/290/w1080h810/20220516/8114-e6ab336af96e393f3f312a58349114d8.png
        </p>
      </div>

      <el-input v-model="url" placeholder="图片url地址" class="handle-input mr10"></el-input>

      <el-button type="primary"  @click="uploadURL(url)">
        <el-icon><Upload /></el-icon>
        <span>上传</span>
      </el-button>
    </div>

    <div class="content-title">图片查询</div>
    <div class="handle-box">
      <el-input v-model="query.keyword" placeholder="文件名称" class="handle-input mr10"></el-input>
      <el-button type="primary" :icon="Search " @click="handleSearch">搜索</el-button>

      <el-button type="success"  @click="handleAll">
        <el-icon><List /></el-icon>
        <span>列出全部</span>
      </el-button>
    </div>

    <el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
      <el-table-column prop="fid" label="文件ID" width="55" align="center"></el-table-column>
      <el-table-column prop="name" label="文件名"></el-table-column>

      <el-table-column label="图片(查看大图)" align="center">
        <template #default="scope">
          <el-image
              class="table-td-thumb"
              :src="scope.row.img_url"
              :z-index="10"
              :preview-src-list="[scope.row.img_url]"
              preview-teleported
          >
          </el-image>
        </template>
      </el-table-column>

      <el-table-column label="时间">
        <template #default="scope">{{ scope.row.timestamp }}</template>
      </el-table-column>

      <el-table-column prop="type" label="文件类别"></el-table-column>
      <el-table-column prop="origin" label="文件来源"></el-table-column>
      <el-table-column prop="width" label="宽度"></el-table-column>
      <el-table-column prop="height" label="高度"></el-table-column>
<!--      <el-table-column prop="date" label="注册时间"></el-table-column>-->
      <el-table-column label="操作" width="220" align="center">
        <template #default="scope">
          <el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index)" v-permiss="16">
            删除
          </el-button>
<!--          <el-button text @click="deletePrepare">Click to open the Message Box</el-button>-->
        </template>
      </el-table-column>
    </el-table>

    <div class="pagination">
      <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          v-model:current-page="query.curPage"
          v-model:page-size="query.pageSize"

          :page-sizes="[5, 10, 20]"
          :total="pageTotal"
          @current-change="handlePageChange"
          @size-change="handleSizeChange"

      ></el-pagination>
    </div>

  </div>
</template>

<script lang="ts" setup>
import {ref, reactive, onMounted} from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
import axios from "axios";
import {compileScript} from "@vue/compiler-sfc";
// import { fetchData } from '../api/index';

interface TableItem {

  // id: number;
  // name: string;
  // money: string;
  // state: string;
  // date: string;
  // address: string;
  fid: number;
  height: number;
  width: number;
  origin: string;
  timestamp:string;
  type: string;
  name: string;
  img_url: string;
}

const query = reactive({
  curPage: 1,
  pageSize: 10,
  tableName: "file",
  keyword: ""
});

const tableData = ref<TableItem[]>([]);  // 表格数据

const pageTotal = ref(0);

const url = ref("")
// 获取表格数据
const getData = () => {
  // console.log(query)

  if(query.keyword.length == 0){
      axios({
        method: 'GET',
        url: '/page',
        params: {
          curPage: query.curPage,
          pageSize: query.pageSize,
          tableName: "file"
        },
      }).then(res=>{
        tableData.value= res.data.results
        // console.log(tableData)
      })

      axios({
        method: 'GET',
        url: '/num',
        params: {
          tableName: "file",
          rid: -2
        },
      }).then(res=>{
        pageTotal.value= res.data.results
      })
  }
  else{
    axios({
      method: 'GET',
      url: '/deblurS',
      params: {
        curPage: query.curPage,
        pageSize: query.pageSize,
        keyword: query.keyword,
        tableName: "file"
      },
    }).then(res=>{
      // console.log(res)
      tableData.value= res.data.results
      // console.log(tableData)
    })

    axios({
      method: 'GET',
      url: '/deblurSNum',
      params: {
        keyword: query.keyword,
        tableName: "file"
      },
    }).then(res=>{
      pageTotal.value= res.data.results
    })
  }
};


onMounted(() => {
  getData()
  // getNum()
})

// 查询操作
const handleSearch = () => {
  query.curPage = 1;
  getData();
};
// 列出全部
const handleAll = () => {
  query.keyword = '';
  query.curPage = 1
  getData();
}

// 分页导航
const handlePageChange = (val: number) => {
  query.curPage = val;
  getData();
};

const handleSizeChange  = (val: number) => {
  query.pageSize = val;
  getData();
};

// 删除操作
const deletePrepare = (index: number) => {

}
const handleDelete = (index: number) => {
  // 二次确认删除
  ElMessageBox.confirm('确定要删除吗?', '提示', {
    type: 'warning'
  })
      .then(() => {
        var _cache: any = tableData.value[index].fid
        // console.log(tableData.value[index])
        axios.delete("/delete/file", {
          data:{
            type: tableData.value[index].type,
            name: tableData.value[index].name
          }
        }).then(
            res=>{
              // console.log(res.data.status)
              if(res.data.status == 'success'){
                // console.log("1919")
                axios.delete("/files/"+ _cache).then(
                    res2=>{
                      // console.log(res2)
                      if(res2.data.status == 'success'){
                        ElMessage.success('删除成功');
                        tableData.value.splice(index, 1);
                        // alert("删除图片成功")
                        getData()
                      }
                    }
                )
              }
            }
        )
      }
      ).catch(() => {ElMessage.success('删除图片过程中断');});
  // getData()
};

// 本地文件上传相关操作
const upload = (response: any, file: any, filelist: any) => {
  // console.log('114514')
  // console.log(response)
  // console.log("114514")
  // console.log({
  //   width: response.results.width,
  //   height: response.results.height,
  //   origin: response.results.origin,
  //   type: response.results.type,
  //   name: response.results.name,
  // })
    axios.post(
        '/files', {
          width: response.results.width,
          height: response.results.height,
          origin: response.results.origin,
          type: response.results.type,
          name: response.results.name,
        }
    ).then(res=>{
      if(res.data.status == 'success'){
        // console.log(1919810)
        getData()
      }
    })
};


// URL图片上传相关操作
const uploadURL = (url: any) => {
  // console.log(url)
  // console.log("114514")
  axios.post('/upload/url', {
    url: url
  }).then((res)=>{
    if(res.data.status == 'success'){
      axios.post('/files', res.data.results).then(
          (res) => {
            if(res.data.status == 'success'){
              alert('url添加数据成功')
              getData()
            }
            else{
              alert('出现异常')
            }
          }
      )
    }
    else{
      alert("爬取图片失败")
    }
  })
}
</script>

<style scoped>
.content-title {
  font-weight: 400;
  line-height: 50px;
  margin: 10px 0;
  font-size: 22px;
  color: #1f2f3d;
}
.upload-demo {
  width: 360px;
}

.handle-box {
  margin-bottom: 20px;
}

.handle-select {
  width: 120px;
}

.handle-input {
  width: 300px;
}
.table {
  width: 100%;
  font-size: 14px;
}
.red {
  color: #F56C6C;
}
.mr10 {
  margin-right: 10px;
}
.table-td-thumb {
  display: block;
  margin: auto;
  width: 40px;
  height: 40px;
}

</style>


================================================
FILE: detection-fontend/src/views/403.vue
================================================
<template>
	<div class="error-page">
		<div class="error-code">4<span>0</span>3</div>
		<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
		<div class="error-handle">
			<router-link to="/">
				<el-button type="primary" size="large">返回首页</el-button>
			</router-link>
			<el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
		</div>
	</div>
</template>

<script setup lang="ts" name="403">
import { useRouter } from 'vue-router';

const router = useRouter();
const goBack = () => {
	router.go(-2);
};
</script>

<style scoped>
.error-page {
	display: flex;
	justify-content: center;
	align-items: center;
	flex-direction: column;
	width: 100%;
	height: 100%;
	background: #f3f3f3;
	box-sizing: border-box;
}
.error-code {
	line-height: 1;
	font-size: 250px;
	font-weight: bolder;
	color: #f02d2d;
}
.error-code span {
	color: #00a854;
}
.error-desc {
	font-size: 30px;
	color: #777;
}
.error-handle {
	margin-top: 30px;
	padding-bottom: 200px;
}
.error-btn {
	margin-left: 100px;
}
</style>


================================================
FILE: detection-fontend/src/views/404.vue
================================================
<template>
	<div class="error-page">
		<div class="error-code">4<span>0</span>4</div>
		<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
		<div class="error-handle">
			<router-link to="/">
				<el-button type="primary" size="large">返回首页</el-button>
			</router-link>
			<el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
		</div>
	</div>
</template>

<script setup lang="ts" name="404">
import { useRouter } from 'vue-router';

const router = useRouter();
const goBack = () => {
	router.go(-1);
};
</script>

<style scoped>
.error-page {
	display: flex;
	justify-content: center;
	align-items: center;
	flex-direction: column;
	width: 100%;
	height: 100%;
	background: #f3f3f3;
	box-sizing: border-box;
}
.error-code {
	line-height: 1;
	font-size: 250px;
	font-weight: bolder;
	color: #2d8cf0;
}
.error-code span {
	color: #00a854;
}
.error-desc {
	font-size: 30px;
	color: #777;
}
.error-handle {
	margin-top: 30px;
	padding-bottom: 200px;
}
.error-btn {
	margin-left: 100px;
}
</style>


================================================
FILE: detection-fontend/src/views/charts.vue
================================================
<template>
	<div class="container">
		<div class="plugins-tips">
			vue-schart:vue.js封装sChart.js的图表组件。 访问地址:
			<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
		</div>
		<div class="schart-box">
			<div class="content-title">柱状图</div>
			<schart class="schart" canvasId="bar" :options="options1"></schart>
		</div>
		<div class="schart-box">
			<div class="content-title">折线图</div>
			<schart class="schart" canvasId="line" :options="options2"></schart>
		</div>
		<div class="schart-box">
			<div class="content-title">饼状图</div>
			<schart class="schart" canvasId="pie" :options="options3"></schart>
		</div>
		<div class="schart-box">
			<div class="content-title">环形图</div>
			<schart class="schart" canvasId="ring" :options="options4"></schart>
		</div>
	</div>
</template>

<script setup lang="ts" name="basecharts">
import Schart from 'vue-schart';

const options1 = {
	type: 'bar',
	title: {
		text: '最近一周各品类销售图'
	},
	bgColor: '#fbfbfb',
	labels: ['周一', '周二', '周三', '周四', '周五'],
	datasets: [
		{
			label: '家电',
			fillColor: 'rgba(241, 49, 74, 0.5)',
			data: [234, 278, 270, 190, 230]
		},
		{
			label: '百货',
			data: [164, 178, 190, 135, 160]
		},
		{
			label: '食品',
			data: [144, 198, 150, 235, 120]
		}
	]
};
const options2 = {
	type: 'line',
	title: {
		text: '最近几个月各品类销售趋势图'
	},
	bgColor: '#fbfbfb',
	labels: ['6月', '7月', '8月', '9月', '10月'],
	datasets: [
		{
			label: '家电',
			data: [234, 278, 270, 190, 230]
		},
		{
			label: '百货',
			data: [164, 178, 150, 135, 160]
		},
		{
			label: '食品',
			data: [114, 138, 200, 235, 190]
		}
	]
};
const options3 = {
	type: 'pie',
	title: {
		text: '服装品类销售饼状图'
	},
	legend: {
		position: 'left'
	},
	bgColor: '#fbfbfb',
	labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
	datasets: [
		{
			data: [334, 278, 190, 235, 260, 200, 141]
		}
	]
};
const options4 = {
	type: 'ring',
	title: {
		text: '环形三等分'
	},
	showValue: false,
	legend: {
		position: 'bottom',
		bottom: 40
	},
	bgColor: '#fbfbfb',
	labels: ['vue', 'react', 'angular'],
	datasets: [
		{
			data: [500, 500, 500]
		}
	]
};
</script>

<style scoped>
.schart-box {
	display: inline-block;
	margin: 20px;
}
.schart {
	width: 600px;
	height: 400px;
}
.content-title {
	clear: both;
	font-weight: 400;
	line-height: 50px;
	margin: 10px 0;
	font-size: 22px;
	color: #1f2f3d;
}
</style>


================================================
FILE: detection-fontend/src/views/dashboard.vue
================================================
<template>
	<div>
		<el-row :gutter="20">
			<el-col :span="8">
				<el-card shadow="hover" class="mgb20" style="height: 252px">
					<div class="user-info">
						<el-avatar :size="120" :src="imgurl" />
						<div class="user-info-cont">
							<div class="user-info-name">{{ name }}</div>
							<div>{{ role }}</div>
						</div>
					</div>
					<div class="user-info-list">
						上次登录时间:
						<span>2022-10-01</span>
					</div>
					<div class="user-info-list">
						上次登录地点:
						<span>东莞</span>
					</div>
				</el-card>
				<el-card shadow="hover" style="height: 252px">
					<template #header>
						<div class="clearfix">
							<span>语言详情</span>
						</div>
					</template>
					Vue
					<el-progress :percentage="79.4" color="#42b983"></el-progress>
					TypeScript
					<el-progress :percentage="14" color="#f1e05a"></el-progress>
					CSS
					<el-progress :percentage="5.6"></el-progress>
					HTML
					<el-progress :percentage="1" color="#f56c6c"></el-progress>
				</el-card>
			</el-col>
			<el-col :span="16">
				<el-row :gutter="20" class="mgb20">
					<el-col :span="8">
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
							<div class="grid-content grid-con-1">
								<el-icon class="grid-con-icon"><User /></el-icon>
								<div class="grid-cont-right">
									<div class="grid-num">1234</div>
									<div>用户访问量</div>
								</div>
							</div>
						</el-card>
					</el-col>
					<el-col :span="8">
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
							<div class="grid-content grid-con-2">
								<el-icon class="grid-con-icon"><ChatDotRound /></el-icon>
								<div class="grid-cont-right">
									<div class="grid-num">321</div>
									<div>系统消息</div>
								</div>
							</div>
						</el-card>
					</el-col>
					<el-col :span="8">
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
							<div class="grid-content grid-con-3">
								<el-icon class="grid-con-icon"><Goods /></el-icon>
								<div class="grid-cont-right">
									<div class="grid-num">5000</div>
									<div>商品数量</div>
								</div>
							</div>
						</el-card>
					</el-col>
				</el-row>
				<el-card shadow="hover" style="height: 403px">
					<template #header>
						<div class="clearfix">
							<span>待办事项</span>
							<el-button style="float: right; padding: 3px 0" text>添加</el-button>
						</div>
					</template>

					<el-table :show-header="false" :data="todoList" style="width: 100%">
						<el-table-column width="40">
							<template #default="scope">
								<el-checkbox v-model="scope.row.status"></el-checkbox>
							</template>
						</el-table-column>
						<el-table-column>
							<template #default="scope">
								<div
									class="todo-item"
									:class="{
										'todo-item-del': scope.row.status
									}"
								>
									{{ scope.row.title }}
								</div>
							</template>
						</el-table-column>
					</el-table>
				</el-card>
			</el-col>
		</el-row>
		<el-row :gutter="20">
			<el-col :span="12">
				<el-card shadow="hover">
					<schart ref="bar" class="schart" canvasId="bar" :options="options"></schart>
				</el-card>
			</el-col>
			<el-col :span="12">
				<el-card shadow="hover">
					<schart ref="line" class="schart" canvasId="line" :options="options2"></schart>
				</el-card>
			</el-col>
		</el-row>
	</div>
</template>

<script setup lang="ts" name="dashboard">
import Schart from 'vue-schart';
import { reactive } from 'vue';
import imgurl from '../assets/img/img.jpg';

const name = localStorage.getItem('ms_username');
const role: string = name === 'admin' ? '超级管理员' : '普通用户';

const options = {
	type: 'bar',
	title: {
		text: '最近一周各品类销售图'
	},
	xRorate: 25,
	labels: ['周一', '周二', '周三', '周四', '周五'],
	datasets: [
		{
			label: '家电',
			data: [234, 278, 270, 190, 230]
		},
		{
			label: '百货',
			data: [164, 178, 190, 135, 160]
		},
		{
			label: '食品',
			data: [144, 198, 150, 235, 120]
		}
	]
};
const options2 = {
	type: 'line',
	title: {
		text: '最近几个月各品类销售趋势图'
	},
	labels: ['6月', '7月', '8月', '9月', '10月'],
	datasets: [
		{
			label: '家电',
			data: [234, 278, 270, 190, 230]
		},
		{
			label: '百货',
			data: [164, 178, 150, 135, 160]
		},
		{
			label: '食品',
			data: [74, 118, 200, 235, 90]
		}
	]
};
const todoList = reactive([
	{
		title: '今天要修复100个bug',
		status: false
	},
	{
		title: '今天要修复100个bug',
		status: false
	},
	{
		title: '今天要写100行代码加几个bug吧',
		status: false
	},
	{
		title: '今天要修复100个bug',
		status: false
	},
	{
		title: '今天要修复100个bug',
		status: true
	},
	{
		title: '今天要写100行代码加几个bug吧',
		status: true
	}
]);
</script>

<style scoped>
.el-row {
	margin-bottom: 20px;
}

.grid-content {
	display: flex;
	align-items: center;
	height: 100px;
}

.grid-cont-right {
	flex: 1;
	text-align: center;
	font-size: 14px;
	color: #999;
}

.grid-num {
	font-size: 30px;
	font-weight: bold;
}

.grid-con-icon {
	font-size: 50px;
	width: 100px;
	height: 100px;
	text-align: center;
	line-height: 100px;
	color: #fff;
}

.grid-con-1 .grid-con-icon {
	background: rgb(45, 140, 240);
}

.grid-con-1 .grid-num {
	color: rgb(45, 140, 240);
}

.grid-con-2 .grid-con-icon {
	background: rgb(100, 213, 114);
}

.grid-con-2 .grid-num {
	color: rgb(100, 213, 114);
}

.grid-con-3 .grid-con-icon {
	background: rgb(242, 94, 67);
}

.grid-con-3 .grid-num {
	color: rgb(242, 94, 67);
}

.user-info {
	display: flex;
	align-items: center;
	padding-bottom: 20px;
	border-bottom: 2px solid #ccc;
	margin-bottom: 20px;
}

.user-info-cont {
	padding-left: 50px;
	flex: 1;
	font-size: 14px;
	color: #999;
}

.user-info-cont div:first-child {
	font-size: 30px;
	color: #222;
}

.user-info-list {
	font-size: 14px;
	color: #999;
	line-height: 25px;
}

.user-info-list span {
	margin-left: 70px;
}

.mgb20 {
	margin-bottom: 20px;
}

.todo-item {
	font-size: 14px;
}

.todo-item-del {
	text-decoration: line-through;
	color: #999;
}

.schart {
	width: 100%;
	height: 300px;
}
</style>


================================================
FILE: detection-fontend/src/views/donate.vue
================================================
<template>
	<div class="container">
		<div class="plugins-tips">
			如果该框架对你有帮助,那就请作者喝杯饮料吧!<el-icon><ColdDrink /></el-icon> 加微信号linxin_20探讨问题。
		</div>
		<div>
			<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
		</div>
	</div>
</template>

<script setup lang="ts" name="donate"></script>

<style></style>


================================================
FILE: detection-fontend/src/views/editor.vue
================================================
<template>
	<div class="container">
		<div class="plugins-tips">
			wangEditor:轻量级 web 富文本编辑器,配置方便,使用简单。 访问地址:
			<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
		</div>
		<div class="mgb20" ref="editor"></div>
		<el-button type="primary" @click="syncHTML">提交</el-button>
	</div>
</template>

<script setup lang="ts" name="editor">
import WangEditor from 'wangeditor';
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';

const editor = ref(null);
const content = reactive({
	html: '',
	text: ''
});
let instance: any;
onMounted(() => {
	instance = new WangEditor(editor.value);
	instance.config.zIndex = 1;
	instance.create();
});
onBeforeUnmount(() => {
	instance.destroy();
	instance = null;
});
const syncHTML = () => {
	content.html = instance.txt.html();
	console.log(content.html);
};
</script>

<style></style>


================================================
FILE: detection-fontend/src/views/export.vue
================================================
<template>
    <div>
        <div class="container">
            <div class="handle-box">
                <el-button type="primary" @click="exportXlsx">导出Excel</el-button>
            </div>
            <el-table :data="tableData" border class="table" header-cell-class-name="table-header">
                <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
                <el-table-column prop="name" label="姓名"></el-table-column>
                <el-table-column prop="sno" label="学号"></el-table-column>
                <el-table-column prop="class" label="班级"></el-table-column>
                <el-table-column prop="age" label="年龄"></el-table-column>
                <el-table-column prop="sex" label="性别"></el-table-column>
            </el-table>
        </div>
    </div>
</template>

<script setup lang="ts" name="export">
import { ref } from 'vue';
import * as XLSX from 'xlsx';

interface TableItem {
    id: number;
    name: string;
    sno: string;
    class: string;
    age: string;
    sex: string;
}

const tableData = ref<TableItem[]>([]);
// 获取表格数据
const getData = () => {
    tableData.value = [
        {
            id: 1,
            name: '小明',
            sno: 'S001',
            class: '一班',
            age: '10',
            sex: '男',
        },
        {
            id: 2,
            name: '小红',
            sno: 'S002',
            class: '一班',
            age: '9',
            sex: '女',
        },
    ];
};
getData();

const list = [['序号', '姓名', '学号', '班级', '年龄', '性别']];
const exportXlsx = () => {
    tableData.value.map((item: any, i: number) => {
        const arr: any[] = [i + 1];
        arr.push(...[item.name, item.sno, item.class, item.age, item.sex]);
        list.push(arr);
    });
    let WorkSheet = XLSX.utils.aoa_to_sheet(list);
    let new_workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页');
    XLSX.writeFile(new_workbook, `表格.xlsx`);
};
</script>

<style scoped>
.handle-box {
    margin-bottom: 20px;
}

.handle-select {
    width: 120px;
}

.handle-input {
    width: 300px;
}
.table {
    width: 100%;
    font-size: 14px;
}
.red {
    color: #f56c6c;
}
.mr10 {
    margin-right: 10px;
}
.table-td-thumb {
    display: block;
    margin: auto;
    width: 40px;
    height: 40px;
}
</style>


================================================
FILE: detection-fontend/src/views/form.vue
================================================
<template>
    <div class="container">
        <div class="form-box">
            <el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
                <el-form-item label="表单名称" prop="name">
                    <el-input v-model="form.name"></el-input>
                </el-form-item>
                <el-form-item label="选择器" prop="region">
                    <el-select v-model="form.region" placeholder="请选择">
                        <el-option key="小明" label="小明" value="小明"></el-option>
                        <el-option key="小红" label="小红" value="小红"></el-option>
                        <el-option key="小白" label="小白" value="小白"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="日期时间">
                    <el-col :span="11">
                        <el-form-item prop="date1">
                            <el-date-picker
                                type="date"
                                placeholder="选择日期"
                                v-model="form.date1"
                                style="width: 100%"
                            ></el-date-picker>
                        </el-form-item>
                    </el-col>
                    <el-col class="line" :span="2">-</el-col>
                    <el-col :span="11">
                        <el-form-item prop="date2">
                            <el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%">
                            </el-time-picker>
                        </el-form-item>
                    </el-col>
                </el-form-item>
                <el-form-item label="城市级联" prop="options">
                    <el-cascader :options="options" v-model="form.options"></el-cascader>
                </el-form-item>
                <el-form-item label="选择开关" prop="delivery">
                    <el-switch v-model="form.delivery"></el-switch>
                </el-form-item>
                <el-form-item label="多选框" prop="type">
                    <el-checkbox-group v-model="form.type">
                        <el-checkbox label="小明" name="type"></el-checkbox>
                        <el-checkbox label="小红" name="type"></el-checkbox>
                        <el-checkbox label="小白" name="type"></el-checkbox>
                    </el-checkbox-group>
                </el-form-item>
                <el-form-item label="单选框" prop="resource">
                    <el-radio-group v-model="form.resource">
                        <el-radio label="小明"></el-radio>
                        <el-radio label="小红"></el-radio>
                        <el-radio label="小白"></el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item label="文本框" prop="desc">
                    <el-input type="textarea" rows="5" v-model="form.desc"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
                    <el-button @click="onReset(formRef)">重置表单</el-button>
                </el-form-item>
            </el-form>
        </div>
    </div>
</template>

<script setup lang="ts" name="baseform">
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';

const options = [
    {
        value: 'guangdong',
        label: '广东省',
        children: [
            {
                value: 'guangzhou',
                label: '广州市',
                children: [
                    {
                        value: 'tianhe',
                        label: '天河区',
                    },
                    {
                        value: 'haizhu',
                        label: '海珠区',
                    },
                ],
            },
            {
                value: 'dongguan',
                label: '东莞市',
                children: [
                    {
                        value: 'changan',
                        label: '长安镇',
                    },
                    {
                        value: 'humen',
                        label: '虎门镇',
                    },
                ],
            },
        ],
    },
    {
        value: 'hunan',
        label: '湖南省',
        children: [
            {
                value: 'changsha',
                label: '长沙市',
                children: [
                    {
                        value: 'yuelu',
                        label: '岳麓区',
                    },
                ],
            },
        ],
    },
];
const rules: FormRules = {
    name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
};
const formRef = ref<FormInstance>();
const form = reactive({
    name: '',
    region: '',
    date1: '',
    date2: '',
    delivery: true,
    type: ['小明'],
    resource: '小红',
    desc: '',
    options: [],
});
// 提交
const onSubmit = (formEl: FormInstance | undefined) => {
    // 表单校验
    if (!formEl) return;
    formEl.validate((valid) => {
        if (valid) {
            console.log(form);
            ElMessage.success('提交成功!');
        } else {
            return false;
        }
    });
};
// 重置
const onReset = (formEl: FormInstance | undefined) => {
    if (!formEl) return;
    formEl.resetFields();
};
</script>


================================================
FILE: detection-fontend/src/views/home.vue
================================================
<template>
	<v-header />
	<v-sidebar />
	<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
		<v-tags></v-tags>
		<div class="content">
			<router-view v-slot="{ Component }">
				<transition name="move" mode="out-in">
					<keep-alive :include="tags.nameList">
						<component :is="Component"></component>
					</keep-alive>
				</transition>
			</router-view>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useSidebarStore } from '../store/sidebar';
import { useTagsStore } from '../store/tags';
import vHeader from '../components/header.vue';
import vSidebar from '../components/sidebar.vue';
import vTags from '../components/tags.vue';

const sidebar = useSidebarStore();
const tags = useTagsStore();
</script>


================================================
FILE: detection-fontend/src/views/icon.vue
================================================
<template>
	<div class="container">
		<h2>使用方法</h2>
		<p style="line-height: 50px">
			直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{ iconList.length }}个图标)
		</p>
		<p class="example-p">
			<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i>
			<span>&lt;i class=&quot;el-icon-lx-redpacket_fill&quot;&gt;&lt;/i&gt;</span>
		</p>
		<p class="example-p">
			<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
			<span>&lt;i class=&quot;el-icon-lx-weibo&quot;&gt;&lt;/i&gt;</span>
		</p>
		<p class="example-p">
			<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i>
			<span>&lt;i class=&quot;el-icon-lx-emojifill&quot;&gt;&lt;/i&gt;</span>
		</p>
		<br />
		<h2>图标</h2>
		<div class="search-box">
			<el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input>
		</div>
		<ul>
			<li class="icon-li" v-for="(item, index) in list" :key="index">
				<div class="icon-li-content">
					<i :class="`el-icon-lx-${item}`"></i>
					<span>{{ item }}</span>
				</div>
			</li>
		</ul>
	</div>
</template>

<script setup lang="ts" name="icon">
import { computed, ref } from 'vue';

const iconList: Array<string> = [
	'attentionforbid',
	'attentionforbidfill',
	'attention',
	'attentionfill',
	'tag',
	'tagfill',
	'people',
	'peoplefill',
	'notice',
	'noticefill',
	'mobile',
	'mobilefill',
	'voice',
	'voicefill',
	'unlock',
	'lock',
	'home',
	'homefill',
	'delete',
	'deletefill',
	'notification',
	'notificationfill',
	'notificationforbidfill',
	'like',
	'likefill',
	'comment',
	'commentfill',
	'camera',
	'camerafill',
	'warn',
	'warnfill',
	'time',
	'timefill',
	'location',
	'locationfill',
	'favor',
	'favorfill',
	'skin',
	'skinfill',
	'news',
	'newsfill',
	'record',
	'recordfill',
	'emoji',
	'emojifill',
	'message',
	'messagefill',
	'goods',
	'goodsfill',
	'crown',
	'crownfill',
	'move',
	'add',
	'hot',
	'hotfill',
	'service',
	'servicefill',
	'present',
	'presentfill',
	'pic',
	'picfill',
	'rank',
	'rankfill',
	'male',
	'female',
	'down',
	'top',
	'recharge',
	'rechargefill',
	'forward',
	'forwardfill',
	'info',
	'infofill',
	'redpacket',
	'redpacket_fill',
	'roundadd',
	'roundaddfill',
	'friendadd',
	'friendaddfill',
	'cart',
	'cartfill',
	'more',
	'moreandroid',
	'back',
	'right',
	'shop',
	'shopfill',
	'question',
	'questionfill',
	'roundclose',
	'roundclosefill',
	'roundcheck',
	'roundcheckfill',
	'global',
	'mail',
	'punch',
	'exit',
	'upload',
	'read',
	'file',
	'link',
	'full',
	'group',
	'friend',
	'profile',
	'addressbook',
	'calendar',
	'text',
	'copy',
	'share',
	'wifi',
	'vipcard',
	'weibo',
	'remind',
	'refresh',
	'filter',
	'settings',
	'scan',
	'qrcode',
	'cascades',
	'apps',
	'sort',
	'searchlist',
	'search',
	'edit'
];
const keyword = ref('');
const list = computed(() => {
	return iconList.filter(item => {
		return item.indexOf(keyword.value) !== -1;
	});
});
</script>

<style scoped>
.example-p {
	height: 45px;
	display: flex;
	align-items: center;
}
.search-box {
	text-align: center;
	margin-top: 10px;
}
.search {
	width: 300px;
}
ul,
li {
	list-style: none;
}
.icon-li {
	display: inline-block;
	padding: 10px;
	width: 120px;
	height: 120px;
}
.icon-li-content {
	display: flex;
	height: 100%;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	cursor: pointer;
}
.icon-li-content i {
	font-size: 36px;
	color: #606266;
}
.icon-li-content span {
	margin-top: 10px;
	color: #787878;
}
</style>


================================================
FILE: detection-fontend/src/views/import.vue
================================================
<template>
    <div>
        <div class="container">
            <div class="handle-box">
                <el-upload
                    action="#"
                    :limit="1"
                    accept=".xlsx, .xls"
                    :show-file-list="false"
                    :before-upload="beforeUpload"
                    :http-request="handleMany"
                >
                    <el-button class="mr10" type="success">批量导入</el-button>
                </el-upload>
                <el-link href="/template.xlsx" target="_blank">下载模板</el-link>
            </div>
            <el-table :data="tableData" border class="table" header-cell-class-name="table-header">
                <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
                <el-table-column prop="name" label="姓名"></el-table-column>
                <el-table-column prop="sno" label="学号"></el-table-column>
                <el-table-column prop="class" label="班级"></el-table-column>
                <el-table-column prop="age" label="年龄"></el-table-column>
                <el-table-column prop="sex" label="性别"></el-table-column>
            </el-table>
        </div>
    </div>
</template>

<script setup lang="ts" name="import">
import { UploadProps } from 'element-plus';
import { ref, reactive } from 'vue';
import * as XLSX from 'xlsx';

interface TableItem {
    id: number;
    name: string;
    sno: string;
    class: string;
    age: string;
    sex: string;
}

const tableData = ref<TableItem[]>([]);
// 获取表格数据
const getData = () => {
    tableData.value = [
        {
            id: 1,
            name: '小明',
            sno: 'S001',
            class: '一班',
            age: '10',
            sex: '男',
        },
        {
            id: 2,
            name: '小红',
            sno: 'S002',
            class: '一班',
            age: '9',
            sex: '女',
        },
    ];
};
getData();

const importList = ref<any>([]);
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
    importList.value = await analysisExcel(rawFile);
    return true;
};
const analysisExcel = (file: any) => {
    return new Promise(function (resolve, reject) {
        const reader = new FileReader();
        reader.onload = function (e: any) {
            const data = e.target.result;
            let datajson = XLSX.read(data, {
                type: 'binary',
            });

            const sheetName = datajson.SheetNames[0];
            const result = XLSX.utils.sheet_to_json(datajson.Sheets[sheetName]);
            resolve(result);
        };
        reader.readAsBinaryString(file);
    });
};

const handleMany = async () => {
    // 把数据传给服务器后获取最新列表,这里只是示例,不做请求
    const list = importList.value.map((item: any, index: number) => {
        return {
            id: index,
            name: item['姓名'],
            sno: item['学号'],
            class: item['班级'],
            age: item['年龄'],
            sex: item['性别'],
        };
    });
    tableData.value.push(...list);
};
</script>

<style scoped>
.handle-box {
    display: flex;
    margin-bottom: 20px;
}

.table {
    width: 100%;
    font-size: 14px;
}
.mr10 {
    margin-right: 10px;
}
</style>


================================================
FILE: detection-fontend/src/views/login.vue
================================================
<template>
	<div class="login-wrap">
		<div class="ms-login">
			<div class="ms-title">后台管理系统</div>
			<el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
				<el-form-item prop="username">
					<el-input v-model="param.username" placeholder="username">
						<template #prepend>
							<el-button :icon="User"></el-button>
						</template>
					</el-input>
				</el-form-item>
				<el-form-item prop="password">
					<el-input
						type="password"
						placeholder="password"
						v-model="param.password"
						@keyup.enter="submitForm(login)"
					>
						<template #prepend>
							<el-button :icon="Lock"></el-button>
						</template>
					</el-input>
				</el-form-item>
				<div class="login-btn">
					<el-button type="primary" @click="submitForm(login)">登录</el-button>
				</div>
				<p class="login-tips">Tips : 用户名和密码随便填。</p>
			</el-form>
		</div>
	</div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useTagsStore } from '../store/tags';
import { usePermissStore } from '../store/permiss';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import { Lock, User } from '@element-plus/icons-vue';

interface LoginInfo {
	username: string;
	password: string;
}

const router = useRouter();
const param = reactive<LoginInfo>({
	username: 'admin',
	password: '123123'
});

const rules: FormRules = {
	username: [
		{
			required: true,
			message: '请输入用户名',
			trigger: 'blur'
		}
	],
	password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
};
const permiss = usePermissStore();
const login = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => {
	if (!formEl) return;
	formEl.validate((valid: boolean) => {
		if (valid) {
			ElMessage.success('登录成功');
			localStorage.setItem('ms_username', param.username);
			const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
			permiss.handleSet(keys);
			localStorage.setItem('ms_keys', JSON.stringify(keys));
			router.push('/');
		} else {
			ElMessage.error('登录成功');
			return false;
		}
	});
};

const tags = useTagsStore();
tags.clearTags();
</script>

<style scoped>
.login-wrap {
	position: relative;
	width: 100%;
	height: 100%;
	background-image: url(../assets/img/login-bg.jpg);
	background-size: 100%;
}
.ms-title {
	width: 100%;
	line-height: 50px;
	text-align: center;
	font-size: 20px;
	color: #fff;
	border-bottom: 1px solid #ddd;
}
.ms-login {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 350px;
	margin: -190px 0 0 -175px;
	border-radius: 5px;
	background: rgba(255, 255, 255, 0.3);
	overflow: hidden;
}
.ms-content {
	padding: 30px 30px;
}
.login-btn {
	text-align: center;
}
.login-btn button {
	width: 100%;
	height: 36px;
	margin-bottom: 10px;
}
.login-tips {
	font-size: 12px;
	line-height: 30px;
	color: #fff;
}
</style>


================================================
FILE: detection-fontend/src/views/markdown.vue
================================================
<template>
	<div class="container">
		<div class="plugins-tips">
			md-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地址:
			<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
		</div>
		<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
		<el-button type="primary">提交</el-button>
	</div>
</template>

<script setup lang="ts" name="md">
import { ref } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';

const text = ref('Hello Editor!');
const onUploadImg = (files: any) => {
	console.log(files);
};
</script>


================================================
FILE: detection-fontend/src/views/permission.vue
================================================
<template>
	<div class="container">
		<div class="plugins-tips">通过 v-permiss 自定义指令实现权限管理,使用非 admin 账号登录,可查看效果。</div>
		<div class="mgb20">
			<span class="label">角色:</span>
			<el-select v-model="role" @change="handleChange">
				<el-option label="超级管理员" value="admin"></el-option>
				<el-option label="普通用户" value="user"></el-option>
			</el-select>
		</div>
		<div class="mgb20 tree-wrapper">
			<el-tree
				ref="tree"
				:data="data"
				node-key="id"
				default-expand-all
				show-checkbox
				:default-checked-keys="checkedKeys"
			/>
		</div>
		<el-button type="primary" @click="onSubmit">保存权限</el-button>
	</div>
</template>

<script setup lang="ts" name="permission">
import { ref } from 'vue';
import { ElTree } from 'element-plus';
import { usePermissStore } from '../store/permiss';

const role = ref<string>('admin');

interface Tree {
	id: string;
	label: string;
	children?: Tree[];
}

const data: Tree[] = [
	{
		id: '1',
		label: '系统首页'
	},
	{
		id: '2',
		label: '基础表格',
		children: [
			{
				id: '15',
				label: '编辑'
			},
			{
				id: '16',
				label: '删除'
			}
		]
	},
	{
		id: '3',
		label: 'tab选项卡'
	},
	{
		id: '4',
		label: '表单相关',
		children: [
			{
				id: '5',
				label: '基本表单'
			},
			{
				id: '6',
				label: '文件上传'
			},
			{
				id: '7',
				label: '三级菜单',
				children: [
					{
						id: '8',
						label: '富文本编辑器'
					},
					{
						id: '9',
						label: 'markdown编辑器'
					}
				]
			}
		]
	},
	{
		id: '10',
		label: '自定义图标'
	},
	{
		id: '11',
		label: 'schart图表'
	},

	{
		id: '13',
		label: '权限管理'
	},
	{
		id: '14',
		label: '支持作者'
	}
];

const permiss = usePermissStore();

// 获取当前权限
const checkedKeys = ref<string[]>([]);
const getPremission = () => {
	// 请求接口返回权限
	checkedKeys.value = permiss.defaultList[role.value];
};
getPremission();

// 保存权限
const tree = ref<InstanceType<typeof ElTree>>();
const onSubmit = () => {
	// 获取选中的权限
	console.log(tree.value!.getCheckedKeys(false));
};

const handleChange = (val: string[]) => {
	tree.value!.setCheckedKeys(permiss.defaultList[role.value]);
};
</script>

<style scoped>
.tree-wrapper {
	max-width: 500px;
}
.label {
	font-size: 14px;
}
</style>


================================================
FILE: detection-fontend/src/views/table.vue
================================================
<template>
	<div>
		<div class="container">
			<div class="handle-box">
				<el-select v-model="query.address" placeholder="地址" class="handle-select mr10">
					<el-option key="1" label="广东省" value="广东省"></el-option>
					<el-option key="2" label="湖南省" value="湖南省"></el-option>
				</el-select>
				<el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
				<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
				<el-button type="primary" :icon="Plus">新增</el-button>
			</div>
			<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
				<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
				<el-table-column prop="name" label="用户名"></el-table-column>
				<el-table-column label="账户余额">
					<template #default="scope">¥{{ scope.row.money }}</template>
				</el-table-column>
				<el-table-column label="头像(查看大图)" align="center">
					<template #default="scope">
						<el-image
							class="table-td-thumb"
							:src="scope.row.thumb"
							:z-index="10"
							:preview-src-list="[scope.row.thumb]"
							preview-teleported
						>
						</el-image>
					</template>
				</el-table-column>
				<el-table-column prop="address" label="地址"></el-table-column>
				<el-table-column label="状态" align="center">
					<template #default="scope">
						<el-tag
							:type="scope.row.state === '成功' ? 'success' : scope.row.state === '失败' ? 'danger' : ''"
						>
							{{ scope.row.state }}
						</el-tag>
					</template>
				</el-table-column>

				<el-table-column prop="date" label="注册时间"></el-table-column>
				<el-table-column label="操作" width="220" align="center">
					<template #default="scope">
						<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)" v-permiss="15">
							编辑
						</el-button>
						<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index)" v-permiss="16">
							删除
						</el-button>
					</template>
				</el-table-column>
			</el-table>
			<div class="pagination">
				<el-pagination
					background
					layout="total, prev, pager, next"
					:current-page="query.pageIndex"
					:page-size="query.pageSize"
					:total="pageTotal"
					@current-change="handlePageChange"
				></el-pagination>
			</div>
		</div>

		<!-- 编辑弹出框 -->
		<el-dialog title="编辑" v-model="editVisible" width="30%">
			<el-form label-width="70px">
				<el-form-item label="用户名">
					<el-input v-model="form.name"></el-input>
				</el-form-item>
				<el-form-item label="地址">
					<el-input v-model="form.address"></el-input>
				</el-form-item>
			</el-form>
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="editVisible = false">取 消</el-button>
					<el-button type="primary" @click="saveEdit">确 定</el-button>
				</span>
			</template>
		</el-dialog>
	</div>
</template>

<script setup lang="ts" name="basetable">
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
import { fetchData } from '../api/index';

interface TableItem {
	id: number;
	name: string;
	money: string;
	state: string;
	date: string;
	address: string;
}

const query = reactive({
	address: '',
	name: '',
	pageIndex: 1,
	pageSize: 10
});
const tableData = ref<TableItem[]>([]);
const pageTotal = ref(0);
// 获取表格数据
const getData = () => {
	fetchData().then(res => {
		tableData.value = res.data.list;
		pageTotal.value = res.data.pageTotal || 50;
	});
};
getData();

// 查询操作
const handleSearch = () => {
	query.pageIndex = 1;
	getData();
};
// 分页导航
const handlePageChange = (val: number) => {
	query.pageIndex = val;
	getData();
};

// 删除操作
const handleDelete = (index: number) => {
	// 二次确认删除
	ElMessageBox.confirm('确定要删除吗?', '提示', {
		type: 'warning'
	})
		.then(() => {
			ElMessage.success('删除成功');
			tableData.value.splice(index, 1);
		})
		.catch(() => {});
};

// 表格编辑时弹窗和保存
const editVisible = ref(false);
let form = reactive({
	name: '',
	address: ''
});
let idx: number = -1;
const handleEdit = (index: number, row: any) => {
	idx = index;
	form.name = row.name;
	form.address = row.address;
	editVisible.value = true;
};
const saveEdit = () => {
	editVisible.value = false;
	ElMessage.success(`修改第 ${idx + 1} 行成功`);
	tableData.value[idx].name = form.name;
	tableData.value[idx].address = form.address;
};
</script>

<style scoped>
.handle-box {
	margin-bottom: 20px;
}

.handle-select {
	width: 120px;
}

.handle-input {
	width: 300px;
}
.table {
	width: 100%;
	font-size: 14px;
}
.red {
	color: #F56C6C;
}
.mr10 {
	margin-right: 10px;
}
.table-td-thumb {
	display: block;
	margin: auto;
	width: 40px;
	height: 40px;
}
</style>


================================================
FILE: detection-fontend/src/views/tabs.vue
================================================
<template>
	<div class="container">
		<el-tabs v-model="message">
			<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
				<el-table :data="state.unread" :show-header="false" style="width: 100%">
					<el-table-column>
						<template #default="scope">
							<span class="message-title">{{ scope.row.title }}</span>
						</template>
					</el-table-column>
					<el-table-column prop="date" width="180"></el-table-column>
					<el-table-column width="120">
						<template #default="scope">
							<el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
						</template>
					</el-table-column>
				</el-table>
				<div class="handle-row">
					<el-button type="primary">全部标为已读</el-button>
				</div>
			</el-tab-pane>
			<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
				<template v-if="message === 'second'">
					<el-table :data="state.read" :show-header="false" style="width: 100%">
						<el-table-column>
							<template #default="scope">
								<span class="message-title">{{ scope.row.title }}</span>
							</template>
						</el-table-column>
						<el-table-column prop="date" width="150"></el-table-column>
						<el-table-column width="120">
							<template #default="scope">
								<el-button type="danger" @click="handleDel(scope.$index)">删除</el-button>
							</template>
						</el-table-column>
					</el-table>
					<div class="handle-row">
						<el-button type="danger">删除全部</el-button>
					</div>
				</template>
			</el-tab-pane>
			<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
				<template v-if="message === 'third'">
					<el-table :data="state.recycle" :show-header="false" style="width: 100%">
						<el-table-column>
							<template #default="scope">
								<span class="message-title">{{ scope.row.title }}</span>
							</template>
						</el-table-column>
						<el-table-column prop="date" width="150"></el-table-column>
						<el-table-column width="120">
							<template #default="scope">
								<el-button @click="handleRestore(scope.$index)">还原</el-button>
							</template>
						</el-table-column>
					</el-table>
					<div class="handle-row">
						<el-button type="danger">清空回收站</el-button>
					</div>
				</template>
			</el-tab-pane>
		</el-tabs>
	</div>
</template>

<script setup lang="ts" name="tabs">
import { ref, reactive } from 'vue';

const message = ref('first');
const state = reactive({
	unread: [
		{
			date: '2018-04-19 20:00:00',
			title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
		},
		{
			date: '2018-04-19 21:00:00',
			title: '今晚12点整发大红包,先到先得'
		}
	],
	read: [
		{
			date: '2018-04-19 20:00:00',
			title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
		}
	],
	recycle: [
		{
			date: '2018-04-19 20:00:00',
			title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
		}
	]
});

const handleRead = (index: number) => {
	const item = state.unread.splice(index, 1);
	state.read = item.concat(state.read);
};
const handleDel = (index: number) => {
	const item = state.read.splice(index, 1);
	state.recycle = item.concat(state.recycle);
};
const handleRestore = (index: number) => {
	const item = state.recycle.splice(index, 1);
	state.read = item.concat(state.read);
};
</script>

<style>
.message-title {
	cursor: pointer;
}
.handle-row {
	margin-top: 30px;
}
</style>


================================================
FILE: detection-fontend/src/views/upload.vue
================================================
<template>
    <div class="container">
        <div class="content-title">支持拖拽</div>
        <div class="plugins-tips">
            Element Plus自带上传组件。 访问地址:
            <a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
        </div>
        <el-upload
            class="upload-demo"
            drag
            action="http://jsonplaceholder.typicode.com/api/posts/"
            multiple
            :on-change="handle"
        >
            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
            <div class="el-upload__text">
                将文件拖到此处,或
                <em>点击上传</em>
            </div>
        </el-upload>

        <div class="content-title">支持裁剪</div>
        <div class="plugins-tips">
            vue-cropperjs:一个封装了 cropperjs 的 Vue 组件。 访问地址:
            <a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a>。 示例请查看
            <router-link to="/user">个人中心</router-link>
        </div>
    </div>
</template>

<script setup lang="ts">
const handle = (rawFile: any) => {
    console.log(rawFile);
};
</script>

<style scoped>
.content-title {
    font-weight: 400;
    line-height: 50px;
    margin: 10px 0;
    font-size: 22px;
    color: #1f2f3d;
}
.upload-demo {
    width: 360px;
}
</style>


================================================
FILE: detection-fontend/src/views/user.vue
================================================
<template>
	<div>
		<el-row :gutter="20">
			<el-col :span="12">
				<el-card shadow="hover">
					<template #header>
						<div class="clearfix">
							<span>基础信息</span>
						</div>
					</template>
					<div class="info">
						<div class="info-image" @click="showDialog">
							<el-avatar :size="100" :src="avatarImg" />
							<span class="info-edit">
								<i class="el-icon-lx-camerafill"></i>
							</span>
						</div>
						<div class="info-name">{{ name }}</div>
						<div class="info-desc">SCAU_DS</div>
					</div>
				</el-card>
			</el-col>
			<el-col :span="12">
				<el-card shadow="hover">
					<template #header>
						<div class="clearfix">
							<span>账户编辑</span>
						</div>
					</template>
					<el-form label-width="90px">
						<el-form-item label="用户名:"> {{ name }} </el-form-item>
						<el-form-item label="旧密码:">
							<el-input type="password" v-model="form.old"></el-input>
						</el-form-item>
						<el-form-item label="新密码:">
							<el-input type="password" v-model="form.new"></el-input>
						</el-form-item>
						<el-form-item label="个人简介:">
							<el-input v-model="form.desc"></el-input>
						</el-form-item>
						<el-form-item>
							<el-button type="primary" @click="onSubmit">保存</el-button>
						</el-form-item>
					</el-form>
				</el-card>
			</el-col>
		</el-row>
		<el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
			<vue-cropper
				ref="cropper"
				:src="imgSrc"
				:ready="cropImage"
				:zoom="cropImage"
				:cropmove="cropImage"
				style="width: 100%; height: 400px"
			></vue-cropper>

			<template #footer>
				<span class="dialog-footer">
					<el-button class="crop-demo-btn" type="primary"
						>选择图片
						<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
					</el-button>
					<el-button type="primary" @click="saveAvatar">上传并保存</el-button>
				</span>
			</template>
		</el-dialog>
	</div>
</template>

<script setup lang="ts" name="user">
import { reactive, ref } from 'vue';
import VueCropper from 'vue-cropperjs';
import 'cropperjs/dist/cropper.css';
// import avatar from '../assets/img/img.jpg';
import avatar from '../assets/img/img.png';

const name = localStorage.getItem('ms_username');
const form = reactive({
	old: '',
	new: '',
	// desc: '不可能!我的代码怎么可能会有bug!'
  desc: '',
});
const onSubmit = () => {};

const avatarImg = ref(avatar);
const imgSrc = ref('');
const cropImg = ref('');
const dialogVisible = ref(false);
const cropper: any = ref();

const showDialog = () => {
	dialogVisible.value = true;
	imgSrc.value = avatarImg.value;
};

const setImage = (e: any) => {
	const file = e.target.files[0];
	if (!file.type.includes('image/')) {
		return;
	}
	const reader = new FileReader();
	reader.onload = (event: any) => {
		dialogVisible.value = true;
		imgSrc.value = event.target.result;
		cropper.value && cropper.value.replace(event.target.result);
	};
	reader.readAsDataURL(file);
};

const cropImage = () => {
	cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
};

const saveAvatar = () => {
	avatarImg.value = cropImg.value;
	dialogVisible.value = false;
};
</script>

<style scoped>
.info {
	text-align: center;
	padding: 35px 0;
}
.info-image {
	position: relative;
	margin: auto;
	width: 100px;
	height: 100px;
	background: #f8f8f8;
	border: 1px solid #eee;
	border-radius: 50px;
	overflow: hidden;
}

.info-edit {
	display: flex;
	justify-content: center;
	align-items: center;
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	background: rgba(0, 0, 0, 0.5);
	opacity: 0;
	transition: opacity 0.3s ease;
}
.info-edit i {
	color: #eee;
	font-size: 25px;
}
.info-image:hover .info-edit {
	opacity: 1;
}
.info-name {
	margin: 15px 0 10px;
	font-size: 24px;
	font-weight: 500;
	color: #262626;
}
.crop-demo-btn {
	position: relative;
}
.crop-input {
	position: absolute;
	width: 100px;
	height: 40px;
	left: 0;
	top: 0;
	opacity: 0;
	cursor: pointer;
}
</style>


================================================
FILE: detection-fontend/src/vite-env.d.ts
================================================
/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

declare module 'vue-schart';
declare module 'vue-cropperjs';

================================================
FILE: detection-fontend/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}


================================================
FILE: detection-fontend/tsconfig.node.json
================================================
{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}


================================================
FILE: detection-fontend/vite.config.ts
================================================
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import VueSetupExtend from 'vite-plugin-vue-setup-extend';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
	base: './',
	plugins: [
		vue(),
		VueSetupExtend(),
		AutoImport({
			resolvers: [ElementPlusResolver()]
		}),
		Components({
			resolvers: [ElementPlusResolver()]
		})
	],
	optimizeDeps: {
		include: ['schart.js']
	}
});
Download .txt
gitextract_4lvw2n3m/

├── README.md
├── detection-backend/
│   ├── api.py
│   ├── app.py
│   ├── config.py
│   ├── demo.http
│   ├── extension.py
│   ├── modelPredict.py
│   ├── models.py
│   └── weights/
│       ├── yolov5nu_coco.pt
│       ├── yolov5su_coco.pt
│       ├── yolov8l_coco.pt
│       ├── yolov8m_coco.pt
│       ├── yolov8n_coco.pt
│       └── yolov8s_coco.pt
└── detection-fontend/
    ├── LICENSE
    ├── README.md
    ├── README_EN.md
    ├── auto-imports.d.ts
    ├── components.d.ts
    ├── index.html
    ├── package.json
    ├── public/
    │   ├── table.json
    │   └── template.xlsx
    ├── src/
    │   ├── App.vue
    │   ├── api/
    │   │   └── index.ts
    │   ├── assets/
    │   │   └── css/
    │   │       ├── color-dark.css
    │   │       ├── icon.css
    │   │       └── main.css
    │   ├── components/
    │   │   ├── header.vue
    │   │   ├── sidebar.vue
    │   │   └── tags.vue
    │   ├── main.ts
    │   ├── router/
    │   │   └── index.ts
    │   ├── store/
    │   │   ├── permiss.ts
    │   │   ├── sidebar.ts
    │   │   └── tags.ts
    │   ├── utils/
    │   │   └── request.ts
    │   ├── viewDetect/
    │   │   ├── ResultVue.vue
    │   │   ├── detectVue.vue
    │   │   └── uploadFile.vue
    │   ├── views/
    │   │   ├── 403.vue
    │   │   ├── 404.vue
    │   │   ├── charts.vue
    │   │   ├── dashboard.vue
    │   │   ├── donate.vue
    │   │   ├── editor.vue
    │   │   ├── export.vue
    │   │   ├── form.vue
    │   │   ├── home.vue
    │   │   ├── icon.vue
    │   │   ├── import.vue
    │   │   ├── login.vue
    │   │   ├── markdown.vue
    │   │   ├── permission.vue
    │   │   ├── table.vue
    │   │   ├── tabs.vue
    │   │   ├── upload.vue
    │   │   └── user.vue
    │   └── vite-env.d.ts
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts
Download .txt
SYMBOL INDEX (66 symbols across 8 files)

FILE: detection-backend/api.py
  class FileApi (line 11) | class FileApi(MethodView):
    method __init__ (line 16) | def __init__(self):
    method get (line 19) | def get(self, fid):
    method delete (line 66) | def delete(self, fid):
    method post (line 88) | def post(self):  # 添加数据
  class WModelApi (line 124) | class WModelApi(MethodView):
    method __init__ (line 129) | def __init__(self):
    method get (line 132) | def get(self, wid):
    method delete (line 173) | def delete(self, wid):
    method put (line 206) | def put(self, wid):
    method post (line 232) | def post(self):  # 添加数据
  class ResultApi (line 266) | class ResultApi(MethodView):
    method __init__ (line 271) | def __init__(self):
    method get (line 274) | def get(self, rid):
    method delete (line 323) | def delete(self, rid):
    method put (line 361) | def put(self, rid):
    method post (line 382) | def post(self):  # 添加数据
  class ObjApi (line 430) | class ObjApi(MethodView):
    method __init__ (line 435) | def __init__(self):
    method get (line 438) | def get(self, rid):
    method delete (line 474) | def delete(self, rid):
    method post (line 500) | def post(self):  # 添加数据
  class PageApi (line 559) | class PageApi(MethodView):  # 分页接口
    method __init__ (line 560) | def __init__(self):
    method get (line 563) | def get(self):
  class ObjectPageApi (line 641) | class ObjectPageApi(MethodView):
    method __init__ (line 642) | def __init__(self):
    method get (line 645) | def get(self, rid):
  class NumApi (line 687) | class NumApi(MethodView):  # 分页显示数目的接口
    method __init__ (line 688) | def __init__(self):
    method get (line 692) | def get(self):
  class DeblurSearchApi (line 731) | class DeblurSearchApi(MethodView):  # 分页实现模糊查找的接口
    method __init__ (line 732) | def __init__(self):
    method get (line 735) | def get(self):
  class DeblurSearchNumApi (line 818) | class DeblurSearchNumApi(MethodView):
    method __init__ (line 819) | def __init__(self):
    method get (line 823) | def get(self):

FILE: detection-backend/app.py
  function show_img (line 119) | def show_img(name):
  function upload_load (line 134) | def upload_load():
  function get_url_pic (line 175) | def get_url_pic():
  function show_model_detail (line 236) | def show_model_detail():
  function show_current_model (line 266) | def show_current_model():
  function switch_model (line 287) | def switch_model():
  function save_model_cache (line 335) | def save_model_cache():
  function show_result_img (line 367) | def show_result_img(name):
  function detection (line 387) | def detection():
  function delete_result (line 478) | def delete_result():
  function delete_file (line 518) | def delete_file():
  function init (line 575) | def init():

FILE: detection-backend/models.py
  class File (line 15) | class File(db.Model):
    method init_db (line 26) | def init_db():
  class WModel (line 60) | class WModel(db.Model):
    method init_db (line 68) | def init_db():
  class Result (line 89) | class Result(db.Model):
  class Object (line 101) | class Object(db.Model):

FILE: detection-fontend/components.d.ts
  type GlobalComponents (line 9) | interface GlobalComponents {

FILE: detection-fontend/src/main.ts
  method mounted (line 27) | mounted(el, binding) {

FILE: detection-fontend/src/store/permiss.ts
  type ObjectList (line 3) | interface ObjectList {
  method handleSet (line 19) | handleSet(val: string[]) {

FILE: detection-fontend/src/store/sidebar.ts
  method handleCollapse (line 11) | handleCollapse() {

FILE: detection-fontend/src/store/tags.ts
  type ListItem (line 3) | interface ListItem {
  method delTagsItem (line 24) | delTagsItem(index: number) {
  method setTagsItem (line 27) | setTagsItem(data: ListItem) {
  method clearTags (line 30) | clearTags() {
  method closeTagsOther (line 33) | closeTagsOther(data: ListItem[]) {
  method closeCurrentTag (line 36) | closeCurrentTag(data: any) {
Condensed preview — 62 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (220K chars).
[
  {
    "path": "README.md",
    "chars": 1739,
    "preview": "# Yolov8-flask-vue(本科毕设)\n\n这是一个基于ultralytics的一个部署到flask后端,然后vue作为前端所展示的一个通用的Yolo目标检测的展示页面,其实本质上类似于有着web页面外观的本地exe项目(因为数据库"
  },
  {
    "path": "detection-backend/api.py",
    "chars": 25463,
    "preview": "import os\nimport shutil\n\nfrom flask.views import MethodView, request\nfrom models import File, WModel, Object, Result\nfro"
  },
  {
    "path": "detection-backend/app.py",
    "chars": 14904,
    "preview": "import datetime\nimport random\nimport time\nfrom flask import Flask, request, make_response\nfrom ultralytics import YOLO\ni"
  },
  {
    "path": "detection-backend/config.py",
    "chars": 544,
    "preview": "save_path = '.\\\\pic\\\\save\\\\'\ncache_save_model_path = '.\\\\save\\\\cache.pt'\nresult_path = '.\\\\pic\\\\result\\\\'\nweights_path ="
  },
  {
    "path": "detection-backend/demo.http",
    "chars": 6434,
    "preview": "###\nGET http://127.0.0.1:5000/files/2\n\n###\nGET http://127.0.0.1:5000/wmodel\n\n### 下面是从切换模型,检测开始记录数据,查询数据,删除数据的步骤\n###\n###\n"
  },
  {
    "path": "detection-backend/extension.py",
    "chars": 135,
    "preview": "from flask_sqlalchemy import SQLAlchemy\nfrom flask_cors import CORS\n\n\ndb = SQLAlchemy()\ncors = CORS(resources={r'/*':{'o"
  },
  {
    "path": "detection-backend/modelPredict.py",
    "chars": 179,
    "preview": "from ultralytics import YOLO\n\nmodel_path = './weights/yolov8n_bdd.pt'\nvideo_path = 'video/save/2.mp4'\n\nmodel = YOLO(mode"
  },
  {
    "path": "detection-backend/models.py",
    "chars": 3853,
    "preview": "import os\n\nfrom sqlalchemy import ForeignKey\nfrom config import cache_save_path\nfrom PIL import Image\n\nfrom extension im"
  },
  {
    "path": "detection-fontend/LICENSE",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) 2016-2023 vue-manage-system\n\nPermission is hereby granted, free of charge, to any person obta"
  },
  {
    "path": "detection-fontend/README.md",
    "chars": 3252,
    "preview": "# vue-manage-system\n\n<a href=\"https://github.com/vuejs/vue\">\n    <img src=\"https://img.shields.io/badge/vue-3.1.2-bright"
  },
  {
    "path": "detection-fontend/README_EN.md",
    "chars": 3260,
    "preview": "# vue-manage-system\n\n<a href=\"https://github.com/vuejs/vue\">\n    <img src=\"https://img.shields.io/badge/vue-2.6.10-brigh"
  },
  {
    "path": "detection-fontend/auto-imports.d.ts",
    "chars": 69,
    "preview": "// Generated by 'unplugin-auto-import'\nexport {}\ndeclare global {\n\n}\n"
  },
  {
    "path": "detection-fontend/components.d.ts",
    "chars": 2983,
    "preview": "// generated by unplugin-vue-components\n// We suggest you to commit this file into source control\n// Read more: https://"
  },
  {
    "path": "detection-fontend/index.html",
    "chars": 653,
    "preview": "<!DOCTYPE html>\n<html lang=\"\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n"
  },
  {
    "path": "detection-fontend/package.json",
    "chars": 839,
    "preview": "{\n\t\"name\": \"vue-manage-system\",\n\t\"version\": \"5.3.0\",\n\t\"private\": true,\n\t\"scripts\": {\n\t\t\"dev\": \"vite\",\n\t\t\"build\": \"vue-ts"
  },
  {
    "path": "detection-fontend/public/table.json",
    "chars": 1074,
    "preview": "{\n    \"list\": [{\n            \"id\": 1,\n            \"name\": \"张三\",\n            \"money\": 123,\n            \"address\": \"广东省东莞市"
  },
  {
    "path": "detection-fontend/src/App.vue",
    "chars": 341,
    "preview": "<template>\n    <el-config-provider :locale=\"zhCn\">\n        <router-view />\n    </el-config-provider>\n</template>\n\n<scrip"
  },
  {
    "path": "detection-fontend/src/api/index.ts",
    "chars": 157,
    "preview": "import request from '../utils/request';\n\nexport const fetchData = () => {\n    return request({\n        url: './table.jso"
  },
  {
    "path": "detection-fontend/src/assets/css/color-dark.css",
    "chars": 337,
    "preview": ".header{\n    background-color: #242f42;\n}\n.login-wrap{\n    background: #324157;\n}\n.plugins-tips{\n    background: #eef1f6"
  },
  {
    "path": "detection-fontend/src/assets/css/icon.css",
    "chars": 88,
    "preview": "[class*=\" el-icon-lx\"],\n[class^=el-icon-lx] {\n    font-family: lx-iconfont !important;\n}"
  },
  {
    "path": "detection-fontend/src/assets/css/main.css",
    "chars": 2081,
    "preview": "* {\n    margin: 0;\n    padding: 0;\n}\n\nhtml,\nbody,\n#app,\n.wrapper {\n    width: 100%;\n    height: 100%;\n    overflow: hidd"
  },
  {
    "path": "detection-fontend/src/components/header.vue",
    "chars": 3414,
    "preview": "<template>\n\t<div class=\"header\">\n\t\t<!-- 折叠按钮 -->\n\t\t<div class=\"collapse-btn\" @click=\"collapseChage\">\n\t\t\t<el-icon v-if=\"s"
  },
  {
    "path": "detection-fontend/src/components/sidebar.vue",
    "chars": 5527,
    "preview": "<template>\n    <div class=\"sidebar\">\n        <el-menu\n            class=\"sidebar-el-menu\"\n            :default-active=\"o"
  },
  {
    "path": "detection-fontend/src/components/tags.vue",
    "chars": 3364,
    "preview": "<template>\n\t<div class=\"tags\" v-if=\"tags.show\">\n\t\t<ul>\n\t\t\t<li\n\t\t\t\tclass=\"tags-li\"\n\t\t\t\tv-for=\"(item, index) in tags.list\""
  },
  {
    "path": "detection-fontend/src/main.ts",
    "chars": 900,
    "preview": "import { createApp } from 'vue';\nimport { createPinia } from 'pinia';\nimport * as ElementPlusIconsVue from '@element-plu"
  },
  {
    "path": "detection-fontend/src/router/index.ts",
    "chars": 6530,
    "preview": "import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';\nimport { usePermissStore } from '../sto"
  },
  {
    "path": "detection-fontend/src/store/permiss.ts",
    "chars": 534,
    "preview": "import { defineStore } from 'pinia';\n\ninterface ObjectList {\n\t[key: string]: string[];\n}\n\nexport const usePermissStore ="
  },
  {
    "path": "detection-fontend/src/store/sidebar.ts",
    "chars": 242,
    "preview": "import { defineStore } from 'pinia';\n\nexport const useSidebarStore = defineStore('sidebar', {\n\tstate: () => {\n\t\treturn {"
  },
  {
    "path": "detection-fontend/src/store/tags.ts",
    "chars": 1033,
    "preview": "import { defineStore } from 'pinia';\n\ninterface ListItem {\n\tname: string;\n\tpath: string;\n\ttitle: string;\n}\n\nexport const"
  },
  {
    "path": "detection-fontend/src/utils/request.ts",
    "chars": 688,
    "preview": "import axios, {AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig} from 'axios';\n\nconst service:AxiosInstance "
  },
  {
    "path": "detection-fontend/src/viewDetect/ResultVue.vue",
    "chars": 21443,
    "preview": "<template>\n  <div class=\"container\">\n    <div>\n      <div class=\"content-title\">模型信息</div>\n      <div class=\"handle-box\""
  },
  {
    "path": "detection-fontend/src/viewDetect/detectVue.vue",
    "chars": 20288,
    "preview": "<template>\n  <div class=\"container\">\n    <div class=\"content-title\">模型本地上传</div>\n    <div class=\"plugins-tips\">\n      <!"
  },
  {
    "path": "detection-fontend/src/viewDetect/uploadFile.vue",
    "chars": 9180,
    "preview": "<template>\n  <div class=\"container\">\n\n    <div>\n      <div class=\"content-title\">文件本地上传</div>\n      <div class=\"plugins-"
  },
  {
    "path": "detection-fontend/src/views/403.vue",
    "chars": 1026,
    "preview": "<template>\n\t<div class=\"error-page\">\n\t\t<div class=\"error-code\">4<span>0</span>3</div>\n\t\t<div class=\"error-desc\">啊哦~ 你没有权"
  },
  {
    "path": "detection-fontend/src/views/404.vue",
    "chars": 1025,
    "preview": "<template>\n\t<div class=\"error-page\">\n\t\t<div class=\"error-code\">4<span>0</span>4</div>\n\t\t<div class=\"error-desc\">啊哦~ 你所访问"
  },
  {
    "path": "detection-fontend/src/views/charts.vue",
    "chars": 2341,
    "preview": "<template>\n\t<div class=\"container\">\n\t\t<div class=\"plugins-tips\">\n\t\t\tvue-schart:vue.js封装sChart.js的图表组件。 访问地址:\n\t\t\t<a href="
  },
  {
    "path": "detection-fontend/src/views/dashboard.vue",
    "chars": 5928,
    "preview": "<template>\n\t<div>\n\t\t<el-row :gutter=\"20\">\n\t\t\t<el-col :span=\"8\">\n\t\t\t\t<el-card shadow=\"hover\" class=\"mgb20\" style=\"height:"
  },
  {
    "path": "detection-fontend/src/views/donate.vue",
    "chars": 314,
    "preview": "<template>\n\t<div class=\"container\">\n\t\t<div class=\"plugins-tips\">\n\t\t\t如果该框架对你有帮助,那就请作者喝杯饮料吧!<el-icon><ColdDrink /></el-ico"
  },
  {
    "path": "detection-fontend/src/views/editor.vue",
    "chars": 858,
    "preview": "<template>\n\t<div class=\"container\">\n\t\t<div class=\"plugins-tips\">\n\t\t\twangEditor:轻量级 web 富文本编辑器,配置方便,使用简单。 访问地址:\n\t\t\t<a hre"
  },
  {
    "path": "detection-fontend/src/views/export.vue",
    "chars": 2332,
    "preview": "<template>\n    <div>\n        <div class=\"container\">\n            <div class=\"handle-box\">\n                <el-button typ"
  },
  {
    "path": "detection-fontend/src/views/form.vue",
    "chars": 5375,
    "preview": "<template>\n    <div class=\"container\">\n        <div class=\"form-box\">\n            <el-form ref=\"formRef\" :rules=\"rules\" "
  },
  {
    "path": "detection-fontend/src/views/home.vue",
    "chars": 758,
    "preview": "<template>\n\t<v-header />\n\t<v-sidebar />\n\t<div class=\"content-box\" :class=\"{ 'content-collapse': sidebar.collapse }\">\n\t\t<"
  },
  {
    "path": "detection-fontend/src/views/icon.vue",
    "chars": 3477,
    "preview": "<template>\n\t<div class=\"container\">\n\t\t<h2>使用方法</h2>\n\t\t<p style=\"line-height: 50px\">\n\t\t\t直接通过设置类名为 el-icon-lx-iconName 来使用"
  },
  {
    "path": "detection-fontend/src/views/import.vue",
    "chars": 3212,
    "preview": "<template>\n    <div>\n        <div class=\"container\">\n            <div class=\"handle-box\">\n                <el-upload\n   "
  },
  {
    "path": "detection-fontend/src/views/login.vue",
    "chars": 2936,
    "preview": "<template>\n\t<div class=\"login-wrap\">\n\t\t<div class=\"ms-login\">\n\t\t\t<div class=\"ms-title\">后台管理系统</div>\n\t\t\t<el-form :model=\""
  },
  {
    "path": "detection-fontend/src/views/markdown.vue",
    "chars": 600,
    "preview": "<template>\n\t<div class=\"container\">\n\t\t<div class=\"plugins-tips\">\n\t\t\tmd-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地址:\n"
  },
  {
    "path": "detection-fontend/src/views/permission.vue",
    "chars": 2148,
    "preview": "<template>\n\t<div class=\"container\">\n\t\t<div class=\"plugins-tips\">通过 v-permiss 自定义指令实现权限管理,使用非 admin 账号登录,可查看效果。</div>\n\t\t<"
  },
  {
    "path": "detection-fontend/src/views/table.vue",
    "chars": 4753,
    "preview": "<template>\n\t<div>\n\t\t<div class=\"container\">\n\t\t\t<div class=\"handle-box\">\n\t\t\t\t<el-select v-model=\"query.address\" placehold"
  },
  {
    "path": "detection-fontend/src/views/tabs.vue",
    "chars": 3276,
    "preview": "<template>\n\t<div class=\"container\">\n\t\t<el-tabs v-model=\"message\">\n\t\t\t<el-tab-pane :label=\"`未读消息(${state.unread.length})`"
  },
  {
    "path": "detection-fontend/src/views/upload.vue",
    "chars": 1320,
    "preview": "<template>\n    <div class=\"container\">\n        <div class=\"content-title\">支持拖拽</div>\n        <div class=\"plugins-tips\">\n"
  },
  {
    "path": "detection-fontend/src/views/user.vue",
    "chars": 3953,
    "preview": "<template>\n\t<div>\n\t\t<el-row :gutter=\"20\">\n\t\t\t<el-col :span=\"12\">\n\t\t\t\t<el-card shadow=\"hover\">\n\t\t\t\t\t<template #header>\n\t\t"
  },
  {
    "path": "detection-fontend/src/vite-env.d.ts",
    "chars": 247,
    "preview": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n  import type { DefineComponent } from 'vue'\n  const com"
  },
  {
    "path": "detection-fontend/tsconfig.json",
    "chars": 474,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"modul"
  },
  {
    "path": "detection-fontend/tsconfig.node.json",
    "chars": 184,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSynthe"
  },
  {
    "path": "detection-fontend/vite.config.ts",
    "chars": 568,
    "preview": "import { defineConfig } from 'vite';\nimport vue from '@vitejs/plugin-vue';\nimport VueSetupExtend from 'vite-plugin-vue-s"
  }
]

// ... and 7 more files (download for full content)

About this extraction

This page contains the full source code of the datar5/yolov8-flask-vue-deploy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 62 files (172.8 MB), approximately 54.1k tokens, and a symbol index with 66 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!