Full Code of itimor/one-workflow for AI

master 805035def06b cached
242 files
993.9 KB
282.2k tokens
409 symbols
1 requests
Download .txt
Showing preview only (1,080K chars total). Download the full file or copy to clipboard to get everything.
Repository: itimor/one-workflow
Branch: master
Commit: 805035def06b
Files: 242
Total size: 993.9 KB

Directory structure:
gitextract_nol__jqe/

├── .gitignore
├── 500.html
├── README.md
├── backend/
│   ├── __init__.py
│   ├── celery.service
│   ├── common/
│   │   ├── JSONRenderer.py
│   │   ├── __init__.py
│   │   ├── dispath.py
│   │   ├── django.py
│   │   ├── exceptions.py
│   │   ├── models.py
│   │   ├── pagination.py
│   │   ├── status.py
│   │   └── views.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── celery.py
│   │   ├── settings/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   ├── dev.py
│   │   │   ├── mac.py
│   │   │   └── prod.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── dev_requirements.txt
│   ├── init.sh
│   ├── manage.py
│   ├── notices/
│   │   ├── __init__.py
│   │   ├── management/
│   │   │   ├── __init__.py
│   │   │   └── commands/
│   │   │       ├── __init__.py
│   │   │       └── init_notice.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── oms.ini
│   ├── oms.service
│   ├── requirements.txt
│   ├── systems/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── management/
│   │   │   ├── __init__.py
│   │   │   └── commands/
│   │   │       ├── __init__.py
│   │   │       └── init_sys.py
│   │   ├── menus.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── permissions.py
│   │   ├── serializers.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── tickets/
│   │   ├── __init__.py
│   │   ├── filters.py
│   │   ├── management/
│   │   │   ├── __init__.py
│   │   │   └── commands/
│   │   │       ├── __init__.py
│   │   │       └── init_ticket.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── tools/
│   │   ├── __init__.py
│   │   ├── filesize.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── storage.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── get_realip.py
│   │   ├── index.py
│   │   ├── mysql.py
│   │   ├── sendmail.py
│   │   ├── sendskype.py
│   │   ├── test.py
│   │   ├── time.py
│   │   └── verifys.py
│   └── workflows/
│       ├── __init__.py
│       ├── admin.py
│       ├── management/
│       │   ├── __init__.py
│       │   └── commands/
│       │       ├── __init__.py
│       │       ├── init_leave.py
│       │       └── init_wf.py
│       ├── migrations/
│       │   ├── 0001_initial.py
│       │   └── __init__.py
│       ├── models.py
│       ├── serializers.py
│       ├── urls.py
│       └── views.py
├── frontend/
│   ├── .editorconfig
│   ├── .eslintignore
│   ├── .eslintrc.js
│   ├── .travis.yml
│   ├── LICENSE
│   ├── babel.config.js
│   ├── build/
│   │   └── index.js
│   ├── jest.config.js
│   ├── jsconfig.json
│   ├── package.json
│   ├── plop-templates/
│   │   ├── component/
│   │   │   ├── index.hbs
│   │   │   └── prompt.js
│   │   ├── store/
│   │   │   ├── index.hbs
│   │   │   └── prompt.js
│   │   ├── utils.js
│   │   └── view/
│   │       ├── index.hbs
│   │       └── prompt.js
│   ├── plopfile.js
│   ├── postcss.config.js
│   ├── public/
│   │   └── index.html
│   ├── src/
│   │   ├── App.vue
│   │   ├── api/
│   │   │   ├── all.js
│   │   │   ├── auths.js
│   │   │   └── common.js
│   │   ├── assets/
│   │   │   └── custom-theme/
│   │   │       └── index.css
│   │   ├── components/
│   │   │   ├── Breadcrumb/
│   │   │   │   └── index.vue
│   │   │   ├── Charts/
│   │   │   │   ├── keyboard.vue
│   │   │   │   ├── lineMarker.vue
│   │   │   │   ├── mixChart.vue
│   │   │   │   └── mixins/
│   │   │   │       └── resize.js
│   │   │   ├── DndList/
│   │   │   │   └── index.vue
│   │   │   ├── Hamburger/
│   │   │   │   └── index.vue
│   │   │   ├── HeaderSearch/
│   │   │   │   └── index.vue
│   │   │   ├── Kanban/
│   │   │   │   └── index.vue
│   │   │   ├── LangSelect/
│   │   │   │   └── index.vue
│   │   │   ├── MDinput/
│   │   │   │   └── index.vue
│   │   │   ├── Pagination/
│   │   │   │   └── index.vue
│   │   │   ├── PanThumb/
│   │   │   │   └── index.vue
│   │   │   ├── RightPanel/
│   │   │   │   └── index.vue
│   │   │   ├── Screenfull/
│   │   │   │   └── index.vue
│   │   │   ├── Share/
│   │   │   │   └── DropdownMenu.vue
│   │   │   ├── Sticky/
│   │   │   │   └── index.vue
│   │   │   ├── SvgIcon/
│   │   │   │   └── index.vue
│   │   │   ├── TextHoverEffect/
│   │   │   │   └── Mallki.vue
│   │   │   ├── ThemePicker/
│   │   │   │   └── index.vue
│   │   │   └── TreeSelect/
│   │   │       └── index.vue
│   │   ├── directive/
│   │   │   ├── clipboard/
│   │   │   │   ├── clipboard.js
│   │   │   │   └── index.js
│   │   │   ├── sticky.js
│   │   │   └── waves/
│   │   │       ├── index.js
│   │   │       ├── waves.css
│   │   │       └── waves.js
│   │   ├── filters/
│   │   │   └── index.js
│   │   ├── icons/
│   │   │   ├── index.js
│   │   │   └── svgo.yml
│   │   ├── lang/
│   │   │   ├── en.js
│   │   │   ├── index.js
│   │   │   └── zh.js
│   │   ├── layout/
│   │   │   ├── components/
│   │   │   │   ├── AppMain.vue
│   │   │   │   ├── Navbar.vue
│   │   │   │   ├── Settings/
│   │   │   │   │   └── index.vue
│   │   │   │   ├── Sidebar/
│   │   │   │   │   ├── Item.vue
│   │   │   │   │   ├── Link.vue
│   │   │   │   │   ├── Logo.vue
│   │   │   │   │   ├── SidebarItem.vue
│   │   │   │   │   └── index.vue
│   │   │   │   ├── TagsView/
│   │   │   │   │   ├── ScrollPane.vue
│   │   │   │   │   └── index.vue
│   │   │   │   └── index.js
│   │   │   ├── index.vue
│   │   │   └── mixin/
│   │   │       └── ResizeHandler.js
│   │   ├── main.js
│   │   ├── permission.js
│   │   ├── router/
│   │   │   ├── index.js
│   │   │   └── modules/
│   │   │       └── components.js
│   │   ├── settings.js
│   │   ├── store/
│   │   │   ├── getters.js
│   │   │   ├── index.js
│   │   │   └── modules/
│   │   │       ├── app.js
│   │   │       ├── permission.js
│   │   │       ├── settings.js
│   │   │       ├── tagsView.js
│   │   │       └── user.js
│   │   ├── styles/
│   │   │   ├── btn.scss
│   │   │   ├── csshake.scss
│   │   │   ├── element-ui.scss
│   │   │   ├── element-variables.scss
│   │   │   ├── index.scss
│   │   │   ├── mixin.scss
│   │   │   ├── sidebar.scss
│   │   │   ├── transition.scss
│   │   │   └── variables.scss
│   │   ├── utils/
│   │   │   ├── auth.js
│   │   │   ├── clipboard.js
│   │   │   ├── get-page-title.js
│   │   │   ├── i18n.js
│   │   │   ├── index.js
│   │   │   ├── open-window.js
│   │   │   ├── permission.js
│   │   │   ├── request.js
│   │   │   ├── scroll-to.js
│   │   │   └── validate.js
│   │   ├── vendor/
│   │   │   ├── Export2Excel.js
│   │   │   └── Export2Zip.js
│   │   └── views/
│   │       ├── components-demo/
│   │       │   ├── clipboard.vue
│   │       │   ├── dnd-list.vue
│   │       │   ├── drag-kanban.vue
│   │       │   ├── mixin.vue
│   │       │   └── sticky.vue
│   │       ├── dashboard/
│   │       │   ├── components/
│   │       │   │   ├── BarChart.vue
│   │       │   │   ├── BoxCard.vue
│   │       │   │   ├── LineChart.vue
│   │       │   │   ├── PanelGroup.vue
│   │       │   │   ├── PieChart.vue
│   │       │   │   ├── RaddarChart.vue
│   │       │   │   ├── TodoList/
│   │       │   │   │   ├── Todo.vue
│   │       │   │   │   ├── index.scss
│   │       │   │   │   └── index.vue
│   │       │   │   ├── TransactionTable.vue
│   │       │   │   └── mixins/
│   │       │   │       └── resize.js
│   │       │   └── index.vue
│   │       ├── error-page/
│   │       │   ├── 401.vue
│   │       │   └── 404.vue
│   │       ├── icons/
│   │       │   ├── element-icons.js
│   │       │   ├── index.vue
│   │       │   └── svg-icons.js
│   │       ├── login/
│   │       │   └── index.vue
│   │       ├── notice/
│   │       │   ├── mail.vue
│   │       │   └── telegram.vue
│   │       ├── sys/
│   │       │   ├── group.vue
│   │       │   ├── menu.vue
│   │       │   ├── role.vue
│   │       │   └── user.vue
│   │       ├── ticket/
│   │       │   ├── all_ticket.vue
│   │       │   ├── my_ticket.vue
│   │       │   ├── new_ticket.vue
│   │       │   ├── s_ticket.vue
│   │       │   ├── todo_ticket.vue
│   │       │   └── u_ticket.vue
│   │       ├── tool/
│   │       │   ├── audit.vue
│   │       │   └── test.vue
│   │       └── workflow/
│   │           ├── pages/
│   │           │   ├── customfield.vue
│   │           │   ├── state.vue
│   │           │   └── transition.vue
│   │           ├── wfconf.vue
│   │           ├── wfset.vue
│   │           └── wftype.vue
│   └── vue.config.js
└── oms_nginx.conf

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

================================================
FILE: .gitignore
================================================
*.pyc
.idea/
.DS_Store
.python-version
upload/
*.log
node_modules/
package-lock.json
.vscode/
venv/
*.db


================================================
FILE: 500.html
================================================
<html>
<head>
<style type="text/css">
*{
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

*::before,
*::after {
    content: '';
  position: absolute;
}

body{
	background: #1B0034;
	background-image: linear-gradient( 135deg, #1B0034 10%, #33265C 100%);
	background-attachment: fixed;
	background-size: cover;

}

.error {
	width: 100%;
	height: auto;
	margin: 50px auto;
	text-align: center;
	margin-bottom: 0;
}

.dracula{
	width: 230px;
	height: 250px;
	display: inline-block;
	margin: auto;
	overflowX: hidden;
}

.error .p {
	height: 50%;
	color: #c4ddc0;
	font-size: 280px;
	margin: 50px;
	display: inline-block;
}






.con {
  width: 500px;
  height: 500px;
  position: relative;
  margin: 9% auto 0;
animation: ani9 0.3s ease-in-out infinite  alternate ;}

@keyframes ani9 {
    0%{
    transform: translateY(10px);
  }

  100%{
    transform: translateY(30px);
  }

}


.con > * {
  position: absolute;
  top: 0; left: 0;
}

.hair{
  top: -20px;
  width: 210px;
  height: 200px;
  background: #C0D7DD;
  border-radius: 0 50% 0 50%;
  transform: rotate(45deg);
  background: #33265C;
}
.hair-r{
  left: 20px;
  width: 210px;
  height: 200px;
  background: #C0D7DD;
  border-radius: 0 50% 0 50%;
  transform: rotate(45deg);
  background: #33265C;

}
.head {
  width: 200px;
  height: 200px;
  background: #C0D7DD;
  border-radius: 0 50% 0 50%;
  transform: rotate(45deg);
}
.eye {
 width: 20px; height:20px;
  background: #111113;
  border-radius: 50%;
  top: 15%; left: 11.5%;
  transition: .3s linear;
}
.eye-r{left: 24%;}

.mouth {
  width: 60px;
  height: 20px;
  background: #840021;
  top: 20%;
  left: 14%;
  border-radius: 50% / 0 0 100% 100%;
}
.mouth::after{

  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 13px solid #FFFFFF;
  left: 10px;

}
.mouth::before{
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 13px solid #FFFFFF;
  left: 40px;
}

.blod {
  width: 8px;
  height: 20px;
  background: #840021;
  top: 23%; left: 17%;
  border-radius: 20px;
}
.blod::after{
   width: 2px;
  height: 10px;
  background: #FFF;
  top: 20%; left: 10%;
  border-radius: 20px;

}
.blod2 {
  top: 23%; left: 20%;
  width: 13px;
  height: 13px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(130deg);
  animation: blod 2s linear infinite;
  opacity: 0;
}
@keyframes blod {
  0%   {opacity: 1;}
  100%   {background:red; opacity: 0; top:50%;}


}



/* page-ms */
.page-ms {transform: translateY(-50px);}

.error p.page-msg {
	text-align: center;
	color: #ddc0c0;
	font-size: 30px;
	font-family: 'Combo', cursive;
	margin-top: 20px;
}
button.go-back {
		font-size: 30px;
    font-family: 'Combo', cursive;
    border: none;
    padding: 10px 20px;
    cursor: pointer;
    transition: 0.3s linear;
    z-index: 9;
    border-radius: 10px;
    background: #c0c7ddad;
    color: #26eff1;
    box-shadow: 0 0 10px 0 #C0D7DD;
}
button:hover {box-shadow: 0 0 20px 0 #C0D7DD;}

audio {
/* 	display: none; */
}
</style>
</head>
<body>
<div class="container">

	<div  class="error">
		<p class="p">5</p>
		<span class="dracula">
			<div class="con">
				<div class="hair"></div>
				<div class="hair-r"></div>
				<div class="head"></div>
    		<div class="eye"></div>
    		<div class="eye eye-r"></div>
  			<div class="mouth"></div>
  			<div class="blod"></div>
  			<div class="blod blod2"></div>
			</div>
		</span>
		<p class="p">0</p>

		<div class="page-ms">
					<button class="go-back">Oops</button>
			<p class="page-msg"> 网站正在更新维护中,请稍后2分钟再试! </p>
		</div>
</div>

	</div>
	</body>
</html>

================================================
FILE: README.md
================================================
# django + vue 工作流管理系统
包含 `用户`、`角色`、`菜单`、`权限` 管理, 这是基础的工作流系统,初始化会生成请假工作流, 也可以自行配置其他工作流比如,发布工单等。

[comment]: <> (- 后端model参考: [loonflow]&#40;https://github.com/blackholll/loonflow&#41;, 非常不错的一个项目)
[comment]: <> (- 前端设计参考: [花裤衩 vue-element-admin]&#40;https://github.com/PanJiaChen/vue-element-admin&#41;, 大神作品没得说)
## 开发环境
### 后端
安装依赖
```bash
cd backend
pip install -r dev_requirements.txt
```

初始化系统
- 生成管理员账号 `admin 123456`
```bash
python manage.py migrate
python manage.py init_sys
```

生成工作流
- 用户 `ops`,`ops_tl`,`dev`,`dev_tl`,`hr`,`hr_tl`
- 密码 `123456`

```bash
python manage.py init_wf
python manage.py init_ticket
python manage.py init_leave
```

运行
```bash
python manage.py runserver
```

### 前端
```bash
cd frontend
npm install
npm run dev
```

## 开始使用
使用 `admin` 登录
### 给所有角色分配工作流权限
![role](https://github.com/itimor/one-workflow/raw/master/gifs/role.png)

### 分配菜单 和 数据 权限
![role_edit](https://github.com/itimor/one-workflow/raw/master/gifs/role_edit.png)

### 配置假期工作流
![role](https://github.com/itimor/one-workflow/raw/master/gifs/leave.png)

### 新建工单
![role](https://github.com/itimor/one-workflow/raw/master/gifs/new.png)

### 编辑工单
![role](https://github.com/itimor/one-workflow/raw/master/gifs/edit.png)

### 所有工单
![role](https://github.com/itimor/one-workflow/raw/master/gifs/all.png)

================================================
FILE: backend/__init__.py
================================================
# -*- coding: utf-8 -*-
# author: itimor



================================================
FILE: backend/celery.service
================================================
[Unit]
Description=start celery worker

[Service]
ExecStart=/bin/bash -c 'cd /opt/projects/one-oms/backend; /root/.pyenv/versions/envoms/bin/celery -A core worker -B --loglevel=info -f /data/logs/celery.log'
#非正常dead,自动重启
Restart=on-failure
#3秒后启动
RestartSec=3s
KillSignal=SIGQUIT
Type=simple
NotifyAccess=all

[Install]
WantedBy=multi-user.target


================================================
FILE: backend/common/JSONRenderer.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from rest_framework.renderers import JSONRenderer


class CustomJSONRenderer(JSONRenderer):
    def render(self, data, accepted_media_type=None, renderer_context=None):
        response_data = {}
        object_list = 'results'
        try:
            meta_dict = getattr(renderer_context.get('view').get_serializer().Meta, 'meta_dict')
        except:
            meta_dict = dict()

        try:
            data.get('paginated_results')
            response_data['meta'] = data['meta']
            response_data[object_list] = data['results']
        except:
            response_data[object_list] = data
            response_data['meta'] = dict()
            response_data['meta'].update(meta_dict)

        response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
        return response


================================================
FILE: backend/common/__init__.py
================================================


================================================
FILE: backend/common/dispath.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import six
from rest_framework.serializers import Serializer
from rest_framework.response import Response


class JsonResponse(Response):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    def __init__(self, data=None, code=None, desc=None,
                 status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
        """
        Alters the init arguments slightly.
        For example, drop 'template_name', and instead use 'data'.
        Setting 'renderer' and 'media_type' will typically be deferred,
        For example being set automatically by the `APIView`.
        """
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = data
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in six.iteritems(headers):
                self[name] = value


================================================
FILE: backend/common/django.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from django.utils.deprecation import MiddlewareMixin

class DisableCSRF(MiddlewareMixin):
    def process_request(self, request):
        setattr(request, '_dont_enforce_csrf_checks', True)


================================================
FILE: backend/common/exceptions.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from rest_framework.views import exception_handler
from common import status


def JSONExceptionHandler(exc, context):
    response = exception_handler(exc, context)

    if response is not None:
        resp = {
            'code': status.HTTP_400_BAD_REQUEST,
            'result': dict(response.data)
        }
        response.data = resp

    return response


class ExceptionX_Result:
    exceptionType = None
    exceptionTitle = None
    exceptionDetail = None


class ExceptionX:

    @staticmethod
    def ToString(e):
        result = ExceptionX_Result
        tempStr = str(type(e))
        tempStrArray = tempStr.split("'")
        result.exceptionTitle = tempStrArray[1]
        result.exceptionType = tempStrArray[0][1:]
        result.exceptionDetail = str(e)

        if result.exceptionDetail[0] == "<":
            if result.exceptionDetail[result.exceptionDetail.__len__() - 1] == ">":
                result.exceptionDetail = result.exceptionDetail[1:result.exceptionDetail.__len__() - 1]
        return result

    @staticmethod
    def PasreRaise(e):
        result = ExceptionX_Result
        tempStr = str(type(e))
        tempStrArray = tempStr.split("'")
        result.exceptionTitle = tempStrArray[1]
        result.exceptionType = tempStrArray[0][1:]
        result.exceptionDetail = str(e)

        if result.exceptionDetail[0] == "<":
            if result.exceptionDetail[result.exceptionDetail.__len__() - 1] == ">":
                result.exceptionDetail = result.exceptionDetail[1:result.exceptionDetail.__len__() - 1]
        return result.exceptionDetail


================================================
FILE: backend/common/models.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.db import models


class BaseModel(models.Model):
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    memo = models.TextField(blank=True, verbose_name='备注')

    class Meta:
        ordering = ['-create_time']
        abstract = True



================================================
FILE: backend/common/pagination.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from collections import OrderedDict

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination

from common import status
from common.dispath import JsonResponse


def _positive_int(integer_string, strict=False, cutoff=None):
    """
    分页大小为零不分页
    """
    ret = int(integer_string)
    if ret < 0:
        raise ValueError()
    if (ret == 0) and strict:
        return None
    if cutoff:
        return min(ret, cutoff)
    return ret


class StandardResultsSetPagination(PageNumberPagination):
    """
    配置分页规则
    """
    page_size = 20
    page_size_query_param = 'limit'
    page_query_param = 'page'
    max_page_size = 1000

    def get_paginated_response(self, data):
        return JsonResponse(OrderedDict([
            ('count', self.page.paginator.count),
            ('next', self.get_next_link()),
            ('previous', self.get_previous_link()),
            ('results', data)
        ], code=status.HTTP_200_OK))

    def get_page_size(self, request):
        if self.page_size_query_param:
            try:
                return _positive_int(
                    request.query_params[self.page_size_query_param],
                    strict=True,
                    cutoff=self.max_page_size
                )
            except (KeyError, ValueError):
                return None
        return self.page_size


class CustomLimitOffsetPagination(LimitOffsetPagination):
    def get_offset(self, request):
        try:
            return (int(request.query_params['offset']) - 1) * int(request.query_params['limit'])
        except (KeyError, ValueError):
            return 1


================================================
FILE: backend/common/status.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from __future__ import unicode_literals


def is_informational(code):
    return 100 <= code <= 199


def is_success(code):
    return 200 <= code <= 299


def is_redirect(code):
    return 300 <= code <= 399


def is_client_error(code):
    return 400 <= code <= 499


def is_server_error(code):
    return 500 <= code <= 599


HTTP_100_CONTINUE = 10000
HTTP_101_SWITCHING_PROTOCOLS = 10100
HTTP_200_OK = 20000
HTTP_201_CREATED = 20100
HTTP_202_ACCEPTED = 20200
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 20300
HTTP_204_NO_CONTENT = 20400
HTTP_205_RESET_CONTENT = 20500
HTTP_206_PARTIAL_CONTENT = 20600
HTTP_207_MULTI_STATUS = 20700
HTTP_300_MULTIPLE_CHOICES = 30000
HTTP_301_MOVED_PERMANENTLY = 30100
HTTP_302_FOUND = 30200
HTTP_303_SEE_OTHER = 30300
HTTP_304_NOT_MODIFIED = 30400
HTTP_305_USE_PROXY = 30500
HTTP_306_RESERVED = 30600
HTTP_307_TEMPORARY_REDIRECT = 30700
HTTP_400_BAD_REQUEST = 40000
HTTP_401_UNAUTHORIZED = 40100
HTTP_402_PAYMENT_REQUIRED = 40200
HTTP_403_FORBIDDEN = 40300
HTTP_404_NOT_FOUND = 40400
HTTP_405_METHOD_NOT_ALLOWED = 40500
HTTP_406_NOT_ACCEPTABLE = 40600
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 40700
HTTP_408_REQUEST_TIMEOUT = 40800
HTTP_409_CONFLICT = 40900
HTTP_410_GONE = 41000
HTTP_411_LENGTH_REQUIRED = 41100
HTTP_412_PRECONDITION_FAILED = 41200
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 41300
HTTP_414_REQUEST_URI_TOO_LONG = 41400
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 41500
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 41600
HTTP_417_EXPECTATION_FAILED = 41700
HTTP_422_UNPROCESSABLE_ENTITY = 42200
HTTP_423_LOCKED = 42300
HTTP_424_FAILED_DEPENDENCY = 42400
HTTP_428_PRECONDITION_REQUIRED = 42800
HTTP_429_TOO_MANY_REQUESTS = 42900
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 43100
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 45100
HTTP_500_INTERNAL_SERVER_ERROR = 50000
HTTP_501_NOT_IMPLEMENTED = 50100
HTTP_502_BAD_GATEWAY = 50200
HTTP_503_SERVICE_UNAVAILABLE = 50300
HTTP_504_GATEWAY_TIMEOUT = 50400
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 50500
HTTP_507_INSUFFICIENT_STORAGE = 50700
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 51100


================================================
FILE: backend/common/views.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from __future__ import print_function, unicode_literals

import json
from rest_framework.response import Response
from collections import OrderedDict
from rest_framework import viewsets
from django.utils import timezone
from rest_framework.decorators import action
from common import status
from common.dispath import JsonResponse
from common.exceptions import *
from tools.models import RequestEvent


class ModelViewSet(viewsets.ModelViewSet):

    def __init__(self, *args, **kwargs):
        super(ModelViewSet, self).__init__(*args, **kwargs)
        self.resultData = False

    def watch_audit_log(self, request):

        ip = request.META.get("HTTP_X_FORWARDED_FOR", "")
        if not ip:
            ip = request.META.get('REMOTE_ADDR', "")
        method = request._request.method
        RequestEvent.objects.create(
            url=request.path,
            method=method,
            query_string=json.dumps({
                'query_params': request.query_params,
                'json': request.data
            }),
            user=self.request.user,
            remote_ip=ip,
            create_time=timezone.now()
        )

    def create(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        try:
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return JsonResponse(OrderedDict([
                ('results', serializer.data)
            ], code=status.HTTP_200_OK), headers=headers)
        except Exception as e:
            print(e)
            return JsonResponse(OrderedDict([
                ('results', {"msg": ExceptionX.PasreRaise(e)})
            ], code=status.HTTP_500_INTERNAL_SERVER_ERROR))

    def perform_create(self, serializer):
        serializer.save()

    def list(self, request, *args, **kwargs):
        # 不记录list get请求
        # self.watch_audit_log(request)

        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return JsonResponse(OrderedDict([
            ('results', serializer.data)
        ], code=status.HTTP_200_OK))

    def retrieve(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return JsonResponse(OrderedDict([
            ('results', serializer.data)
        ], code=status.HTTP_200_OK))

    def update(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            instance._prefetched_objects_cache = {}

        return JsonResponse(OrderedDict([
            ('results', serializer.data)
        ], code=status.HTTP_200_OK))

    def perform_update(self, serializer):
        serializer.is_valid(raise_exception=True)
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

    def destroy(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        instance = self.get_object()
        self.perform_destroy(instance)
        return JsonResponse(OrderedDict(code=status.HTTP_200_OK))

    def perform_destroy(self, instance):
        instance.delete()


class FKModelViewSet(ModelViewSet):

    def transer(self, instance=None, id=None):
        self.resultData = True
        if self.action == "create":
            self.kwargs = {'pk': id}
            instance = self.get_object()
        serializer = self.get_serializer(instance)
        return serializer

    def perform_create(self, serializer):
        super(FKModelViewSet, self).perform_create(serializer)
        self.readSerializer = self.transer(id=serializer.data['id'])

    def perform_update(self, serializer):
        super(FKModelViewSet, self).perform_update(serializer)
        self.readSerializer = self.transer(self.readInstance)

    def perform_destroy(self, instance):
        super(FKModelViewSet, self).perform_destroy(instance)

    def create(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        serializer = self.readSerializer
        return JsonResponse(OrderedDict([
            ('results', serializer.data)
        ], code=status.HTTP_200_OK), headers=headers)

    def update(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        partial = kwargs.pop('partial', False)
        self.readInstance = instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        serializer = self.readSerializer

        if getattr(instance, '_prefetched_objects_cache', None):
            instance._prefetched_objects_cache = {}

        return JsonResponse(OrderedDict([
            ('results', serializer.data)
        ], code=status.HTTP_200_OK))

    def destroy(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        instance = self.get_object()
        self.perform_destroy(instance)
        return JsonResponse(OrderedDict(code=status.HTTP_200_OK))


# 批量操作modelview  bulk_create|bulk_delete|bulk_update
class BulkModelMixin(ModelViewSet):
    # 批量添加
    @action(methods=['post'], url_path='bulk_create', detail=False)
    def bulk_create(self, request, *args, **kwargs):
        """
        /api/tool/simple/bulk_create/
        :return:
        """
        self.watch_audit_log(request)
        objs = request.data

        if not objs:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        bulk_models = []
        for obj in objs:
            req = {'id': '0102', 'msg': 'success'}
            print(obj)
            try:
                serializer = self.get_serializer(data=obj)
                serializer.is_valid(raise_exception=True)
                self.perform_create(serializer)
                req['id'] = serializer.data['id']
            except Exception as e:
                req['msg'] = ExceptionX.ToString(e)

            bulk_models.append(req)

        return JsonResponse(OrderedDict([
            ('results', bulk_models)
        ], code=status.HTTP_200_OK))

    @action(methods=['delete'], url_path='bulk_delete', detail=False)
    def bulk_delete(self, request, *args, **kwargs):
        """
        /api/tool/simple/bulk_delete/
        :return:
        """
        self.watch_audit_log(request)
        ids = request.data

        if not ids:
            return Response(status=status.HTTP_404_NOT_FOUND)

        bulk_models = []
        for id in ids:
            req = {'id': id, 'msg': 'success'}
            try:
                queryset = self.filter_queryset(self.get_queryset())
                instance = queryset.get(pk=id)
                self.perform_destroy(instance)
            except Exception as e:
                req['msg'] = ExceptionX.ToString(e)

            bulk_models.append(req)

        return JsonResponse(OrderedDict([
            ('results', bulk_models)
        ], code=status.HTTP_200_OK))

    @action(methods=['put', 'patch'], url_path='bulk_update', detail=False)
    def bulk_update(self, request, *args, **kwargs):
        """
        /api/tool/simple/bulk_update/
        :return:
        """
        self.watch_audit_log(request)
        ids = request.data['ids']
        obj = request.data['obj']

        if not ids or not obj:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        bulk_models = []
        for id in ids:
            req = {'id': id, 'msg': 'success'}
            try:
                queryset = self.filter_queryset(self.get_queryset())
                instance = queryset.get(pk=id)
                serializer = self.get_serializer(instance, data=obj, partial=True)
                serializer.is_valid(raise_exception=True)
                self.perform_update(serializer)
            except Exception as e:
                req['msg'] = ExceptionX.ToString(e)

            bulk_models.append(req)

        return JsonResponse(OrderedDict([
            ('results', bulk_models)
        ], code=status.HTTP_200_OK))


================================================
FILE: backend/core/__init__.py
================================================
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
# import pymysql
#
# pymysql.install_as_MySQLdb()


================================================
FILE: backend/core/celery.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
celery_app = Celery('core', result_backend='django-db')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
#   should have a `CELERY_` prefix.
celery_app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
celery_app.autodiscover_tasks()
celery_app.loader.override_backends['django-db'] = 'django_celery_results.backends.database:DatabaseBackend'

================================================
FILE: backend/core/settings/__init__.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import platform
from .base import *

os_type = platform.system()

if os_type == 'Windows':
    print('进入 dev ')
    from .dev import *
elif os_type == 'Linux':
    print('进入 prod ')
    from .prod import *
else:
    print('进入 mac')
    from .mac import *


================================================
FILE: backend/core/settings/base.py
================================================
"""
Django settings for core project.

Generated by 'django-admin startproject' using Django 2.1.1.

For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""

import os
import datetime
from logging.config import dictConfig

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  # restful api
    'django_filters',  # 过滤
    'corsheaders',  # 跨域
    'common',
    'tools',
    'systems',
    'workflows',
    'tickets',
    'notices',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'core.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 'DIRS': [],
        'DIRS': ['../frontend/dist'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'core.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/

STATIC_URL = '/static/'
# Add for vuejs
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "./templates"),
]

MEDIA_ROOT = os.path.join(BASE_DIR, '../upload')
MEDIA_URL = '/upload/'

REST_USE_JWT = True
REST_FRAMEWORK = {
    'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
    'DEFAULT_PAGINATION_CLASS': 'common.pagination.StandardResultsSetPagination',
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.AllowAny',
        'rest_framework.permissions.IsAuthenticated',
        'systems.permissions.IsOwnerRoles',
    ),
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}

JWT_AUTH = {
    'JWT_AUTH_HEADER_PREFIX': 'core',
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=30),
}

# user model
AUTH_USER_MODEL = "systems.User"
SUPER_ADMIN_USER = 'admin'

CORS_ORIGIN_ALLOW_ALL = True

LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'simple': {
            'format': '%(levelname)s %(asctime)s %(message)s',
            'datefmt': '%y %b %d, %H:%M:%S',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'propagate': True,
            'level': 'INFO',
        },
    }
}

dictConfig(LOGGING)


================================================
FILE: backend/core/settings/dev.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import os

APP_ENV = 'dev'

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '64318ob@vbou7h50)b0a_pfda4d$bw2nhl4h*m$qo0_e_fxw=658!z*x'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['*']

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# sqlite
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, '../core.db'),
    }
}

# mysql
# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.mysql',
#         'NAME': 'one',
#         'USER': 'root',
#         'PASSWORD': 'momo520',
#         'HOST': '1.1.1.11',
#         'OPTIONS': {
#             "init_command": "SET foreign_key_checks=0;",
#         }
#     }
# }

# 加载 mysql
# import pymysql
# pymysql.install_as_MySQLdb()


================================================
FILE: backend/core/settings/mac.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import os

APP_ENV = 'dev'

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '64318ob@vbou7h50)b0a_pfda4d$bw2nhl4h*m$qo0_e_fxw=658!z*x'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['*']

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# sqlite
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, '../core.db'),
    }
}

# mysql
# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.mysql',
#         'NAME': 'one',
#         'USER': 'root',
#         'PASSWORD': 'momo520',
#         'HOST': '1.1.1.11',
#         'OPTIONS': {
#             "init_command": "SET foreign_key_checks=0;",
#         }
#     }
# }

# 加载 mysql
# import pymysql
# pymysql.install_as_MySQLdb()


================================================
FILE: backend/core/settings/prod.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import os

APP_ENV = 'prod'

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '64318ob@vbou7h50)b0a_pfda4d$bw2nhl4h*m$qo0_e_fxw=658!z*x'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ['*']

# mysql
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'one',
        'USER': 'root',
        'PASSWORD': 'TY%pwd123',
        'HOST': 'localhost',
        'OPTIONS': {
            "init_command": "SET foreign_key_checks=0;",
        }
    }
}


================================================
FILE: backend/core/urls.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from django.conf.urls import url, include
from django.conf.urls.static import static
from django.views.generic.base import TemplateView
from core import settings

urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \
              [
                  # 工具管理
                  url(r'api/tool/', include(('tools.urls', 'tools'), namespace="tools")),
                  # 系统管理
                  url(r'api/sys/', include(('systems.urls', 'systems'), namespace="systems")),
                  # 工作流管理
                  url(r'api/workflow/', include(('workflows.urls', 'workflows'), namespace="workflows")),
                  # 工单管理
                  url(r'api/ticket/', include(('tickets.urls', 'tickets'), namespace="tickets")),
                  # 通知管理
                  url(r'api/notice/', include(('notices.urls', 'notices'), namespace="notices")),
              ]

if settings.APP_ENV == 'prod':
    from rest_framework.documentation import include_docs_urls

    urlpatterns += [
        # api文档
        url(r'^docs/', include_docs_urls(title='X Document')),
        # 静态模板
        url(r'', TemplateView.as_view(template_name="index.html")),
    ]
else:
    from django.contrib import admin

    urlpatterns += [
        # 管理后台
        url(r'^admin/', admin.site.urls),
    ]


================================================
FILE: backend/core/wsgi.py
================================================
"""
WSGI config for core project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')

application = get_wsgi_application()


================================================
FILE: backend/dev_requirements.txt
================================================
asgiref==3.2.3
certifi==2019.11.28
chardet==3.0.4
coreapi==2.3.3
coreschema==0.0.4
Django==3.0.3
django-cors-headers==3.2.1
django-filter==2.2.0
django-rest-auth==0.9.5
djangorestframework==3.11.0
djangorestframework-jwt==1.11.0
idna==2.8
IPy==1.0
itypes==1.1.0
Jinja2==2.10.3
MarkupSafe==1.1.1
PyMySQL==0.9.3
PyJWT==1.7.1
pytz==2019.3
requests==2.22.0
six==1.14.0
sqlparse==0.3.0
uritemplate==3.0.1
urllib3==1.25.8


================================================
FILE: backend/init.sh
================================================
#!/bin/bash

apps=(systems tools notices workflows tickets)
rm -rf core.db
for app in ${apps[@]};do
  rm -rf $app/migrations
done

for app in ${apps[@]};do
  echo $app
  python manage.py makemigrations $app
done

python manage.py migrate
python manage.py init_sys
python manage.py init_wf
python manage.py init_ticket
python manage.py init_leave
python manage.py runserver

================================================
FILE: backend/manage.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import os
import sys

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')

    # 排错 “The maximum column size is 767 bytes”
    from django.db.backends.mysql.schema import DatabaseSchemaEditor
    DatabaseSchemaEditor.sql_create_table += " ROW_FORMAT=DYNAMIC"

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


================================================
FILE: backend/notices/__init__.py
================================================


================================================
FILE: backend/notices/management/__init__.py
================================================


================================================
FILE: backend/notices/management/commands/__init__.py
================================================


================================================
FILE: backend/notices/management/commands/init_notice.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.core.management.base import BaseCommand, CommandError
from systems.models import *
from systems.menus import init_menu


class Command(BaseCommand):
    help = '初始化工作流'

    def handle(self, *args, **options):
        topmenu = Menu.objects.get(name='top', code='top')
        self.stdout.write(self.style.SUCCESS('############ 初始化通知菜单 ###########'))
        noticemenu = Menu.objects.create(name='通知管理', code='notice', curl='/notice', icon='notice', sequence=5, type=1,
                                         parent_id=topmenu.id)
        menumodel = Menu.objects.create(name='mail通知', code='mail', curl='/mail', icon='mail', sequence=10, type=2,
                                        parent_id=noticemenu.id)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='telegram通知', code='telegram', curl='/telegram', icon='telegram',
                                        sequence=20, type=2, parent_id=noticemenu.id)
        init_menu(menumodel)
        self.stdout.write(self.style.SUCCESS('初始化完成'))


================================================
FILE: backend/notices/migrations/0001_initial.py
================================================
# Generated by Django 3.0.3 on 2021-06-27 00:05

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='MailBot',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('type', models.CharField(choices=[('mail', 'mail'), ('telegram', 'telegram')], default=0, max_length=10, verbose_name='通知类型')),
                ('name', models.CharField(max_length=112, unique=True, verbose_name='名称')),
                ('host', models.CharField(max_length=112, verbose_name='主机')),
                ('user', models.CharField(max_length=112, verbose_name='账号')),
                ('password', models.CharField(max_length=112, verbose_name='密码')),
                ('to', models.CharField(max_length=112, verbose_name='接收者')),
            ],
            options={
                'verbose_name': '邮件机器人',
                'verbose_name_plural': '邮件机器人',
            },
        ),
        migrations.CreateModel(
            name='TelegramBot',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('type', models.CharField(choices=[('mail', 'mail'), ('telegram', 'telegram')], default=0, max_length=10, verbose_name='通知类型')),
                ('name', models.CharField(max_length=112, unique=True, verbose_name='名称')),
                ('uid', models.CharField(max_length=112, verbose_name='账号id')),
                ('token', models.CharField(max_length=112, verbose_name='token')),
                ('chat_id', models.CharField(max_length=112, verbose_name='chat_id')),
            ],
            options={
                'verbose_name': 'tg机器人',
                'verbose_name_plural': 'tg机器人',
            },
        ),
    ]


================================================
FILE: backend/notices/migrations/__init__.py
================================================


================================================
FILE: backend/notices/models.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from django.db import models
from common.models import BaseModel

notice_type = {
    'mail': 'mail',
    'telegram': 'telegram',
}


class MailBot(BaseModel):
    type = models.CharField(max_length=10, choices=tuple(notice_type.items()), default=0, verbose_name='通知类型')
    name = models.CharField(max_length=112, unique=True, verbose_name='名称')
    host = models.CharField(max_length=112, verbose_name='主机')
    user = models.CharField(max_length=112, verbose_name='账号')
    password = models.CharField(max_length=112, verbose_name='密码')
    to = models.CharField(max_length=112, verbose_name='接收者')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "邮件机器人"
        verbose_name_plural = verbose_name


class TelegramBot(BaseModel):
    type = models.CharField(max_length=10, choices=tuple(notice_type.items()), default=0, verbose_name='通知类型')
    name = models.CharField(max_length=112, unique=True, verbose_name='名称')
    uid = models.CharField(max_length=112, verbose_name='账号id')
    token = models.CharField(max_length=112, verbose_name='token')
    chat_id = models.CharField(max_length=112, verbose_name='chat_id')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "tg机器人"
        verbose_name_plural = verbose_name


================================================
FILE: backend/notices/serializers.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from notices.models import *
from rest_framework import serializers


class MailBotSerializer(serializers.ModelSerializer):
    class Meta:
        model = MailBot
        fields = '__all__'


class TelegramBotSerializer(serializers.ModelSerializer):
    class Meta:
        model = TelegramBot
        fields = '__all__'


================================================
FILE: backend/notices/urls.py
================================================
# -*- coding: utf-8 -*-
# author: itimor


from django.conf.urls import url
from rest_framework import routers
from notices.views import NoticeViewSet, MailBotViewSet, TelegramBotViewSet

router = routers.DefaultRouter()

router.register(r'notice', NoticeViewSet)
router.register(r'mail', MailBotViewSet)
router.register(r'telegram', TelegramBotViewSet)

urlpatterns = [
]

urlpatterns += router.urls


================================================
FILE: backend/notices/views.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from notices.serializers import *
from common.views import ModelViewSet, JsonResponse
from common import status
from collections import OrderedDict
from rest_framework.decorators import action


class NoticeViewSet(ModelViewSet):
    queryset = MailBot.objects.all()
    serializer_class = MailBotSerializer

    # send
    @action(methods=['post'], url_path='send', detail=False)
    def send(self, request, *args, **kwargs):
        self.watch_audit_log(request)
        data = {'code': 20000, 'msg': 'null'}
        type = request.GET['type']
        if type == 'mail':
            import smtplib
            from email.mime.text import MIMEText

            bot_name = request.GET['bot_name']
            bot_obj = MailBot.objects.get(name=bot_name)
            mail_user = '{}@{}'.format(bot_obj.user, bot_obj.host)

            tos = bot_obj.to
            content = request.data.get('content', 'Hello Pornhub')
            message = MIMEText(content, 'plain', 'utf-8')
            message['Subject'] = "{}".format(request.form['subject'])
            message['From'] = mail_user
            message['To'] = tos[0:]

            try:
                smtpObj = smtplib.SMTP_SSL(bot_obj.host)
                smtpObj.login(mail_user, bot_obj.pasword)
                smtpObj.sendmail(mail_user, tos, message.as_string())
                smtpObj.quit()
            except smtplib.SMTPException as e:
                print(e)
                data['msg'] = 'error'
        elif type == 'telegram':
            import telegram
            bot_name = request.GET['bot_name']
            content = request.data.get('content', 'Hello Pornhub')
            bot_obj = TelegramBot.objects.get(name=bot_name)
            token = '{}:{}'.format(bot_obj.uid, bot_obj.token)
            bot = telegram.Bot(token=token)
            data['msg'] = bot.send_message(chat_id=bot_obj.chat_id, text=content)
        else:
            pass

        return JsonResponse(OrderedDict([
            ('results', data)
        ], code=status.HTTP_200_OK))


class MailBotViewSet(ModelViewSet):
    queryset = MailBot.objects.all()
    serializer_class = MailBotSerializer
    search_fields = ['name']
    filter_fields = ['type', 'id', 'name']
    ordering_fields = ['name']


class TelegramBotViewSet(ModelViewSet):
    queryset = TelegramBot.objects.all()
    serializer_class = TelegramBotSerializer
    search_fields = ['name']
    filter_fields = ['type', 'id', 'name']
    ordering_fields = ['name']


================================================
FILE: backend/oms.ini
================================================
[uwsgi]
project = core
base = /data/app/one/backend

chdir = %(base)
module = %(project).wsgi:application

master = true
processes = 5
enable-threads = true

socket = %(base)/%(project).sock
chmod-socket = 666
vacuum = true
logto = /data/logs/django/one.log


================================================
FILE: backend/oms.service
================================================
[Unit]
Description=uWSGI instance to serve one-oms

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/data/app/one/backend
ExecStart=/root/.pyenv/versions/boce/bin/uwsgi --ini oms.ini --touch-reload=/etc/nginx/uwsgi_params
Restart=on-failure
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target


================================================
FILE: backend/requirements.txt
================================================
asgiref==3.2.3
certifi==2019.11.28
chardet==3.0.4
coreapi==2.3.3
coreschema==0.0.4
Django==3.0.3
django-cors-headers==3.2.1
django-filter==2.2.0
django-rest-auth==0.9.5
djangorestframework==3.11.0
djangorestframework-jwt==1.11.0
idna==2.8
IPy==1.0
itypes==1.1.0
Jinja2==2.10.3
MarkupSafe==1.1.1
python-telegram-bot==12.6.1
mysqlclient==1.4.6
PyJWT==1.7.1
pytz==2019.3
requests==2.22.0
six==1.14.0
sqlparse==0.3.0
uritemplate==3.0.1
urllib3==1.25.8


================================================
FILE: backend/systems/__init__.py
================================================
# -*- coding: utf-8 -*-
# author: timor



================================================
FILE: backend/systems/admin.py
================================================
from django.contrib import admin
from systems.models import *

admin.site.register(Menu)
admin.site.register(Role)
admin.site.register(Group)
admin.site.register(User)


================================================
FILE: backend/systems/management/__init__.py
================================================


================================================
FILE: backend/systems/management/commands/__init__.py
================================================


================================================
FILE: backend/systems/management/commands/init_sys.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.core.management.base import BaseCommand, CommandError
from systems.models import *
from systems.menus import init_menu
from django.contrib.auth.hashers import make_password


class Command(BaseCommand):
    help = '初始化 菜单 角色 用户 用户组'

    def handle(self, *args, **options):
        try:
            self.stdout.write(self.style.SUCCESS('############ 初始化角色 ###########'))
            role = Role.objects.create(name='top', code='top', sequence=0, parent=None)
        except:
            raise CommandError('初始化角色失败')

        try:
            self.stdout.write(self.style.SUCCESS('############ 初始化用户组 ###########'))
            group = Group.objects.create(name='top', code='top', sequence=0, parent=None)
            group.roles.add(role)
        except:
            raise CommandError('初始化用户组失败')

        try:
            self.stdout.write(self.style.SUCCESS('############ 初始化用户 ###########'))
            user = User.objects.create(username='admin', password=make_password("123456"), group=group, is_admin=True)
            user.roles.add(role)
        except:
            raise CommandError('初始化用户失败')

        menus = Menu.objects.all()
        if len(menus) == 0:
            topmenu = Menu.objects.create(name='top', code='top', curl='/top', icon='top', sequence=0, type=1, parent=None)
            self.stdout.write(self.style.SUCCESS('############ 初始化系统菜单 ###########'))
            sysmenu = Menu.objects.create(name='系统管理', code='sys', curl='/sys', icon='sys', sequence=1, type=1, parent=topmenu)
            menumodel = Menu.objects.create(name='角色管理', code='role', curl='/role', icon='role', sequence=30, type=2, parent=sysmenu)
            init_menu(menumodel)
            menumodel = Menu.objects.create(name='分组管理', code='group', curl='/group', icon='group', sequence=10, type=2, parent=sysmenu)
            init_menu(menumodel)
            menumodel = Menu.objects.create(name='用户管理', code='user', curl='/user', icon='user', sequence=20, type=2, parent=sysmenu)
            init_menu(menumodel)
            menumodel = Menu.objects.create(name='菜单管理', code='menu', curl='/menu', icon='menu', sequence=40, type=2, parent=sysmenu)
            init_menu(menumodel)
            menumodel = Menu.objects.create(name='图标管理', code='icon', curl='/icon', icon='icon', sequence=50, type=2, parent=sysmenu)
            init_menu(menumodel)

            self.stdout.write(self.style.SUCCESS('############ 初始化工具菜单 ###########'))
            toolmenu = Menu.objects.create(name='工具管理', code='tool', curl='/tool', icon='tool', sequence=2, type=1, parent=topmenu)
            menumodel = Menu.objects.create(name='审计日志', code='audit', curl='/audit', icon='audit', sequence=10, type=2, parent=toolmenu)
            init_menu(menumodel)
            menumodel = Menu.objects.create(name='测试页面', code='test', curl='/test', icon='list', sequence=20, type=2, parent=toolmenu)
            init_menu(menumodel)

            self.stdout.write(self.style.SUCCESS('############ 初始化通知菜单 ###########'))
            noticemenu = Menu.objects.create(name='通知管理', code='notice', curl='/notice', icon='notice', sequence=4, type=1, parent=topmenu)
            menumodel = Menu.objects.create(name='邮箱通知', code='mail', curl='/mail', icon='mail', sequence=10, type=2, parent=noticemenu)
            init_menu(menumodel)
            menumodel = Menu.objects.create(name='telegram通知', code='telegram', curl='/telegram', icon='telegram', sequence=20, type=2, parent=noticemenu)
            init_menu(menumodel)

        self.stdout.write(self.style.SUCCESS('初始化完成'))


================================================
FILE: backend/systems/menus.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from systems.models import *
from itertools import chain


# 获取管理员权限下所有菜单
def get_menus_by_user(user):
    user_obj = User.objects.get(username=user)
    if user_obj.is_admin:
        menus = Menu.objects.all()
        all_roles = Role.objects.all()
    else:
        user_roles = user_obj.roles.all()
        group_roles = user_obj.group.roles.all()
        all_roles = sorted(chain(user_roles, group_roles), key=lambda t: t.id, reverse=True)
        print("用户拥有角色: %s" % all_roles)

        menu_list = [item.menus.all() for item in all_roles if item.menus.all()][0]

        menuMap = dict()
        for item in menu_list:
            menuMap[item.id] = item
        for item in menu_list:
            if item.parent_id in menuMap:
                continue
            user_menus = find_menu_daddy(item.parent_id, menuMap)
        menus = [user_menus[i] for i in sorted(user_menus.keys())]
    roles = [i.code for i in all_roles]
    return menus, roles


def find_menu_daddy(menuid, menuMap):
    obj = Menu.objects.filter(id=menuid).first()
    if obj:
        mid = obj.id
        if mid not in menuMap:
            menuMap[mid] = obj
            find_menu_daddy(obj.parent_id, menuMap)
            return menuMap


def set_menu(menus, parent_id):
    amenus = [i for i in menus if i.parent_id == parent_id]

    if len(amenus) == 0:
        return []

    all_menus = []
    for item in amenus:
        menu = {
            'path': item.curl,
            'component': item.code,
            'name': item.code,
            'hidden': item.hidden,
            'meta': {'title': item.code, 'icon': item.icon, 'no_cache': item.no_cache, 'active_menu': item.active_menu,
                     'hidden': item.hidden,
                     },
            'children': []
        }
        if item.type == 3:
            menu['hidden'] = True

        # 查询是否有子级
        menu_children = set_menu(menus, item.id)
        if len(menu_children) > 0:
            menu['children'] = menu_children

        if item.type == 2:
            # 添加子级首页,有这一级NoCache才有效
            menu_index = {
                'path': 'index',
                'component': item.code,
                'name': item.code,
                'hidden': item.hidden,
                'meta': {'title': item.code, 'icon': item.icon, 'no_cache': item.no_cache,
                         'active_menu': item.active_menu, 'hidden': item.hidden,
                         },
                'children': []
            }
            menu['children'].append(menu_index)
            menu['meta'] = []

        all_menus.append(menu)
    return all_menus


# 新增菜单后自动添加菜单下的常规操作
def init_menu(menu):
    if menu.type == 2:
        menu_list = [
            {"name": "新增", "code": menu.code + "_add", "curl": menu.curl + "/add", 'type': 3, "operate": "add",
             "sequence": 10},
            {"name": "删除", "code": menu.code + "_del", "curl": menu.curl + "/del", 'type': 3, "operate": "del",
             "sequence": 20},
            {"name": "编辑", "code": menu.code + "_update", "curl": menu.curl + "/update", 'type': 3, "operate": "update",
             "sequence": 30},
            {"name": "查看", "code": menu.code + "_view", "curl": menu.curl + "/view", 'type': 3, "operate": "view",
             "sequence": 40},
        ]

        menu_models = []
        for item in menu_list:
            menu_models.append(Menu(name=item['name'], code=item['code'], curl=item['curl'], type=item['type'],
                                    operate=item['operate'],
                                    sequence=item['sequence'], parent_id=menu.id, hidden=True))
        Menu.objects.bulk_create(menu_models)


================================================
FILE: backend/systems/migrations/0001_initial.py
================================================
# Generated by Django 3.0.3 on 2021-06-27 00:05

import django.contrib.auth.models
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('auth', '0011_update_proxy_permissions'),
    ]

    operations = [
        migrations.CreateModel(
            name='Menu',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('name', models.CharField(max_length=32, verbose_name='菜单名称')),
                ('code', models.CharField(max_length=32, verbose_name='菜单代码')),
                ('curl', models.CharField(max_length=101, verbose_name='菜单URL')),
                ('icon', models.CharField(blank=True, max_length=32, verbose_name='菜单图标')),
                ('hidden', models.BooleanField(default=False, verbose_name='菜单是否隐藏')),
                ('no_cache', models.BooleanField(default=True, verbose_name='菜单是否缓存')),
                ('active_menu', models.CharField(blank=True, max_length=32, verbose_name='激活菜单')),
                ('sequence', models.SmallIntegerField(default=0, verbose_name='排序值')),
                ('type', models.CharField(choices=[(1, '模块'), (2, '菜单'), (3, '操作')], default=2, max_length=1, verbose_name='菜单类型')),
                ('status', models.BooleanField(default=True, verbose_name='状态')),
                ('operate', models.CharField(choices=[('none', '无'), ('add', '新增'), ('del', '删除'), ('update', '编辑'), ('view', '查看')], default='none', max_length=11, verbose_name='操作类型')),
                ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='systems.Menu', verbose_name='父级菜单')),
            ],
            options={
                'verbose_name': '角色',
                'verbose_name_plural': '角色',
                'ordering': ['id'],
            },
        ),
        migrations.CreateModel(
            name='Role',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('name', models.CharField(max_length=32, unique=True, verbose_name='名称')),
                ('code', models.CharField(max_length=32, unique=True, verbose_name='代码')),
                ('sequence', models.SmallIntegerField(default=0, verbose_name='排序值')),
                ('menus', models.ManyToManyField(blank=True, to='systems.Menu', verbose_name='菜单')),
                ('model_perms', models.ManyToManyField(blank=True, to='auth.Permission', verbose_name='model权限')),
                ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='systems.Role', verbose_name='父级角色')),
            ],
            options={
                'verbose_name': '角色',
                'verbose_name_plural': '角色',
            },
        ),
        migrations.CreateModel(
            name='Group',
            fields=[
                ('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.Group')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('code', models.CharField(max_length=32, unique=True, verbose_name='代码')),
                ('sequence', models.SmallIntegerField(default=0, verbose_name='排序值')),
                ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='systems.Group', verbose_name='父级角色')),
                ('roles', models.ManyToManyField(blank=True, to='systems.Role', verbose_name='roles')),
            ],
            options={
                'verbose_name': '分组',
                'verbose_name_plural': '分组',
            },
            bases=('auth.group', models.Model),
            managers=[
                ('objects', django.contrib.auth.models.GroupManager()),
            ],
        ),
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('password', models.CharField(max_length=128, verbose_name='password')),
                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('username', models.CharField(db_index=True, max_length=32, unique=True)),
                ('realname', models.CharField(blank=True, default='图书馆管理员', max_length=32, verbose_name='真实名字')),
                ('email', models.EmailField(blank=True, default='itimor@126.com', max_length=254, verbose_name='邮箱')),
                ('avatar', models.CharField(default='http://m.imeitou.com/uploads/allimg/2017110610/b3c433vwhsk.jpg', max_length=255)),
                ('status', models.BooleanField(default=True)),
                ('is_admin', models.BooleanField(default=False)),
                ('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user_set', related_query_name='user', to='systems.Group', verbose_name='group')),
                ('model_perms', models.ManyToManyField(blank=True, related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
                ('roles', models.ManyToManyField(blank=True, related_name='user_set', related_query_name='user', to='systems.Role', verbose_name='roles')),
            ],
            options={
                'verbose_name': '用户',
                'verbose_name_plural': '用户',
            },
        ),
    ]


================================================
FILE: backend/systems/migrations/__init__.py
================================================


================================================
FILE: backend/systems/models.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from django.db import models
from django.contrib.auth.models import Permission, Group, GroupManager
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
from systems.models import *
from common.models import BaseModel

menu_type = {
    1: '模块',
    2: '菜单',
    3: '操作',
}

operate_type = {
    'none': '无',
    'add': '新增',
    'del': '删除',
    'update': '编辑',
    'view': '查看',
}


class Menu(BaseModel):
    parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='父级菜单')
    name = models.CharField(max_length=32, verbose_name='菜单名称')
    code = models.CharField(max_length=32, verbose_name='菜单代码')
    curl = models.CharField(max_length=101, verbose_name='菜单URL')
    icon = models.CharField(max_length=32, blank=True, verbose_name='菜单图标')
    hidden = models.BooleanField(default=False, verbose_name='菜单是否隐藏')
    no_cache = models.BooleanField(default=True, verbose_name='菜单是否缓存')
    active_menu = models.CharField(max_length=32, blank=True, verbose_name='激活菜单')
    sequence = models.SmallIntegerField(default=0, verbose_name='排序值')
    type = models.CharField(max_length=1, choices=tuple(menu_type.items()), default=2, verbose_name='菜单类型')
    status = models.BooleanField(default=True, verbose_name='状态')
    operate = models.CharField(max_length=11, choices=tuple(operate_type.items()), default='none', verbose_name='操作类型')

    def __str__(self):
        return "{parent}{name}".format(name=self.name, parent="%s-->" % self.parent.name if self.parent else '')

    class Meta:
        verbose_name = '菜单'
        verbose_name_plural = verbose_name
        ordering = ['id']


class Role(BaseModel):
    parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='父级角色')
    name = models.CharField(max_length=32, unique=True, verbose_name='名称')
    code = models.CharField(max_length=32, unique=True, verbose_name='代码')
    sequence = models.SmallIntegerField(default=0, verbose_name='排序值')
    menus = models.ManyToManyField(Menu, blank=True, verbose_name='菜单')
    model_perms = models.ManyToManyField(Permission, blank=True, verbose_name='model权限')

    def __str__(self):
        return "{parent}{name}".format(name=self.name, parent="%s-->" % self.parent.name if self.parent else '')

    class Meta:
        verbose_name = '角色'
        verbose_name_plural = verbose_name
        ordering = ['id']


class Group(BaseModel, Group):
    parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='父级角色')
    code = models.CharField(max_length=32, unique=True, verbose_name='代码')
    sequence = models.SmallIntegerField(default=0, verbose_name='排序值')
    roles = models.ManyToManyField(Role, verbose_name='roles', blank=True, )

    def __str__(self):
        return "{parent}{name}".format(name=self.name, parent="%s-->" % self.parent.name if self.parent else '')

    class Meta:
        verbose_name = '分组'
        verbose_name_plural = verbose_name
        ordering = ['id']

    objects = GroupManager()  # 创建用户


class PermissionsMixin(models.Model):
    group = models.ForeignKey(
        Group,
        verbose_name='group',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="user_set",
        related_query_name="user",
    )
    roles = models.ManyToManyField(
        Role,
        verbose_name='roles',
        blank=True,
        related_name="user_set",
        related_query_name="user",
    )
    model_perms = models.ManyToManyField(
        Permission,
        verbose_name='user permissions',
        blank=True,
        related_name="user_set",
        related_query_name="user",
    )

    class Meta:
        abstract = True
        ordering = ['id']


class UserManager(BaseUserManager):
    def create_user(self, username, password=None):
        """
        username 是唯一标识,没有会报错
        """

        if not username:
            raise ValueError('Users must have an username')

        user = self.model(
            username=username,
        )
        user.set_password(password)  # 检测密码合理性
        user.save(using=self._db)  # 保存密码
        return user

    def create_superuser(self, username, password):
        user = self.create_user(username=username,
                                password=password,
                                )
        user.is_admin = True  # 比创建用户多的一个字段
        user.save(using=self._db)
        return user


class User(BaseModel, PermissionsMixin, AbstractBaseUser):
    username = models.CharField(max_length=32, unique=True, db_index=True)
    realname = models.CharField(max_length=32, default="图书馆管理员", blank=True, verbose_name='真实名字')
    email = models.EmailField(blank=True, default="itimor@126.com", verbose_name='邮箱')
    avatar = models.CharField(max_length=255, default='https://pica.zhimg.com/80/v2-e4e933375b971b0941907ff9d7985188_640w.jpg')
    status = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    USERNAME_FIELD = 'username'  # 必须有一个唯一标识--USERNAME_FIELD

    @property
    def is_staff(self):
        return self.is_admin

    def has_perm(self, perm, obj=None):
        return self.is_admin

    def has_module_perms(self, app_label):
        return self.is_admin

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = '用户'
        verbose_name_plural = verbose_name
        ordering = ['id']

    objects = UserManager()  # 创建用户


================================================
FILE: backend/systems/permissions.py
================================================
# -*- coding: utf-8 -*-
# author: itimor


from rest_framework.permissions import BasePermission
from systems.models import *
from itertools import chain

parse_method_action = {
    'GET': 'view',
    'POST': 'add',
    'PUT': 'change',
    'PATCH': 'change',
    'DELETE': 'delete',
}

ignore_path = [
    '/api/sys/auth/jwt-token-auth/',
    '/api/sys/auth/getuserinfo/',
    '/api/sys/auth/getmenubutons/',
]


def check_permission(request, perm):
    user = User.objects.get(username=request.user)

    if user.is_admin:
        return True

    if request.path in ignore_path:
        return True

    user_roles = user.roles.all()
    group_roles = user.group.roles.all()
    all_roles = sorted(chain(user_roles, group_roles), key=lambda t: t.id, reverse=True)
    perms = Permission.objects.filter(role__in=all_roles)
    for i in perms:
        if i.codename == perm:
            return True


class IsOwnerRoles(BasePermission):

    def has_permission(self, request, view):
        app = view.get_view_name().split()
        object = ''.join(app[:-1]).lower()
        perm = 'view_{}'.format(object)
        return check_permission(request, perm)

    def has_object_permission(self, request, view, obj):
        app_label = obj._meta.app_label
        model = obj._meta.object_name.lower()
        perm = '{}_{}'.format(parse_method_action[request.method], model)
        return check_permission(request, perm)


================================================
FILE: backend/systems/serializers.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from systems.models import *
from systems.menus import init_menu
from rest_framework import serializers


class UserReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'
        depth = 1


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'
        extra_kwargs = {'password': {'write_only': True, 'required': False}}

    def create(self, validated_data):
        roles = validated_data.pop('roles')
        obj = User.objects.create(**validated_data)
        if len(roles) > 0:
            obj.roles.set(roles)
        else:
            role = Role.objects.get(id=1)
            obj.roles.add(role)
        try:
            obj.set_password(validated_data['password'])
        except:
            pass
        obj.save()
        return obj

    def update(self, instance, validated_data):
        roles = validated_data.pop('roles')
        instance.username = validated_data.get('username', instance.username)
        instance.realname = validated_data.get('realname', instance.realname)
        instance.group = validated_data.get('group', instance.group)
        instance.email = validated_data.get('email', instance.email)
        instance.avatar = validated_data.get('avatar', instance.avatar)
        instance.status = validated_data.get('status', instance.status)
        instance.memo = validated_data.get('memo', instance.memo)
        try:
            instance.set_password(validated_data['password'])
        except:
            pass
        instance.roles.set(roles)
        instance.save()
        return instance


class RoleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Role
        fields = '__all__'


class GroupSerializer(serializers.ModelSerializer):
    user_set = UserSerializer(many=True, read_only=True)

    class Meta:
        model = Group
        fields = '__all__'


class PermissionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Permission
        fields = '__all__'


class MenuSerializer(serializers.ModelSerializer):
    class Meta:
        model = Menu
        fields = '__all__'

    def create(self, validated_data):
        obj = Menu.objects.create(**validated_data)
        if obj.type == 2:
            init_menu(obj)
        return obj


================================================
FILE: backend/systems/urls.py
================================================
# -*- coding: utf-8 -*-
# author: itimor


from django.conf.urls import url, include
from rest_framework import routers
from rest_auth.views import PasswordChangeView
from systems.views import UserViewSet, GroupViewSet, RoleViewSet, PermissionViewSet, MenuViewSet, AuthViewSet, \
    ObtainJSONWebToken

router = routers.DefaultRouter()

router.register(r'user', UserViewSet)
router.register(r'group', GroupViewSet)
router.register(r'role', RoleViewSet)
router.register(r'perm', PermissionViewSet)
router.register(r'menu', MenuViewSet)
router.register(r'auth', AuthViewSet)

urlpatterns = [
    url(r'^auth/changepwd/', PasswordChangeView.as_view(), name='changepwd'),
    # token认证
    url(r'^auth/jwt-token-auth/', ObtainJSONWebToken.as_view(), name='rest_framework_token'),
    url(r'^auth/api-token-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

urlpatterns += router.urls


================================================
FILE: backend/systems/views.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from systems.serializers import *
from common.views import ModelViewSet, FKModelViewSet, JsonResponse, BulkModelMixin
from rest_framework.decorators import action
from systems.menus import get_menus_by_user, set_menu
from common import status
from collections import OrderedDict
from rest_framework_jwt.serializers import JSONWebTokenSerializer
from rest_framework_jwt.views import JSONWebTokenAPIView, jwt_response_payload_handler
from rest_framework_jwt.settings import api_settings
from datetime import datetime


class UserViewSet(BulkModelMixin):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    search_fields = ['username']
    filter_fields = ['username', 'group', 'roles']
    ordering_fields = ['username', 'status']


class GroupViewSet(BulkModelMixin):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    search_fields = ['name']
    filter_fields = ['name']
    ordering_fields = ['parent_id', 'sequence']


class RoleViewSet(BulkModelMixin):
    queryset = Role.objects.all()
    serializer_class = RoleSerializer
    search_fields = ['name']
    filter_fields = ['name']
    ordering_fields = ['parent_id', 'sequence']


class PermissionViewSet(ModelViewSet):
    queryset = Permission.objects.all()
    serializer_class = PermissionSerializer
    search_fields = ['name']
    filter_fields = ['name']
    ordering_fields = ['name']


class MenuViewSet(BulkModelMixin):
    queryset = Menu.objects.all()
    serializer_class = MenuSerializer
    search_fields = ['name']
    filter_fields = ['name']
    ordering_fields = ['parent_id', 'sequence']


class AuthViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(methods=['get'], url_path='getuserinfo', detail=False)
    def getuserinfo(self, request):
        user = request.user
        user_obj = User.objects.get(username=user)

        data, roles = get_menus_by_user(user)

        if len(data) > 0:
            topmenuid = data[0].parent_id
            if not topmenuid:
                topmenuid = data[0].id

        menus = set_menu(data, topmenuid)

        ip = request.META.get("HTTP_X_FORWARDED_FOR", "")
        if not ip:
            ip = request.META.get('REMOTE_ADDR', "")

        data = {'menus': menus, 'username': user_obj.username, 'avatar': user_obj.avatar, 'memo': user_obj.memo,
                'ip': ip, 'user_id': user_obj.id, 'roles': roles}
        return JsonResponse(OrderedDict([
            ('results', data)
        ], code=status.HTTP_200_OK))

    @action(methods=['get'], url_path='getmenubutons', detail=False)
    def getmenubutons(self, request):
        user = request.user
        user_obj = User.objects.get(username=user)
        buttons = []

        if user_obj.is_admin:
            buttons = ['add', 'del', 'update', 'view']
        else:
            menucode = request.GET['menucode']

            match_menu = Menu.objects.get(code=menucode)

            data, roles = get_menus_by_user(user)
            for item in data:
                if item.parent_id == match_menu.id:
                    buttons.append(item.operate)
        data = buttons
        return JsonResponse(OrderedDict([
            ('results', data)
        ], code=status.HTTP_200_OK))


class ObtainJSONWebToken(JSONWebTokenAPIView):
    serializer_class = JSONWebTokenSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)

        if serializer.is_valid():
            user = serializer.object.get('user') or request.user
            token = serializer.object.get('token')
            response_data = jwt_response_payload_handler(token, user, request)
            response = JsonResponse(OrderedDict([
                ('results', response_data)
            ], code=status.HTTP_200_OK))
            if api_settings.JWT_AUTH_COOKIE:
                expiration = (datetime.utcnow() +
                              api_settings.JWT_EXPIRATION_DELTA)
                response.set_cookie(api_settings.JWT_AUTH_COOKIE,
                                    token,
                                    expires=expiration,
                                    httponly=True)
            return response

        return JsonResponse(OrderedDict([
            ('results', serializer.errors)
        ], code=status.HTTP_500_INTERNAL_SERVER_ERROR))


================================================
FILE: backend/tickets/__init__.py
================================================
# -*- coding: utf-8 -*-
# author: itimor



================================================
FILE: backend/tickets/filters.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from tickets.models import *
from django_filters import rest_framework as filters


class TicketFilter(filters.FilterSet):
    class Meta:
        model = Ticket

        fields = {
            'id': ['exact'],
            'name': ['exact'],
            'participant': ['exact'],
            'create_user__username': ['exact'],
            "transition__attribute_type": ['exact', "lt"],
        }


================================================
FILE: backend/tickets/management/__init__.py
================================================


================================================
FILE: backend/tickets/management/commands/__init__.py
================================================


================================================
FILE: backend/tickets/management/commands/init_ticket.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.core.management.base import BaseCommand, CommandError
from systems.models import *
from systems.menus import init_menu


class Command(BaseCommand):
    help = '初始化工作流'

    def handle(self, *args, **options):
        topmenu = Menu.objects.get(name='top', code='top')
        self.stdout.write(self.style.SUCCESS('############ 初始化工单菜单 ###########'))
        ticketmenu = Menu.objects.create(name='工单系统', code='ticket', curl='/ticket', icon='ticket', sequence=4, type=1,
                                      parent_id=topmenu.id)
        menumodel = Menu.objects.create(name='新建工单', code='new_ticket', curl='/new_ticket', icon='new_ticket', sequence=10, type=2,
                                        parent_id=ticketmenu.id)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='编辑工单', code='u_ticket', curl='/u_ticket/:id', icon='u_ticket', sequence=10, type=2,
                                        hidden=True, active_menu='/new_ticket', parent_id=ticketmenu.id)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='审批工单', code='s_ticket', curl='/s_ticket/:id', icon='s_ticket', sequence=10, type=2,
                                        hidden=True, active_menu='/todo_ticket', parent_id=ticketmenu.id)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='我的工单', code='my_ticket', curl='/my_ticket', icon='my_ticket', sequence=30, type=2,
                                        no_cache=True, parent_id=ticketmenu.id)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='我的待办', code='todo_ticket', curl='/todo_ticket', icon='todo_ticket', sequence=40, type=2,
                                        no_cache=True, parent_id=ticketmenu.id)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='所有工单', code='all_ticket', curl='/all_ticket', icon='all_ticket', sequence=90, type=2,
                                        no_cache=True, parent_id=ticketmenu.id)
        init_menu(menumodel)
        self.stdout.write(self.style.SUCCESS('初始化完成'))


================================================
FILE: backend/tickets/migrations/0001_initial.py
================================================
# Generated by Django 3.0.3 on 2021-06-27 00:05

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('workflows', '0001_initial'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='Ticket',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('name', models.CharField(blank=True, default='', max_length=112, verbose_name='标题')),
                ('sn', models.CharField(blank=True, help_text='工单的流水号', max_length=25, verbose_name='流水号')),
                ('participant', models.CharField(blank=True, default='', max_length=50, verbose_name='当前处理人')),
                ('customfield', models.TextField(default=[], verbose_name='所有表单数据')),
                ('relation', models.TextField(blank=True, default='', help_text='创建人、处理人,用于查询', verbose_name='工单关联人')),
                ('create_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='创建者')),
                ('state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.State', verbose_name='当前状态')),
                ('transition', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='workflows.Transition', verbose_name='进行状态')),
                ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.Workflow', verbose_name='工作流')),
            ],
            options={
                'verbose_name': '工单记录',
                'verbose_name_plural': '工单记录',
            },
        ),
        migrations.CreateModel(
            name='TicketUser',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('username', models.CharField(max_length=100, verbose_name='关系人')),
                ('in_process', models.BooleanField(default=False, verbose_name='待处理中')),
                ('worked', models.BooleanField(default=False, verbose_name='处理过')),
                ('ticket', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tickets.Ticket')),
            ],
            options={
                'verbose_name': '工单关系人',
                'verbose_name_plural': '工单关系人',
            },
        ),
        migrations.CreateModel(
            name='TicketFlowLog',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('suggestion', models.CharField(blank=True, max_length=140, verbose_name='审批意见')),
                ('participant', models.CharField(blank=True, default='', max_length=50, verbose_name='处理人')),
                ('intervene_type', models.CharField(choices=[(0, '转交操作'), (1, '接单操作'), (2, '评论操作'), (3, '删除操作'), (4, '强制关闭操作'), (5, '强制修改状态操作'), (6, '撤回')], default=0, max_length=1, verbose_name='干预类型')),
                ('state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.State', verbose_name='当前状态')),
                ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.Ticket', verbose_name='工单')),
                ('transition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.Transition', verbose_name='流转')),
            ],
            options={
                'verbose_name': '工单流转日志',
                'verbose_name_plural': '工单流转日志',
            },
        ),
        migrations.CreateModel(
            name='TicketCustomField',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('field_value', models.TextField(blank=True, default='', verbose_name='字段值')),
                ('customfield', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.CustomField', verbose_name='字段')),
                ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.Ticket', verbose_name='工单')),
            ],
            options={
                'verbose_name': '工单自定义字段值',
                'verbose_name_plural': '工单自定义字段值',
            },
        ),
    ]


================================================
FILE: backend/tickets/migrations/__init__.py
================================================


================================================
FILE: backend/tickets/models.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.db import models
from common.models import BaseModel
from workflows.models import *
from systems.models import User


class Ticket(BaseModel):
    """
    工单记录
    """
    name = models.CharField(u'标题', max_length=112, blank=True, default='')
    sn = models.CharField(u'流水号', max_length=25, blank=True, help_text="工单的流水号")
    create_user = models.ForeignKey(User, verbose_name='创建者', blank=True, null=True, on_delete=models.SET_NULL)
    participant = models.CharField('当前处理人', max_length=50, default='', blank=True)
    workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='工作流')
    state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='当前状态')
    transition = models.ForeignKey(Transition, on_delete=models.SET_NULL, blank=True, null=True, verbose_name='进行状态')
    customfield = models.TextField('所有表单数据', default=[])
    relation = models.TextField('工单关联人', default='', blank=True, help_text='创建人、处理人,用于查询')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '工单记录'
        verbose_name_plural = verbose_name


intervene_type = {
    0: '转交操作',
    1: '接单操作',
    2: '评论操作',
    3: '删除操作',
    4: '强制关闭操作',
    5: '强制修改状态操作',
    6: '撤回',
}


class TicketFlowLog(BaseModel):
    """
    工单流转日志
    """
    ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='工单')
    suggestion = models.CharField('审批意见', max_length=140, blank=True)
    transition = models.ForeignKey(Transition, on_delete=models.CASCADE, verbose_name='流转')
    participant = models.CharField('处理人', max_length=50, default='', blank=True)
    state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='当前状态')
    intervene_type = models.CharField(max_length=1, choices=tuple(intervene_type.items()), default=0,
                                      verbose_name='干预类型')

    class Meta:
        verbose_name = '工单流转日志'
        verbose_name_plural = verbose_name


field_type = {
    10: '字符串',
    15: '整形',
    20: '浮点型',
    25: '布尔',
    30: '日期',
    35: '时间',
    40: '日期时间',
    45: '单选框',
    50: '多选框',
    55: '下拉列表',
    60: '多选下拉列表',
    65: '文本域',
    70: '用户名',
    75: '多选的用户名',
}


class TicketCustomField(BaseModel):
    """
    工单自定义字段, 工单自定义字段实际的值。
    """
    ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='工单')
    customfield = models.ForeignKey(CustomField, on_delete=models.CASCADE, verbose_name='字段')
    field_value = models.TextField('字段值', default='', blank=True)

    class Meta:
        verbose_name = '工单自定义字段值'
        verbose_name_plural = verbose_name


class TicketUser(BaseModel):
    """
    工单关系人, 用于加速待办工单及关联工单列表查询
    """
    ticket = models.ForeignKey(Ticket, null=True, blank=True, on_delete=models.SET_NULL)
    username = models.CharField('关系人', max_length=100)
    in_process = models.BooleanField('待处理中', default=False)
    worked = models.BooleanField('处理过', default=False)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = '工单关系人'
        verbose_name_plural = verbose_name


================================================
FILE: backend/tickets/serializers.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from tickets.models import *
from workflows.models import *
from rest_framework import serializers
from utils.index import gen_time_pid
import json


class TicketReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = Ticket
        fields = '__all__'
        depth = 1


class TicketSerializer(serializers.ModelSerializer):
    class Meta:
        model = Ticket
        fields = '__all__'

    def create(self, validated_data):
        cur_user = self.context['request'].user

        workflow = validated_data["workflow"]
        transition = validated_data["transition"]
        customfield_list = validated_data["customfield"]

        # save ticket
        validated_data["sn"] = gen_time_pid(workflow.ticket_sn_prefix)
        ticket = Ticket.objects.create(**validated_data)

        # save ticketlog
        ticketlog = dict()
        ticketlog["ticket"] = ticket
        ticketlog["suggestion"] = "没啥意见"
        ticketlog["state"] = transition.source_state
        ticketlog["transition"] = transition
        ticketlog["participant"] = cur_user
        TicketFlowLog.objects.create(**ticketlog)

        # save customfield
        print(ticket.create_time)
        field_models = []
        for item in json.loads(customfield_list):
            if item['field_key'] == "create_user":
                field_models.append(
                    TicketCustomField(ticket=ticket, customfield_id=int(item['customfield']),
                                      field_value=ticket.create_user),
                )
            elif item['field_key'] == "create_time":
                field_models.append(
                    TicketCustomField(ticket=ticket, customfield_id=int(item['customfield']),
                                      field_value=ticket.create_time),
                )
            elif item['field_key'] == "group":
                field_models.append(
                    TicketCustomField(ticket=ticket, customfield_id=int(item['customfield']),
                                      field_value=ticket.create_user.group),
                )
            elif item['field_key'] == "id":
                field_models.append(
                    TicketCustomField(ticket=ticket, customfield_id=int(item['customfield']),
                                      field_value=ticket.create_user.id),
                )
            else:
                field_models.append(
                    TicketCustomField(ticket=ticket, customfield_id=int(item['customfield']),
                                      field_value=item['field_value'])
                )
        TicketCustomField.objects.bulk_create(field_models)

        # save ticketuser
        TicketUser.objects.create(ticket=ticket, username=cur_user, worked=True)
        TicketUser.objects.create(ticket=ticket, username=ticket.participant, in_process=True)

        return ticket

    def update(self, instance, validated_data):
        cur_user = self.context['request'].user
        instance.name = validated_data.get('name', instance.name)
        instance.workflow = validated_data.get('workflow', instance.workflow)
        instance.sn = validated_data.get('sn', instance.sn)
        instance.state = validated_data.get('state', instance.state)
        instance.create_user = validated_data.get('create_user', instance.create_user)
        instance.participant = validated_data.get('participant', instance.participant)
        instance.transition = validated_data.get('transition', instance.transition)
        instance.customfield = validated_data.get('customfield', instance.customfield)
        instance.memo = validated_data.get('memo', instance.memo)

        # update relation
        relation = instance.relation.split(',')
        relation.append(validated_data['relation'])
        instance.relation = ','.join(set(relation))
        instance.save()

        # save ticketlog
        ticketlog = dict()
        ticketlog["ticket"] = instance
        ticketlog["suggestion"] = instance.memo
        ticketlog["state"] = instance.transition.source_state
        ticketlog["transition"] = instance.transition
        ticketlog["participant"] = cur_user
        TicketFlowLog.objects.create(**ticketlog)

        # save customfield
        customfield_list = json.loads(instance.customfield)
        for item in customfield_list:
            TicketCustomField.objects.filter(id=item["id"]).update(field_value=item["field_value"])

        # save ticketuser
        TicketUser.objects.create(ticket=instance, username=cur_user, worked=True)
        TicketUser.objects.create(ticket=instance, username=instance.participant, in_process=True)
        return instance


class TicketFlowLogReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = TicketFlowLog
        fields = '__all__'
        depth = 2


class TicketFlowLogSerializer(serializers.ModelSerializer):
    class Meta:
        model = TicketFlowLog
        fields = '__all__'


class TicketCustomFieldReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = TicketCustomField
        fields = '__all__'
        depth = 1


class TicketCustomFieldSerializer(serializers.ModelSerializer):
    class Meta:
        model = TicketCustomField
        fields = '__all__'


class TicketUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = TicketUser
        fields = '__all__'


================================================
FILE: backend/tickets/urls.py
================================================
# -*- coding: utf-8 -*-
# author: itimor


from django.conf.urls import url, include
from rest_framework import routers
from tickets.views import TicketViewSet, TicketFlowLogViewSet, TicketCustomFieldViewSet, TicketUserViewSet

router = routers.DefaultRouter()

router.register(r'ticket', TicketViewSet)
router.register('ticketflowlog', TicketFlowLogViewSet)
router.register(r'ticketcustomfield', TicketCustomFieldViewSet)
router.register(r'ticketuser', TicketUserViewSet)

urlpatterns = [
]

urlpatterns += router.urls


================================================
FILE: backend/tickets/views.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from tickets.serializers import *
from tickets.filters import *
from common.views import ModelViewSet, FKModelViewSet, JsonResponse, BulkModelMixin


class TicketViewSet(BulkModelMixin):
    queryset = Ticket.objects.all()
    serializer_class = TicketSerializer
    filterset_class = TicketFilter
    search_fields = ['name']
    ordering_fields = ['state']

    def get_serializer_class(self):
        if self.action in ['list', 'retrieve'] or self.resultData:
            return TicketReadSerializer
        return TicketSerializer

    def get_queryset(self):
        try:
            user = User.objects.get(username=self.request.user)
            if user.is_admin:
                return Ticket.objects.all()
            else:
                return Ticket.objects.filter(relation__icontains=self.request.user).distinct()
        except Exception as e:
            print(e)
            return Ticket.objects.all()


class TicketFlowLogViewSet(BulkModelMixin):
    queryset = TicketFlowLog.objects.all()
    serializer_class = TicketFlowLogSerializer
    search_fields = ['ticket']
    filter_fields = ['ticket', 'state']

    def get_serializer_class(self):
        if self.action in ['list', 'retrieve'] or self.resultData:
            return TicketFlowLogReadSerializer
        return TicketFlowLogSerializer


class TicketCustomFieldViewSet(BulkModelMixin):
    queryset = TicketCustomField.objects.all()
    serializer_class = TicketCustomFieldSerializer
    filter_fields = ['ticket', 'customfield']

    def get_serializer_class(self):
        if self.action in ['list', 'retrieve'] or self.resultData:
            return TicketCustomFieldReadSerializer
        return TicketCustomFieldSerializer


class TicketUserViewSet(BulkModelMixin):
    queryset = TicketUser.objects.all()
    serializer_class = TicketUserSerializer
    search_fields = ['username']
    filter_fields = ['username', 'in_process', 'worked']


================================================
FILE: backend/tools/__init__.py
================================================
# -*- coding: utf-8 -*-
# author: timor



================================================
FILE: backend/tools/filesize.py
================================================
# -*- coding: utf-8 -*-
# author: timor

import math


def convert_size(size_bytes):
    if size_bytes == 0:
        return "0B"
    size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
    i = int(math.floor(math.log(size_bytes, 1024)))
    p = math.pow(1024, i)
    s = round(size_bytes / p, 2)
    return "%s %s" % (s, size_name[i])


================================================
FILE: backend/tools/migrations/0001_initial.py
================================================
# Generated by Django 3.0.3 on 2021-06-27 00:05

from django.db import migrations, models
import tools.storage


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='FileUpload',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('file', models.FileField(blank=True, upload_to='./tmp', verbose_name='上传文件')),
            ],
            options={
                'verbose_name': '文件上传',
                'verbose_name_plural': '文件上传',
            },
        ),
        migrations.CreateModel(
            name='RequestEvent',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('url', models.CharField(db_index=True, max_length=255, verbose_name='请求URI')),
                ('method', models.CharField(db_index=True, max_length=20, verbose_name='请求方法')),
                ('query_string', models.TextField(verbose_name='请求内容')),
                ('user', models.CharField(max_length=255, null=True, verbose_name='用户')),
                ('remote_ip', models.CharField(db_index=True, max_length=50, null=True, verbose_name='请求IP')),
            ],
            options={
                'verbose_name': '请求事件',
                'verbose_name_plural': '请求事件',
                'ordering': ['-create_time'],
            },
        ),
        migrations.CreateModel(
            name='SimpleModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=255, unique=True, verbose_name='名称')),
            ],
        ),
        migrations.CreateModel(
            name='Upload',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('username', models.CharField(max_length=20, verbose_name='上传用户')),
                ('file', models.FileField(blank=True, upload_to=tools.storage.PathAndRename('./'), verbose_name='上传文件')),
                ('archive', models.CharField(blank=True, default='其他', max_length=201, null=True, verbose_name='文件归档')),
                ('filename', models.CharField(blank=True, max_length=201, null=True, verbose_name='文件名')),
                ('filepath', models.CharField(blank=True, max_length=201, null=True, verbose_name='文件路径')),
                ('type', models.CharField(blank=True, max_length=100, null=True, verbose_name='文件类型')),
                ('size', models.CharField(blank=True, max_length=20, null=True, verbose_name='文件大小')),
            ],
            options={
                'verbose_name': '文件上传',
                'verbose_name_plural': '文件上传',
            },
        ),
    ]


================================================
FILE: backend/tools/migrations/__init__.py
================================================


================================================
FILE: backend/tools/models.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from django.db import models
from common.models import BaseModel
from tools.filesize import convert_size
from tools.storage import PathAndRename
import os


class Upload(BaseModel):
    username = models.CharField(max_length=20, verbose_name=u'上传用户')
    file = models.FileField(upload_to=PathAndRename("./"), blank=True, verbose_name=u'上传文件')
    archive = models.CharField(max_length=201, default=u'其他', null=True, blank=True, verbose_name=u'文件归档')
    filename = models.CharField(max_length=201, null=True, blank=True, verbose_name=u'文件名')
    filepath = models.CharField(max_length=201, null=True, blank=True, verbose_name=u'文件路径')
    type = models.CharField(max_length=100, null=True, blank=True, verbose_name=u'文件类型')
    size = models.CharField(max_length=20, null=True, blank=True, verbose_name=u'文件大小')

    def save(self, *args, **kwargs):
        from re import sub
        self.size = '{}'.format(convert_size(self.file.size))
        filename = os.path.splitext(self.file.name)
        self.filename = '{}-{}{}'.format(sub('\W+', '', filename[0]), self.create_time, filename[1]).replace(' ', '_')
        self.filepath = '{}/{}'.format(self.archive, self.filename)
        super(Upload, self).save(*args, **kwargs)

    def __str__(self):
        return self.filepath

    class Meta:
        verbose_name = u'文件上传'
        verbose_name_plural = u'文件上传'


class FileUpload(models.Model):
    file = models.FileField(upload_to=("./tmp"), blank=True, verbose_name=u'上传文件')

    class Meta:
        verbose_name = u'文件上传'
        verbose_name_plural = u'文件上传'


class RequestEvent(BaseModel):
    url = models.CharField(max_length=255, null=False, db_index=True, verbose_name='请求URI')
    method = models.CharField(max_length=20, null=False, db_index=True, verbose_name='请求方法')
    query_string = models.TextField(verbose_name='请求内容')
    user = models.CharField(max_length=255, null=True, verbose_name='用户')
    remote_ip = models.CharField(max_length=50, null=True, db_index=True, verbose_name='请求IP')

    class Meta:
        ordering = ['-create_time']
        verbose_name = '请求事件'
        verbose_name_plural = verbose_name


class SimpleModel(models.Model):
    name = models.CharField(max_length=255, unique=True, verbose_name='名称')


================================================
FILE: backend/tools/serializers.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from rest_framework import serializers
from tools.models import *


class UploadSerializer(serializers.ModelSerializer):
    class Meta:
        model = Upload
        fields = '__all__'


class FileUploadSerializer(serializers.ModelSerializer):
    class Meta:
        model = FileUpload
        fields = '__all__'


class RequestEventSerializer(serializers.ModelSerializer):
    class Meta:
        model = RequestEvent
        fields = '__all__'

class SimpleSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = SimpleModel
        fields = '__all__'


================================================
FILE: backend/tools/storage.py
================================================
# -*- coding: utf-8 -*-
# author: timor

import os
from django.utils.deconstruct import deconstructible
from re import sub


@deconstructible
class PathAndRename(object):
    def __init__(self, sub_path):
        self.path = sub_path

    def __call__(self, instance, file):
        filename = os.path.splitext(file)
        last_filename = "%s-%s%s" % (sub('\W+', '', filename[0]), instance.create_time, filename[1])
        return os.path.join(self.path, instance.archive, last_filename)


================================================
FILE: backend/tools/urls.py
================================================
# -*- coding: utf-8 -*-
# author: itimor


from django.conf.urls import url
from rest_framework import routers
from tools.views import UploadViewSet, FileUploadViewSet, RequestEventViewSet, SimpleViewSet

router = routers.DefaultRouter()

router.register(r'upload', UploadViewSet)
router.register(r'fileupload', FileUploadViewSet)
router.register(r'audit', RequestEventViewSet)
router.register('simple', SimpleViewSet)

urlpatterns = [
]

urlpatterns += router.urls


================================================
FILE: backend/tools/views.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from rest_framework import viewsets, permissions
from tools.models import *
from tools.serializers import *
from common.views import ModelViewSet, FKModelViewSet, JsonResponse, BulkModelMixin


class UploadViewSet(ModelViewSet):
    queryset = Upload.objects.all().order_by("-create_time")
    serializer_class = UploadSerializer
    filter_fields = ('username', 'type',)


class FileUploadViewSet(ModelViewSet):
    permission_classes = [permissions.AllowAny]
    queryset = FileUpload.objects.all()
    serializer_class = FileUploadSerializer


class RequestEventViewSet(ModelViewSet):
    queryset = RequestEvent.objects.all()
    serializer_class = RequestEventSerializer
    search_fields = ['url', 'query_string', 'user', 'remote_ip']
    filter_fields = ['method']


class SimpleViewSet(BulkModelMixin):
    queryset = SimpleModel.objects.all()
    serializer_class = SimpleSerializer
    permission_classes = [permissions.AllowAny]
    filter_fields = ['id', 'name']



================================================
FILE: backend/utils/__init__.py
================================================
# -*- coding: utf-8 -*-
# author: timor



================================================
FILE: backend/utils/get_realip.py
================================================
# -*- coding: utf-8 -*-
# author: itimor


import requests
import socket
import json

#获取外网ip信息
output = requests.get('https://ifconfig.me/all.json').json()

# 获取本机计算机ip
def get_local_ip():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()

    return ip

output['local_ip'] = get_local_ip()

## output
"""
{
  "ip_addr": "203.177.78.226",
  "remote_host": "unavailable",
  "user_agent": "python-requests/2.22.0",
  "port": 38542,
  "method": "GET",
  "encoding": "gzip, deflate",
  "mime": "*/*",
  "via": "1.1 google",
  "forwarded": "203.177.78.226, 216.239.32.21"
  "local_ip": "172.16.51.115"
}"""

with open('d:/ooxx.log', 'a+') as fn:
    print(output)
    fn.write(json.dumps(output))


================================================
FILE: backend/utils/index.py
================================================
# -*- coding: utf-8 -*-
# author: timor

from datetime import datetime, timedelta
import time


def gen_time_pid(prefix):
    pid = '{}_{}'.format(prefix, datetime.now().strftime('%Y%m%d%H%M%S') + str(time.time()).replace('.', '')[-3:])
    return pid


def diff_times_in_seconds(t1, t2):
    h1, m1, s1 = t1.hour, t1.minute, t1.second
    h2, m2, s2 = t2.hour, t2.minute, t2.second
    t1_secs = s1 + 60 * (m1 + 60 * h1)
    t2_secs = s2 + 60 * (m2 + 60 * h2)
    tc = str(timedelta(seconds=(t2_secs - t1_secs)))
    return tc


if __name__ == '__main__':
    prefix = 'xxoo'
    print(gen_time_pid(prefix))


================================================
FILE: backend/utils/mysql.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import MySQLdb


class MYSQL:
    def __init__(self, db, sql):
        self.sql = sql
        self.conn = MySQLdb.connect(
            host=db["host"],
            port=db["port"],
            user=db["user"],
            passwd=db["passwd"],
            db=db["db"],
            charset='utf8')
        self.cursor = self.conn.cursor()

    def insert(self):
        self.cursor.execute(self.sql)
        self.conn.commit()
        self.cursor.close()
        self.conn.close()
        return True

    def select(self):
        self.cursor.execute(self.sql)
        alldata = self.cursor.fetchall()
        self.cursor.close()
        self.conn.close()
        return alldata

    def update(self):
        self.cursor.execute(self.sql)
        self.conn.commit()
        self.cursor.close()
        self.conn.close()
        return True


if __name__ == '__main__':
    xxljob_info = {
        "host": "localhost",
        "port": 3306,
        "user": "root",
        "passwd": "TY%pwd123",
        "db": "xxl_job",
    }
    jobapi = MYSQL(xxljob_info)
    sql = "select * from xxl_job_group"
    data = jobapi.insert(sql)
    rep_data = []
    for item in data:
        json_data = {
            "id": item[0],
            "app_name": item[1],
            "title": item[2],
            "order": item[3],
            "address_type": item[4],
            "address_list": item[5],
        }
        rep_data.append(json_data)
    print(rep_data)


================================================
FILE: backend/utils/sendmail.py
================================================
# -*- coding: utf-8 -*-
# author: timor

import sys
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from omsBackend.settings import MAIL_ACOUNT

# 设置服务器名称、用户名、密码以及邮件后缀
mail_host = MAIL_ACOUNT["mail_host"]
mail_user = MAIL_ACOUNT["mail_user"]
mail_pass = MAIL_ACOUNT["mail_pass"]
mail_postfix = MAIL_ACOUNT["mail_postfix"]


# 发送邮件函数
def send_mail(to_list, cc_list, sub, content):
    me = mail_user + "<" + mail_user + "@" + mail_postfix + ">"
    # f = open(context)
    # msg = MIMEText(f.read(),_charset="utf-8")
    # f.close()
    # msg = MIMEText(context)
    msg = MIMEMultipart('alternative')
    msg['Subject'] = sub
    msg['From'] = me
    msg['To'] = to_list
    msg['Cc'] = cc_list
    list = msg['Cc'].split(',')
    list.append(msg['To'])
    context = MIMEText(content, _subtype='html', _charset='utf-8')  # 解决乱码
    msg.attach(context)
    try:
        send_smtp = smtplib.SMTP()
        send_smtp.connect(mail_host, 587)
        send_smtp.starttls()
        send_smtp.login(mail_user, mail_pass)

        send_smtp.sendmail(me, list, msg.as_string())
        send_smtp.close()
        return {"code": 'success', "msg": "通知邮件发送成功"}
    except Exception as e:
        print(e)
        return {"code": 'error', "msg": "通知邮件发送失败"}


if __name__ == '__main__':
    to_list = sys.argv[1]  # 收件人列表   '111@126.com'
    cc_list = sys.argv[2]  # 抄送人列表   '111@126.com;222@126.com;'
    sub = sys.argv[3]
    context = sys.argv[4]
    if send_mail(to_list, cc_list, sub, context):
        print({"code": 'success', "msg": "通知邮件发送成功"})
    else:
        print({"code": 'error', "msg": "通知邮件发送失败"})

================================================
FILE: backend/utils/sendskype.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

# 登录skype
from skpy import Skype

# skype账号
SK_ACOUNT = {
    'sk_user': 'itimor@126.com',
    'sk_pass': 'xxx'
}
SK = Skype(SK_ACOUNT["sk_user"], SK_ACOUNT["sk_pass"])


def skype_bot(user, content):
    chat = SK.chats[user]
    chat.sendMsg(content)


if __name__ == '__main__':
    skypeid = 'live:dafaricky123'
    user = '8:' + skypeid  # skypeid 前面需要加 8
    skype_bot(user, "hello,gay,你个逗比,不加好友,发你妹的消息啊")


================================================
FILE: backend/utils/test.py
================================================
#!/bin/bash

id=$1
echo $id
cp -r site-10 site-$id
cd site-$id
sed -i "s/10/$id/" *

================================================
FILE: backend/utils/time.py
================================================
# -*- coding: utf-8 -*-
# author: timor

import time
import datetime


def utc2local(utc_st):
    """
    UTC时间转本地时间 +8:00
    """
    now_stamp = time.time()
    local_time = datetime.datetime.fromtimestamp(now_stamp)
    utc_time = datetime.datetime.utcfromtimestamp(now_stamp)
    offset = local_time - utc_time
    local_st = utc_st + offset
    return local_st


def local2utc(local_st):
    """
    本地时间转UTC时间 -8:00
    """
    time_struct = time.mktime(local_st.timetuple())
    utc_st = datetime.datetime.utcfromtimestamp(time_struct)
    return utc_st


# '2015-08-28 16:43:37.283' --> 1440751417.283
# 或者 '2015-08-28 16:43:37' --> 1440751417.0
def string2timestamp(strValue):
    try:
        d = datetime.datetime.strptime(strValue, "%Y-%m-%d %H:%M:%S.%f")
        t = d.timetuple()
        timeStamp = int(time.mktime(t))
        timeStamp = float(str(timeStamp) + str("%06d" % d.microsecond)) / 1000000
        return int(timeStamp)
    except ValueError as e:
        d = datetime.datetime.strptime(strValue, "%Y-%m-%d %H:%M:%S")
        t = d.timetuple()
        timeStamp = int(time.mktime(t))
        timeStamp = float(str(timeStamp) + str("%06d" % d.microsecond)) / 1000000
        return int(timeStamp)


# 1440751417.283 --> '2015-08-28 16:43:37.283'
def timestamp2string(timeStamp):
    try:
        d = datetime.datetime.fromtimestamp(timeStamp)
        str1 = d.strftime("%Y-%m-%d %H:%M:%S.%f")
        # 2015-08-28 16:43:37.283000'
        return str1
    except Exception as e:
        pass


================================================
FILE: backend/utils/verifys.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

import re
import IPy


def is_valid_domain(value):
    domain_pattern = re.compile(
        r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|'
        r'([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|'
        r'([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.'
        r'([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$'
    )
    return True if domain_pattern.match(value) else False


def is_domain(domain):
    domain_regex = re.compile(
        r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
        re.IGNORECASE)
    return True if domain_regex.match(domain) else False


def is_ip(address):
    print(address)
    try:
        IPy.IP(address)
        return True
    except Exception as e:
        print(e)
        return False


if __name__ == '__main__':
    print(is_valid_domain('https://aa.www.baidu.com'))
    print(is_ip('22.2.2.256'))


================================================
FILE: backend/workflows/__init__.py
================================================
# -*- coding: utf-8 -*-
# author: itimor



================================================
FILE: backend/workflows/admin.py
================================================
from django.contrib import admin
from workflows.models import *

admin.site.register(WorkflowBpmn)


================================================
FILE: backend/workflows/management/__init__.py
================================================


================================================
FILE: backend/workflows/management/commands/__init__.py
================================================


================================================
FILE: backend/workflows/management/commands/init_leave.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.hashers import make_password

from workflows.models import *
from systems.models import *


class Command(BaseCommand):
    help = '假期工作流'

    def handle(self, *args, **options):
        self.stdout.write(self.style.SUCCESS('############ 配置工作流用户角色 ###########'))
        topgroup = Group.objects.get(name='top', code='top')

        group_ops = Group.objects.create(name='运维', code='ops', sequence=1, parent=topgroup)
        group_dev = Group.objects.create(name='开发', code='dev', sequence=2, parent=topgroup)
        group_hr = Group.objects.create(name='人事', code='hr', sequence=3, parent=topgroup)

        self.stdout.write(self.style.SUCCESS('############ 初始化角色 ###########'))
        toprole = Role.objects.get(name='top', code='top')

        role_ops_tl = Role.objects.create(name='运维经理', code='ops_tl', sequence=1, group=group_ops, parent=toprole)
        role_ops = Role.objects.create(name='运维', code='ops', sequence=1, group=group_ops, parent=toprole)
        role_dev_tl = Role.objects.create(name='开发经理', code='dev_tl', sequence=2, group=group_dev, parent=toprole)
        role_dev = Role.objects.create(name='开发', code='dev', sequence=2, group=group_dev, parent=toprole)
        role_hr_tl = Role.objects.create(name='人事经理', code='hr_tl', sequence=3, group=group_hr, parent=toprole)
        role_hr = Role.objects.create(name='人事', code='hr', sequence=3, group=group_hr, parent=toprole)

        self.stdout.write(self.style.SUCCESS('############ 初始化用户 ###########'))
        ops_tl = User.objects.create(username='ops_tl', password=make_password("123456"), realname="青龙",
                                     group=group_ops)
        ops_tl.roles.add(role_ops_tl)
        ops = User.objects.create(username='ops', password=make_password("123456"), realname="白虎", group=group_ops)
        ops.roles.add(role_ops)
        dev_tl = User.objects.create(username='dev_tl', password=make_password("123456"), realname="朱雀",
                                     group=group_dev)
        dev_tl.roles.add(role_dev_tl)
        dev = User.objects.create(username='dev', password=make_password("123456"), realname="玄武", group=group_dev)
        dev.roles.add(role_dev)
        hr_tl = User.objects.create(username='hr_tl', password=make_password("123456"), realname="霸下", group=group_hr)
        hr_tl.roles.add(role_hr_tl)
        hr = User.objects.create(username='hr', password=make_password("123456"), realname="青鸾", group=group_hr)
        hr.roles.add(role_hr)

        self.stdout.write(self.style.SUCCESS('############ 请假工作流 ###########'))
        ## 工作流类型
        ad_type = WorkflowType.objects.create(name='行政', code='ad', order_id=1)

        ## 工作流
        leave_wf = Workflow.objects.create(name='请假单', type=ad_type, ticket_sn_prefix='leave')

        ## 工作流字段
        # 建立内置字段
        CustomField.objects.create(field_name="申请人", order_id=1, field_attribute=True, field_type=1,
                                   field_key="create_user", workflow=leave_wf)
        CustomField.objects.create(field_name="申请时间", order_id=2, field_attribute=True, field_type=6,
                                   field_key="create_time", workflow=leave_wf)
        CustomField.objects.create(field_name="部门", order_id=3, field_attribute=True, field_type=1, field_key="group",
                                   workflow=leave_wf)
        CustomField.objects.create(field_name="工号", order_id=4, field_attribute=True, field_type=2, field_key="id",
                                   workflow=leave_wf)
        # 建立扩展字段
        c1 = CustomField.objects.create(field_name="请假时间", order_id=10, field_type=7, field_key="start_end_time",
                                        workflow=leave_wf)
        c2 = CustomField.objects.create(field_name="请假类型", order_id=30, field_type=9, field_key="type",
                                        field_choice='{"1":"病假", "2":"产假"}', workflow=leave_wf)
        c3 = CustomField.objects.create(field_name="事由说明", order_id=50, field_type=8, field_key="memo",
                                        workflow=leave_wf)
        c4 = CustomField.objects.create(field_name="领导审批", order_id=60, field_type=9, field_key="leader_radio",
                                        field_choice='{"1":"同意", "2":"不同意"}', workflow=leave_wf)
        c5 = CustomField.objects.create(field_name="人事审批", order_id=80, field_type=9, field_key="hr_radio",
                                        field_choice='{"1":"同意", "2":"不同意"}', workflow=leave_wf)

        # 建立初始和结束状态
        s1 = State.objects.create(name="开始", order_id=1, state_type=1, is_hidden=True, participant_type='none',
                                  workflow=leave_wf)
        s2 = State.objects.create(name="关闭", order_id=99, state_type=2, is_hidden=True, participant_type='none',
                                  workflow=leave_wf)
        # 建立流转状态
        s3 = State.objects.create(name="申请人-编辑中", order_id=2, participant_type='none', workflow=leave_wf)
        s3.fields.add(c1, c2, c3)
        s4 = State.objects.create(name="领导-审批中", order_id=3, participant_type='role', workflow=leave_wf)
        s4.fields.add(c4)
        s4.role_participant.add(role_ops_tl, role_dev_tl, role_hr_tl)
        s5 = State.objects.create(name="人事-审批中", order_id=4, participant_type='group', workflow=leave_wf)
        s5.fields.add(c5)
        s5.group_participant.add(group_hr)
        s6 = State.objects.create(name="结束", order_id=98, state_type=2, participant_type='none', workflow=leave_wf)

        # 建立工作流步骤
        Transition.objects.create(name=0, source_state=s1, dest_state=s3, attribute_type=0, workflow=leave_wf)
        Transition.objects.create(name=1, source_state=s1, dest_state=s4, attribute_type=1, workflow=leave_wf)

        Transition.objects.create(name=0, source_state=s3, dest_state=s3, attribute_type=0, workflow=leave_wf)
        Transition.objects.create(name=1, source_state=s3, dest_state=s4, attribute_type=1, workflow=leave_wf)
        Transition.objects.create(name=3, source_state=s3, dest_state=s6, attribute_type=3, workflow=leave_wf)

        Transition.objects.create(name=2, source_state=s4, dest_state=s3, attribute_type=2, workflow=leave_wf)
        Transition.objects.create(name=1, source_state=s4, dest_state=s5, attribute_type=1, workflow=leave_wf)

        Transition.objects.create(name=2, source_state=s5, dest_state=s3, attribute_type=2, workflow=leave_wf)
        Transition.objects.create(name=4, source_state=s5, dest_state=s2, attribute_type=5, workflow=leave_wf)

        self.stdout.write(self.style.SUCCESS('请假工作流完成'))

        self.stdout.write(self.style.SUCCESS('############ 发布工作流 ###########'))
        ## 工作流类型
        it_type = WorkflowType.objects.create(name='技术', code='it', order_id=2)

        ## 工作流
        deploy_wf = Workflow.objects.create(name='发布单', type=it_type, ticket_sn_prefix='deploy')

        ## 工作流字段
        # 建立内置字段
        CustomField.objects.create(field_name="申请人", order_id=1, field_attribute=True, field_type=1,
                                   field_key="create_user", workflow=deploy_wf)
        CustomField.objects.create(field_name="申请时间", order_id=2, field_attribute=True, field_type=6,
                                   field_key="create_time", workflow=deploy_wf)
        CustomField.objects.create(field_name="部门", order_id=3, field_attribute=True, field_type=1, field_key="group",
                                   workflow=deploy_wf)
        CustomField.objects.create(field_name="工号", order_id=4, field_attribute=True, field_type=2, field_key="id",
                                   workflow=deploy_wf)
        # 建立扩展字段
        c1 = CustomField.objects.create(field_name="发布时间", order_id=10, field_type=6, field_key="start_time",
                                        workflow=deploy_wf)
        c2 = CustomField.objects.create(field_name="发布项目", order_id=30, field_type=9, field_key="type",
                                        field_choice='{"1":"前端", "2":"后端"}', workflow=deploy_wf)
        c3 = CustomField.objects.create(field_name="发布内容", order_id=50, field_type=8, field_key="memo",
                                        workflow=deploy_wf)
        c4 = CustomField.objects.create(field_name="领导审批", order_id=60, field_type=9, field_key="leader_radio",
                                        field_choice='{"1":"同意", "2":"不同意"}', workflow=deploy_wf)
        c5 = CustomField.objects.create(field_name="运维执行", order_id=80, field_type=9, field_key="ops_radio",
                                        field_choice='{"1":"已执行", "2":"未执行"}', workflow=deploy_wf)

        # 建立初始和结束状态
        s1 = State.objects.create(name="开始", order_id=1, state_type=1, is_hidden=True, participant_type='none',
                                  workflow=deploy_wf)
        s2 = State.objects.create(name="关闭", order_id=99, state_type=2, is_hidden=True, participant_type='none',
                                  workflow=deploy_wf)
        # 建立流转状态
        s3 = State.objects.create(name="申请人-编辑中", order_id=2, participant_type='none', workflow=deploy_wf)
        s3.fields.add(c1, c2, c3)
        s4 = State.objects.create(name="领导-审批中", order_id=3, participant_type='role', workflow=deploy_wf)
        s4.fields.add(c4)
        s4.role_participant.add(role_ops_tl, role_dev_tl, role_hr_tl)
        s5 = State.objects.create(name="运维-执行中", order_id=4, participant_type='group', workflow=deploy_wf)
        s5.fields.add(c5)
        s5.group_participant.add(group_hr)
        s6 = State.objects.create(name="结束", order_id=98, state_type=2, participant_type='none', workflow=deploy_wf)

        # 建立工作流步骤
        Transition.objects.create(name=0, source_state=s1, dest_state=s3, attribute_type=0, workflow=deploy_wf)
        Transition.objects.create(name=1, source_state=s1, dest_state=s4, attribute_type=1, workflow=deploy_wf)

        Transition.objects.create(name=0, source_state=s3, dest_state=s3, attribute_type=0, workflow=deploy_wf)
        Transition.objects.create(name=1, source_state=s3, dest_state=s4, attribute_type=1, workflow=deploy_wf)
        Transition.objects.create(name=3, source_state=s3, dest_state=s6, attribute_type=3, workflow=deploy_wf)

        Transition.objects.create(name=2, source_state=s4, dest_state=s3, attribute_type=2, workflow=deploy_wf)
        Transition.objects.create(name=1, source_state=s4, dest_state=s5, attribute_type=1, workflow=deploy_wf)

        Transition.objects.create(name=2, source_state=s5, dest_state=s3, attribute_type=2, workflow=deploy_wf)
        Transition.objects.create(name=4, source_state=s5, dest_state=s2, attribute_type=5, workflow=deploy_wf)

        self.stdout.write(self.style.SUCCESS('############ 初始化角色权限 ###########'))
        menus = [34, 35, 36, 37, 38, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76,
                 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96]
        perms = [52, 56, 60, 92, 96, 48, 44, 52, 73, 74, 75, 76, 85, 86, 87, 88, 81, 82, 83, 84, 77, 78, 79, 80, 28, 32, 68, 64]
        menu_obj_list = Menu.objects.filter(id__in=menus)
        perm_obj_list = Permission.objects.filter(id__in=perms)

        role_ops_tl.menus.add(*menu_obj_list)
        role_ops_tl.model_perms.add(*perm_obj_list)
        role_ops.menus.add(*menu_obj_list)
        role_ops.model_perms.add(*perm_obj_list)
        role_dev_tl.menus.add(*menu_obj_list)
        role_dev_tl.model_perms.add(*perm_obj_list)
        role_dev.menus.add(*menu_obj_list)
        role_dev.model_perms.add(*perm_obj_list)
        role_hr_tl.menus.add(*menu_obj_list)
        role_hr_tl.model_perms.add(*perm_obj_list)
        role_hr.menus.add(*menu_obj_list)
        role_hr.model_perms.add(*perm_obj_list)
        self.stdout.write(self.style.SUCCESS('发布工作流完成'))


================================================
FILE: backend/workflows/management/commands/init_wf.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.core.management.base import BaseCommand, CommandError
from systems.models import *
from systems.menus import init_menu


class Command(BaseCommand):
    help = '初始化工作流'

    def handle(self, *args, **options):
        topmenu = Menu.objects.get(name='top', code='top')
        self.stdout.write(self.style.SUCCESS('############ 初始化工作流菜单 ###########'))
        workflowmenu = Menu.objects.create(name='工作流', code='workflow', curl='/workflow', icon='workflow', sequence=3, type=1, parent=topmenu)
        menumodel = Menu.objects.create(name='工作流类型', code='wftype', curl='/wftype', icon='wftype', sequence=10, type=2, parent=workflowmenu)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='工作流设计', code='wfset', curl='/wfset', icon='wfset', sequence=20, type=2, parent=workflowmenu)
        init_menu(menumodel)
        menumodel = Menu.objects.create(name='工作流配置', code='wfconf', curl='/wfconf/:id', icon='wfconf', sequence=30, type=2, hidden=True, active_menu='/wfset', parent=workflowmenu)
        init_menu(menumodel)
        self.stdout.write(self.style.SUCCESS('初始化完成'))


================================================
FILE: backend/workflows/migrations/0001_initial.py
================================================
# Generated by Django 3.0.3 on 2021-06-27 00:05

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('systems', '0001_initial'),
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='CustomField',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('field_attribute', models.BooleanField(default=False, verbose_name='字段是否内置')),
                ('field_type', models.CharField(choices=[(1, '字符串'), (2, '整型'), (3, '浮点型'), (4, '布尔'), (5, '日期'), (6, '日期时间'), (7, '范围日期'), (8, '文本域'), (9, '单选框'), (10, '下拉列表'), (11, '用户名'), (12, '多选框'), (13, '多选下拉'), (14, '多选用户名')], default=0, max_length=1, verbose_name='字段类型')),
                ('field_key', models.CharField(help_text='字段类型请尽量特殊,避免与系统中关键字冲突', max_length=50, verbose_name='字段标识')),
                ('field_name', models.CharField(max_length=50, verbose_name='字段名称')),
                ('order_id', models.IntegerField(default=0, verbose_name='排序')),
                ('default_value', models.CharField(blank=True, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值', max_length=100, null=True, verbose_name='默认值')),
                ('field_template', models.TextField(blank=True, default='', help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder', verbose_name='文本域模板')),
                ('boolean_field_display', models.CharField(blank=True, default='{}', help_text='当为布尔类型时候,可以支持自定义显示形式。{"1":"是","0":"否"}或{"1":"需要","0":"不需要"},注意数字也需要引号', max_length=100, verbose_name='布尔类型显示名')),
                ('field_choice', models.CharField(blank=True, default='{}', help_text='radio,checkbox,select,multiselect类型可供选择的选项,格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号', max_length=255, verbose_name='radio、checkbox、select的选项')),
                ('label', models.CharField(blank=True, default='{}', help_text='自定义标签,json格式,调用方可根据标签自行处理特殊场景逻辑,loonflow只保存文本内容', max_length=100, verbose_name='标签')),
            ],
            options={
                'verbose_name': '工作流自定义字段',
                'verbose_name_plural': '工作流自定义字段',
                'ordering': ['order_id'],
            },
        ),
        migrations.CreateModel(
            name='State',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('name', models.CharField(max_length=50, verbose_name='名称')),
                ('is_hidden', models.BooleanField(default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)', verbose_name='是否隐藏')),
                ('order_id', models.IntegerField(default=1, verbose_name='状态顺序')),
                ('state_type', models.CharField(choices=[(0, '普通状态'), (1, '初始状态'), (2, '结束状态')], default=0, max_length=1, verbose_name='状态类型')),
                ('enable_retreat', models.BooleanField(default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态', verbose_name='允许撤回')),
                ('participant_type', models.CharField(choices=[('none', '无处理人'), ('user', '个人'), ('group', '部门'), ('role', '角色')], default='none', max_length=5, verbose_name='参与者类型')),
                ('fields', models.ManyToManyField(blank=True, to='workflows.CustomField', verbose_name='可编辑字段')),
                ('group_participant', models.ManyToManyField(blank=True, to='systems.Group', verbose_name='参与组')),
                ('role_participant', models.ManyToManyField(blank=True, to='systems.Role', verbose_name='参与角色')),
                ('user_participant', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='参与用户')),
            ],
            options={
                'verbose_name': '工作流状态',
                'verbose_name_plural': '工作流状态',
                'ordering': ['order_id'],
            },
        ),
        migrations.CreateModel(
            name='Workflow',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('name', models.CharField(max_length=50, verbose_name='名称')),
                ('key', models.CharField(blank=True, max_length=168, verbose_name='流程标识key')),
                ('ticket_sn_prefix', models.CharField(default='xxoo', max_length=20, verbose_name='工单流水号前缀')),
                ('status', models.BooleanField(default=True)),
                ('view_permission_check', models.BooleanField(default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单', verbose_name='查看权限校验')),
                ('limit_expression', models.TextField(blank=True, default='{}', help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)', verbose_name='限制表达式')),
                ('display_form_str', models.TextField(blank=True, default='[]', help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='展现表单字段')),
                ('title_template', models.CharField(blank=True, default='你有一个待办工单:{title}', help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}', max_length=50, null=True, verbose_name='标题模板')),
                ('roles', models.ManyToManyField(blank=True, related_name='workflow_set', related_query_name='workflow', to='systems.Role', verbose_name='关联角色')),
            ],
            options={
                'verbose_name': '工作流',
                'verbose_name_plural': '工作流',
            },
        ),
        migrations.CreateModel(
            name='WorkflowType',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('name', models.CharField(max_length=50, verbose_name='名称')),
                ('status', models.BooleanField(default=True)),
                ('code', models.CharField(max_length=32, unique=True, verbose_name='代码')),
                ('order_id', models.IntegerField(default=1, verbose_name='状态顺序')),
            ],
            options={
                'verbose_name': '工作流类型',
                'verbose_name_plural': '工作流类型',
                'ordering': ['order_id'],
            },
        ),
        migrations.CreateModel(
            name='WorkflowBpmn',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('xml', models.TextField(blank=True, verbose_name='xml数据')),
                ('svg', models.TextField(blank=True, verbose_name='svg数据')),
                ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.Workflow', verbose_name='工作流')),
            ],
            options={
                'verbose_name': '工作流bpmn',
                'verbose_name_plural': '工作流bpmn',
            },
        ),
        migrations.AddField(
            model_name='workflow',
            name='type',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.WorkflowType', verbose_name='工作流类型'),
        ),
        migrations.CreateModel(
            name='Transition',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
                ('memo', models.TextField(blank=True, verbose_name='备注')),
                ('name', models.CharField(choices=[(0, '保存'), (1, '转交下一步'), (2, '驳回'), (3, '撤销'), (4, '关闭')], default=1, max_length=1, verbose_name='名称类型')),
                ('transition_type', models.CharField(choices=[(0, '常规流转'), (1, '定时器流转')], default=0, max_length=1, verbose_name='流转类型')),
                ('timer', models.IntegerField(default=0, help_text='流转类型设置为定时器流转时生效,单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态', verbose_name='定时器(单位秒)')),
                ('condition_expression', models.TextField(default='[]', help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state_id":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突', verbose_name='条件表达式')),
                ('attribute_type', models.CharField(choices=[(0, '草稿'), (1, '待审'), (2, '驳回'), (3, '撤销'), (4, '结束'), (5, '已关闭')], default=0, max_length=1, verbose_name='属性类型')),
                ('alert_enable', models.BooleanField(default=False, verbose_name='点击弹窗提示')),
                ('alert_text', models.CharField(blank=True, default='', max_length=100, verbose_name='弹窗内容')),
                ('dest_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dest_state', to='workflows.State', verbose_name='目的状态')),
                ('source_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='source_state', to='workflows.State', verbose_name='源状态')),
                ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.Workflow', verbose_name='工作流')),
            ],
            options={
                'verbose_name': '工作流流转',
                'verbose_name_plural': '工作流流转',
            },
        ),
        migrations.AddField(
            model_name='state',
            name='workflow',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.Workflow', verbose_name='工作流'),
        ),
        migrations.AddField(
            model_name='customfield',
            name='workflow',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workflows.Workflow', verbose_name='工作流'),
        ),
    ]


================================================
FILE: backend/workflows/migrations/__init__.py
================================================


================================================
FILE: backend/workflows/models.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from django.db import models
from common.models import BaseModel
from systems.models import *


class WorkflowType(BaseModel):
    name = models.CharField('名称', max_length=50)
    status = models.BooleanField(default=True)
    code = models.CharField(max_length=32, unique=True, verbose_name='代码')
    order_id = models.IntegerField('状态顺序', default=1)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['order_id']
        verbose_name = '工作流类型'
        verbose_name_plural = verbose_name


class Workflow(BaseModel):
    """
    工作流
    """
    name = models.CharField('名称', max_length=50)
    key = models.CharField('流程标识key', blank=True, max_length=168)
    ticket_sn_prefix = models.CharField('工单流水号前缀', default='xxoo', max_length=20)
    status = models.BooleanField(default=True)
    type = models.ForeignKey(WorkflowType, on_delete=models.CASCADE, verbose_name='工作流类型')
    roles = models.ManyToManyField(Role, verbose_name='关联角色', blank=True, related_name="workflow_set", related_query_name="workflow")
    view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单')
    limit_expression = models.TextField('限制表达式', default='{}', blank=True,
                                        help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)')
    display_form_str = models.TextField('展现表单字段', default='[]', blank=True,
                                        help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
    title_template = models.CharField('标题模板', max_length=50, default='你有一个待办工单:{title}', null=True, blank=True,
                                      help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '工作流'
        verbose_name_plural = verbose_name


field_type = {
    1: '字符串',
    2: '整型',
    3: '浮点型',
    4: '布尔',
    5: '日期',
    6: '日期时间',
    7: '范围日期',
    8: '文本域',
    9: '单选框',
    10: '下拉列表',
    11: '用户名',
    12: '多选框',
    13: '多选下拉',
    14: '多选用户名',
}


class CustomField(BaseModel):
    """自定义字段, 设定某个工作流有哪些自定义字段"""
    workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='工作流')
    field_attribute = models.BooleanField('字段是否内置', default=False)
    field_type = models.CharField(max_length=1, choices=tuple(field_type.items()), default=0, verbose_name='字段类型')
    field_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突')
    field_name = models.CharField('字段名称', max_length=50)
    # 内置 field 的 order_id 不要超过10
    order_id = models.IntegerField('排序', default=0)
    default_value = models.CharField('默认值', null=True, blank=True, max_length=100, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值')
    field_template = models.TextField('文本域模板', default='', blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder')
    boolean_field_display = models.CharField('布尔类型显示名', max_length=100, default='{}', blank=True,
                                             help_text='当为布尔类型时候,可以支持自定义显示形式。{"1":"是","0":"否"}或{"1":"需要","0":"不需要"},注意数字也需要引号')
    field_choice = models.CharField('radio、checkbox、select的选项', max_length=255, default='{}', blank=True,
                                    help_text='radio,checkbox,select,multiselect类型可供选择的选项,格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号')
    label = models.CharField('标签', max_length=100, blank=True, default='{}',
                             help_text='自定义标签,json格式,调用方可根据标签自行处理特殊场景逻辑,loonflow只保存文本内容')

    def __str__(self):
        return self.field_name

    class Meta:
        ordering = ['order_id']
        verbose_name = '工作流自定义字段'
        verbose_name_plural = verbose_name


# 0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)
state_type = {
    0: '普通状态',
    1: '初始状态',
    2: '结束状态',
}

participant_type = {
    'none': '无处理人',
    'user': '个人',
    'group': '部门',
    'role': '角色',
}


class State(BaseModel):
    """
    状态记录, 变量支持通过脚本获取
    """
    name = models.CharField('名称', max_length=50)
    workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='工作流')
    is_hidden = models.BooleanField('是否隐藏', default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)')
    order_id = models.IntegerField('状态顺序', default=1)
    state_type = models.CharField(max_length=1, choices=tuple(state_type.items()), default=0, verbose_name='状态类型')
    enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态')
    participant_type = models.CharField(max_length=5, choices=tuple(participant_type.items()), default='none', verbose_name='参与者类型')
    user_participant = models.ManyToManyField(User, blank=True, verbose_name='参与用户')
    group_participant = models.ManyToManyField(Group, blank=True, verbose_name='参与组')
    role_participant = models.ManyToManyField(Role, blank=True, verbose_name='参与角色')
    fields = models.ManyToManyField(CustomField, blank=True, verbose_name='可编辑字段')

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['order_id']
        verbose_name = '工作流状态'
        verbose_name_plural = verbose_name


transition_name = {
    0: '保存',
    1: '转交下一步',
    2: '驳回',
    3: '撤销',
    4: '关闭',
}

transition_type = {
    0: '常规流转',
    1: '定时器流转',
}

attribute_type = {
    0: '草稿',
    1: '待审',
    2: '驳回',
    3: '撤销',
    4: '结束',
    5: '已关闭',
}


class Transition(BaseModel):
    """
    工作流流转,定时器,条件(允许跳过), 条件流转与定时器不可同时存在
    """
    name = models.CharField('名称类型', max_length=1, choices=tuple(transition_name.items()), default=1)
    workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='工作流')
    transition_type = models.CharField('流转类型', max_length=1, choices=tuple(transition_type.items()), default=0)
    timer = models.IntegerField('定时器(单位秒)', default=0, help_text='流转类型设置为定时器流转时生效,单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态')
    source_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.SET_NULL, related_name="source_state", verbose_name='源状态')
    dest_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.SET_NULL, related_name="dest_state", verbose_name='目的状态')
    condition_expression = models.TextField('条件表达式', default='[]',
                                            help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state_id":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突')
    attribute_type = models.CharField(max_length=1, choices=tuple(attribute_type.items()), default=0, verbose_name='属性类型')
    alert_enable = models.BooleanField('点击弹窗提示', default=False)
    alert_text = models.CharField('弹窗内容', max_length=100, default='', blank=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '工作流流转'
        verbose_name_plural = verbose_name


"""
https://github.com/GoldSubmarine/workflow-bpmn-modeler
https://github.com/miyuesc/bpmn-process-designer
https://juejin.cn/post/6844904069736169480
"""


class WorkflowBpmn(BaseModel):
    workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='工作流')
    xml = models.TextField('xml数据', blank=True)
    svg = models.TextField('svg数据', blank=True)

    class Meta:
        verbose_name = '工作流bpmn'
        verbose_name_plural = verbose_name


================================================
FILE: backend/workflows/serializers.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from workflows.models import *
from rest_framework import serializers


class WorkflowReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = Workflow
        fields = '__all__'
        depth = 1


class WorkflowSerializer(serializers.ModelSerializer):
    class Meta:
        model = Workflow
        fields = '__all__'

    def create(self, validated_data):
        roles = validated_data['roles']
        del validated_data['roles']
        obj = Workflow.objects.create(**validated_data)
        obj.roles.set(roles)
        obj.save()

        # 建立初始和结束状态
        State.objects.create(name="开始", order_id=1, state_type=1, is_hidden=True, participant_type=0, workflow=obj)
        State.objects.create(name="关闭", order_id=99, state_type=2, is_hidden=True, participant_type=0, workflow=obj)

        # 建立内置字段
        CustomField.objects.create(field_name="申请人", order_id=1, field_attribute=True, field_type=1,
                                   field_key="create_user", workflow=obj)
        CustomField.objects.create(field_name="申请时间", order_id=2, field_attribute=True, field_type=6,
                                   field_key="create_time", workflow=obj)
        CustomField.objects.create(field_name="部门", order_id=3, field_attribute=True, field_type=1, field_key="group",
                                   workflow=obj)
        CustomField.objects.create(field_name="工号", order_id=4, field_attribute=True, field_type=1, field_key="id",
                                   workflow=obj)

        return obj


class WorkflowTypeSerializer(serializers.ModelSerializer):
    workflow_list = serializers.SerializerMethodField()
    # workflow_set = WorkflowSerializer(many=True, read_only=True,)

    def get_workflow_list(self, instance):
        a = instance.workflow_set.get_queryset().filter(status=True)
        return a.values()

    class Meta:
        model = WorkflowType
        fields = '__all__'


class StateReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = State
        fields = '__all__'
        depth = 1


class StateSerializer(serializers.ModelSerializer):
    class Meta:
        model = State
        fields = '__all__'


class TransitionReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = Transition
        fields = '__all__'
        depth = 2


class TransitionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Transition
        fields = '__all__'


class CustomFieldSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomField
        fields = '__all__'


class WorkflowBpmnSerializer(serializers.ModelSerializer):
    class Meta:
        model = WorkflowBpmn
        fields = '__all__'


================================================
FILE: backend/workflows/urls.py
================================================
# -*- coding: utf-8 -*-
# author: itimor


from django.conf.urls import url, include
from rest_framework import routers
from workflows.views import WorkflowTypeViewSet, WorkflowViewSet, StateViewSet, TransitionViewSet, CustomFieldViewSet, WorkflowBpmnViewSet

router = routers.DefaultRouter()

router.register(r'workflowtype', WorkflowTypeViewSet)
router.register(r'workflow', WorkflowViewSet)
router.register('state', StateViewSet)
router.register(r'transition', TransitionViewSet)
router.register(r'customfield', CustomFieldViewSet)
router.register(r'workflowbpmn', WorkflowBpmnViewSet)


urlpatterns = [
]

urlpatterns += router.urls


================================================
FILE: backend/workflows/views.py
================================================
# -*- coding: utf-8 -*-
# author: itimor

from itertools import chain
from workflows.serializers import *
from common.views import ModelViewSet, FKModelViewSet, JsonResponse, BulkModelMixin


class WorkflowTypeViewSet(BulkModelMixin):
    queryset = WorkflowType.objects.all()
    serializer_class = WorkflowTypeSerializer
    search_fields = ['name']
    filter_fields = ['name', 'code', 'status']


class WorkflowViewSet(BulkModelMixin):
    queryset = Workflow.objects.all()
    serializer_class = WorkflowSerializer
    search_fields = ['name']
    filter_fields = ['name', 'id', 'type']

    def get_serializer_class(self):
        if self.action in ['list', 'retrieve'] or self.resultData:
            return WorkflowReadSerializer
        return WorkflowSerializer

    # def get_queryset(self):
    #     try:
    #         user = User.objects.get(username=self.request.user)
    #         if user.is_admin:
    #             return Workflow.objects.all()
    #         else:
    #             user_roles = user.roles.all()
    #             group_roles = user.group.roles.all()
    #             all_roles = sorted(chain(user_roles, group_roles), key=lambda t: t.id, reverse=True)
    #             return Workflow.objects.filter(roles__in=all_roles).distinct()
    #     except Exception as e:
    #         print(e)
    #         return Workflow.objects.all()


class StateViewSet(BulkModelMixin):
    queryset = State.objects.all()
    serializer_class = StateSerializer
    search_fields = ['name']
    filter_fields = ['workflow', 'is_hidden']
    ordering_fields = ['state_type', 'order_id']


class TransitionViewSet(BulkModelMixin):
    queryset = Transition.objects.all()
    serializer_class = TransitionSerializer
    search_fields = ['name']
    filter_fields = ['workflow', 'transition_type', 'source_state', 'dest_state']

    def get_serializer_class(self):
        if self.action in ['list', 'retrieve'] or self.resultData:
            return TransitionReadSerializer
        return TransitionSerializer


class CustomFieldViewSet(BulkModelMixin):
    queryset = CustomField.objects.all()
    serializer_class = CustomFieldSerializer
    search_fields = ['field_name']
    filter_fields = ['workflow', 'field_type', 'field_attribute']
    ordering_fields = ['field_type', 'order_id']


class WorkflowBpmnViewSet(BulkModelMixin):
    queryset = WorkflowBpmn.objects.all()
    serializer_class = WorkflowBpmnSerializer


================================================
FILE: frontend/.editorconfig
================================================
# https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false


================================================
FILE: frontend/.eslintignore
================================================
build/*.js
src/assets
public
dist


================================================
FILE: frontend/.eslintrc.js
================================================
module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint',
    sourceType: 'module'
  },
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  extends: ['plugin:vue/recommended', 'eslint:recommended'],

  // add your custom rules here
  //it is base on https://github.com/vuejs/eslint-config-vue
  rules: {
    "vue/max-attributes-per-line": [2, {
      "singleline": 10,
      "multiline": {
        "max": 1,
        "allowFirstLine": false
      }
    }],
    "vue/singleline-html-element-content-newline": "off",
    "vue/multiline-html-element-content-newline":"off",
    "vue/name-property-casing": ["error", "PascalCase"],
    "vue/no-v-html": "off",
    'accessor-pairs': 2,
    'arrow-spacing': [2, {
      'before': true,
      'after': true
    }],
    'block-spacing': [2, 'always'],
    'brace-style': [2, '1tbs', {
      'allowSingleLine': true
    }],
    'camelcase': [0, {
      'properties': 'always'
    }],
    'comma-dangle': [2, 'never'],
    'comma-spacing': [2, {
      'before': false,
      'after': true
    }],
    'comma-style': [2, 'last'],
    'constructor-super': 2,
    'curly': [2, 'multi-line'],
    'dot-location': [2, 'property'],
    'eol-last': 2,
    'eqeqeq': ["error", "always", {"null": "ignore"}],
    'generator-star-spacing': [2, {
      'before': true,
      'after': true
    }],
    'handle-callback-err': [2, '^(err|error)$'],
    'indent': [2, 2, {
      'SwitchCase': 1
    }],
    'jsx-quotes': [2, 'prefer-single'],
    'key-spacing': [2, {
      'beforeColon': false,
      'afterColon': true
    }],
    'keyword-spacing': [2, {
      'before': true,
      'after': true
    }],
    'new-cap': [2, {
      'newIsCap': true,
      'capIsNew': false
    }],
    'new-parens': 2,
    'no-array-constructor': 2,
    'no-caller': 2,
    'no-console': 'off',
    'no-class-assign': 2,
    'no-cond-assign': 2,
    'no-const-assign': 2,
    'no-control-regex': 0,
    'no-delete-var': 2,
    'no-dupe-args': 2,
    'no-dupe-class-members': 2,
    'no-dupe-keys': 2,
    'no-duplicate-case': 2,
    'no-empty-character-class': 2,
    'no-empty-pattern': 2,
    'no-eval': 2,
    'no-ex-assign': 2,
    'no-extend-native': 2,
    'no-extra-bind': 2,
    'no-extra-boolean-cast': 2,
    'no-extra-parens': [2, 'functions'],
    'no-fallthrough': 2,
    'no-floating-decimal': 2,
    'no-func-assign': 2,
    'no-implied-eval': 2,
    'no-inner-declarations': [2, 'functions'],
    'no-invalid-regexp': 2,
    'no-irregular-whitespace': 2,
    'no-iterator': 2,
    'no-label-var': 2,
    'no-labels': [2, {
      'allowLoop': false,
      'allowSwitch': false
    }],
    'no-lone-blocks': 2,
    'no-mixed-spaces-and-tabs': 2,
    'no-multi-spaces': 2,
    'no-multi-str': 2,
    'no-multiple-empty-lines': [2, {
      'max': 1
    }],
    'no-native-reassign': 2,
    'no-negated-in-lhs': 2,
    'no-new-object': 2,
    'no-new-require': 2,
    'no-new-symbol': 2,
    'no-new-wrappers': 2,
    'no-obj-calls': 2,
    'no-octal': 2,
    'no-octal-escape': 2,
    'no-path-concat': 2,
    'no-proto': 2,
    'no-redeclare': 2,
    'no-regex-spaces': 2,
    'no-return-assign': [2, 'except-parens'],
    'no-self-assign': 2,
    'no-self-compare': 2,
    'no-sequences': 2,
    'no-shadow-restricted-names': 2,
    'no-spaced-func': 2,
    'no-sparse-arrays': 2,
    'no-this-before-super': 2,
    'no-throw-literal': 2,
    'no-trailing-spaces': 2,
    'no-undef': 2,
    'no-undef-init': 2,
    'no-unexpected-multiline': 2,
    'no-unmodified-loop-condition': 2,
    'no-unneeded-ternary': [2, {
      'defaultAssignment': false
    }],
    'no-unreachable': 2,
    'no-unsafe-finally': 2,
    'no-unused-vars': [2, {
      'vars': 'all',
      'args': 'none'
    }],
    'no-useless-call': 2,
    'no-useless-computed-key': 2,
    'no-useless-constructor': 2,
    'no-useless-escape': 0,
    'no-whitespace-before-property': 2,
    'no-with': 2,
    'one-var': [2, {
      'initialized': 'never'
    }],
    'operator-linebreak': [2, 'after', {
      'overrides': {
        '?': 'before',
        ':': 'before'
      }
    }],
    'padded-blocks': [2, 'never'],
    'quotes': [2, 'single', {
      'avoidEscape': true,
      'allowTemplateLiterals': true
    }],
    'semi': [2, 'never'],
    'semi-spacing': [2, {
      'before': false,
      'after': true
    }],
    'space-before-blocks': [2, 'always'],
    'space-before-function-paren': [2, 'never'],
    'space-in-parens': [2, 'never'],
    'space-infix-ops': 2,
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false
    }],
    'spaced-comment': [2, 'always', {
      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
    }],
    'template-curly-spacing': [2, 'never'],
    'use-isnan': 2,
    'valid-typeof': 2,
    'wrap-iife': [2, 'any'],
    'yield-star-spacing': [2, 'both'],
    'yoda': [2, 'never'],
    'prefer-const': 2,
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    'object-curly-spacing': [2, 'always', {
      objectsInObjects: false
    }],
    'array-bracket-spacing': [2, 'never']
  }
}


================================================
FILE: frontend/.travis.yml
================================================
language: node_js
node_js: 10
script: npm run test
notifications:
  email: false


================================================
FILE: frontend/LICENSE
================================================
MIT License

Copyright (c) 2017-present PanJiaChen

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: frontend/babel.config.js
================================================
module.exports = {
  presets: [
    '@vue/app'
  ]
}


================================================
FILE: frontend/build/index.js
================================================
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')

if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
  const report = rawArgv.includes('--report')

  run(`vue-cli-service build ${args}`)

  const port = 9526
  const publicPath = config.publicPath

  var connect = require('connect')
  var serveStatic = require('serve-static')
  const app = connect()

  app.use(
    publicPath,
    serveStatic('./dist', {
      index: ['index.html', '/']
    })
  )

  app.listen(port, function () {
    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
    if (report) {
      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}report.html`))
    }

  })
} else {
  run(`vue-cli-service build ${args}`)
}


================================================
FILE: frontend/jest.config.js
================================================
module.exports = {
  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
  transform: {
    '^.+\\.vue$': 'vue-jest',
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
      'jest-transform-stub',
    '^.+\\.jsx?$': 'babel-jest'
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  snapshotSerializers: ['jest-serializer-vue'],
  testMatch: [
    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
  ],
  collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
  coverageDirectory: '<rootDir>/tests/unit/coverage',
  // 'collectCoverage': true,
  'coverageReporters': [
    'lcov',
    'text-summary'
  ],
  testURL: 'http://localhost/'
}


================================================
FILE: frontend/jsconfig.json
================================================
{ 
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
        "@/*": ["src/*"]
    }
  },
  "exclude": ["node_modules", "dist"]
}

================================================
FILE: frontend/package.json
================================================
{
  "name": "vue-element-admin",
  "version": "4.0.0",
  "description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features",
  "author": "Pan <panfree23@gmail.com>",
  "license": "MIT",
  "scripts": {
    "dev": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "eslint --ext .js,.vue src",
    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "src/**/*.{js,vue}": [
      "eslint --fix",
      "git add"
    ]
  },
  "keywords": [
    "vue",
    "admin",
    "dashboard",
    "element-ui",
    "boilerplate",
    "admin-template",
    "management-system"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
  },
  "bugs": {
    "url": "https://github.com/PanJiaChen/vue-element-admin/issues"
  },
  "dependencies": {
    "axios": "^0.19.2",
    "csshake": "^1.5.3",
    "element-ui": "^2.12.0",
    "fibers": "^5.0.0",
    "path-to-regexp": "^6.1.0",
    "vue": "^2.6.11",
    "vue-bpmn-modeler": "^1.2.0",
    "vue-i18n": "^8.15.0",
    "vue-markdown": "^2.2.4",
    "vue-route": "^1.5.1",
    "vuex": "^3.1.1"
  },
  "devDependencies": {
    "@babel/core": "7.0.0",
    "@babel/register": "7.0.0",
    "@vue/cli-plugin-babel": "3.5.3",
    "@vue/cli-plugin-eslint": "^3.5.1",
    "@vue/cli-plugin-unit-jest": "3.5.3",
    "@vue/cli-service": "3.5.3",
    "babel-core": "^6.26.3",
    "babel-eslint": "^10.1.0",
    "chalk": "^2.4.2",
    "clipboard": "^2.0.4",
    "echarts": "^4.7.0",
    "eslint": "^6.8.0",
    "eslint-plugin-vue": "^6.2.2",
    "fuse.js": "^3.6.1",
    "html-webpack-plugin": "^3.2.0",
    "js-cookie": "^2.2.1",
    "node-sass": "^4.14.0",
    "normalize.css": "^8.0.1",
    "nprogress": "^0.2.0",
    "sass-loader": "^8.0.2",
    "screenfull": "^5.0.2",
    "script-ext-html-webpack-plugin": "^2.1.4",
    "script-loader": "^0.7.2",
    "svg-sprite-loader": "^4.2.5",
    "svgo": "^1.3.2",
    "vue-count-to": "^1.0.13",
    "vue-router": "^3.1.6",
    "vue-template-compiler": "^2.6.11",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
  },
  "engines": {
    "node": ">=8.9",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}


================================================
FILE: frontend/plop-templates/component/index.hbs
================================================
{{#if template}}
<template>
  <div />
</template>
{{/if}}

{{#if script}}
<script>
export default {
  name: '{{ properCase name }}',
  props: {},
  data() {
    return {}
  },
  created() {},
  mounted() {},
  methods: {}
}
</script>
{{/if}}

{{#if style}}
<style lang="scss" scoped>

</style>
{{/if}}


================================================
FILE: frontend/plop-templates/component/prompt.js
================================================
const { notEmpty } = require('../utils.js')

module.exports = {
  description: 'generate vue component',
  prompts: [{
    type: 'input',
    name: 'name',
    message: 'component name please',
    validate: notEmpty('name')
  },
  {
    type: 'checkbox',
    name: 'blocks',
    message: 'Blocks:',
    choices: [{
      name: '<template>',
      value: 'template',
      checked: true
    },
    {
      name: '<script>',
      value: 'script',
      checked: true
    },
    {
      name: 'style',
      value: 'style',
      checked: true
    }
    ],
    validate(value) {
      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
        return 'Components require at least a <script> or <template> tag.'
      }
      return true
    }
  }
  ],
  actions: data => {
    const name = '{{properCase name}}'
    const actions = [{
      type: 'add',
      path: `src/components/${name}/index.vue`,
      templateFile: 'plop-templates/component/index.hbs',
      data: {
        name: name,
        template: data.blocks.includes('template'),
        script: data.blocks.includes('script'),
        style: data.blocks.includes('style')
      }
    }]

    return actions
  }
}


================================================
FILE: frontend/plop-templates/store/index.hbs
================================================
{{#if state}}
const state = {}
{{/if}}

{{#if mutations}}
const mutations = {}
{{/if}}

{{#if actions}}
const actions = {}
{{/if}}

export default {
  namespaced: true,
  {{options}}
}


================================================
FILE: frontend/plop-templates/store/prompt.js
================================================
const { notEmpty } = require('../utils.js')

module.exports = {
  description: 'generate store',
  prompts: [{
    type: 'input',
    name: 'name',
    message: 'store name please',
    validate: notEmpty('name')
  },
  {
    type: 'checkbox',
    name: 'blocks',
    message: 'Blocks:',
    choices: [{
      name: 'state',
      value: 'state',
      checked: true
    },
    {
      name: 'mutations',
      value: 'mutations',
      checked: true
    },
    {
      name: 'actions',
      value: 'actions',
      checked: true
    }
    ],
    validate(value) {
      if (!value.includes('state') || !value.includes('mutations')) {
        return 'store require at least state and mutations'
      }
      return true
    }
  }
  ],
  actions(data) {
    const name = '{{name}}'
    const { blocks } = data
    const options = ['state', 'mutations']
    const joinFlag = `,
  `
    if (blocks.length === 3) {
      options.push('actions')
    }

    const actions = [{
      type: 'add',
      path: `src/store/modules/${name}.js`,
      templateFile: 'plop-templates/store/index.hbs',
      data: {
        options: options.join(joinFlag),
        state: blocks.includes('state'),
        mutations: blocks.includes('mutations'),
        actions: blocks.includes('actions')
      }
    }]
    return actions
  }
}


================================================
FILE: frontend/plop-templates/utils.js
================================================
exports.notEmpty = name => {
  return v => {
    if (!v || v.trim === '') {
      return `${name} is required`
    } else {
      return true
    }
  }
}


================================================
FILE: frontend/plop-templates/view/index.hbs
================================================
{{#if template}}
<template>
  <div />
</template>
{{/if}}

{{#if script}}
<script>
export default {
  name: '{{ properCase name }}',
  props: {},
  data() {
    return {}
  },
  created() {},
  mounted() {},
  methods: {}
}
</script>
{{/if}}

{{#if style}}
<style lang="scss" scoped>

</style>
{{/if}}


================================================
FILE: frontend/plop-templates/view/prompt.js
================================================
const { notEmpty } = require('../utils.js')

module.exports = {
  description: 'generate a view',
  prompts: [{
    type: 'input',
    name: 'name',
    message: 'view name please',
    validate: notEmpty('name')
  },
  {
    type: 'checkbox',
    name: 'blocks',
    message: 'Blocks:',
    choices: [{
      name: '<template>',
      value: 'template',
      checked: true
    },
    {
      name: '<script>',
      value: 'script',
      checked: true
    },
    {
      name: 'style',
      value: 'style',
      checked: true
    }
    ],
    validate(value) {
      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
        return 'View require at least a <script> or <template> tag.'
      }
      return true
    }
  }
  ],
  actions: data => {
    const name = '{{name}}'
    const actions = [{
      type: 'add',
      path: `src/views/${name}/index.vue`,
      templateFile: 'plop-templates/view/index.hbs',
      data: {
        name: name,
        template: data.blocks.includes('template'),
        script: data.blocks.includes('script'),
        style: data.blocks.includes('style')
      }
    }]

    return actions
  }
}


================================================
FILE: frontend/plopfile.js
================================================
const viewGenerator = require('./plop-templates/view/prompt')
const componentGenerator = require('./plop-templates/component/prompt')
const storeGenerator = require('./plop-templates/store/prompt.js')

module.exports = function(plop) {
  plop.setGenerator('view', viewGenerator)
  plop.setGenerator('component', componentGenerator)
  plop.setGenerator('store', storeGenerator)
}


================================================
FILE: frontend/postcss.config.js
================================================
module.exports = {
  plugins: {
    autoprefixer: {}
  }
}


================================================
FILE: frontend/public/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="icon" href="http://softqual.co.il/wp-content/uploads/2017/06/cropped-favicon.png">
    <title><%= webpackConfig.name %></title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>


================================================
FILE: frontend/src/App.vue
================================================
<template>
  <div id="app">
    <router-view />
    <el-backtop :visibility-height="100">
      <el-tooltip placement="top" content="ちょっと待って">
        <div class="shake shake-crazy">
          <svg-icon icon-class="rocket" style="width: 40px; height: 40px;" />
        </div>
      </el-tooltip>
    </el-backtop>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>


================================================
FILE: frontend/src/api/all.js
================================================
import Request from '@/api/common'

// auth
import * as auths from '@/api/auths'
export const auth = auths

// systems
export const user = new Request('/sys/user/')
export const group = new Request('/sys/group/')
export const role = new Request('/sys/role/')
export const menu = new Request('/sys/menu/')
export const perm = new Request('/sys/perm/')

// tools
export const audit = new Request('/tool/audit/')
export const simple = new Request('/tool/simple/')

// workflows
export const workflowtype = new Request('/workflow/workflowtype/')
export const workflow = new Request('/workflow/workflow/')
export const state = new Request('/workflow/state/')
export const transition = new Request('/workflow/transition/')
export const customfield = new Request('/workflow/customfield/')

// tickets
export const ticket = new Request('/ticket/ticket/')
export const ticketflowlog = new Request('/ticket/ticketflowlog/')
export const ticketcustomfield = new Request('/ticket/ticketcustomfield/')
export const ticketuser = new Request('/ticket/ticketuser/')

// notices
export const mail = new Request('/notice/mail/')
export const telegram = new Request('/notice/telegram/')


================================================
FILE: frontend/src/api/auths.js
================================================
import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/sys/auth/jwt-token-auth/',
    method: 'post',
    data
  })
}

export function getInfo() {
  return request({
    url: '/sys/auth/getuserinfo/',
    method: 'get'
  })
}

export function requestMenuButton(menucode) {
  return request({
    url: '/sys/auth/getmenubutons/',
    method: 'get',
    params: {menucode}
  })
}

export function changepwd(data) {
  return request({
    url: '/sys/auth/changepwd/',
    method: 'post',
    data
  })
}

export function getuser_by_group(data) {
  return request({
    url: '/sys/user/getuser_by_group/',
    method: 'post',
    data
  })
}

export function getuser_by_roles(data) {
  return request({
    url: '/sys/user/getuser_by_roles/',
    method: 'post',
    data
  })
}

================================================
FILE: frontend/src/api/common.js
================================================
import request from '@/utils/request'

export default class Request {
  constructor(apiurl) {
    this.apiurl = apiurl;
  }

  requestPost(data) {
    return request({
      url: this.apiurl,
      method: 'post',
      data
    })
  }

  requestDelete(id) {
    return request({
      url: this.apiurl + id + '/',
      method: 'delete'
    })
  }

  requestPut(id, data) {
    return request({
      url: this.apiurl + id + '/',
      method: 'put',
      data
    })
  }

  requestGet(query) {
    return request({
      url: this.apiurl,
      method: 'get',
      params: query
    })
  }

  requestBulkPost(data) {
    return request({
      url: this.apiurl + 'bulk_create/',
      method: 'post',
      data
    })
  }

  requestBulkPut(data) {
    return request({
      url: this.apiurl + 'bulk_update/',
      method: 'put',
      data
    })
  }

  requestBulkDelete(data) {
    return request({
      url: this.apiurl + 'bulk_delete/',
      method: 'delete',
      data
    })
  }
}

================================================
FILE: frontend/src/assets/custom-theme/index.css
================================================
@charset "UTF-8";.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .fade-in-linear-enter,.custom-theme .fade-in-linear-leave,.custom-theme .fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-linear-enter-active,.custom-theme .el-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.custom-theme .el-fade-in-linear-enter,.custom-theme .el-fade-in-linear-leave,.custom-theme .el-fade-in-linear-leave-active{opacity:0}.custom-theme .el-fade-in-enter-active,.custom-theme .el-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-fade-in-enter,.custom-theme .el-fade-in-leave-active{opacity:0}.custom-theme .el-zoom-in-center-enter-active,.custom-theme .el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.custom-theme .el-zoom-in-center-enter,.custom-theme .el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.custom-theme .el-zoom-in-top-enter-active,.custom-theme .el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center top;transform-origin:center top}.custom-theme .el-zoom-in-top-enter,.custom-theme .el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-bottom-enter-active,.custom-theme .el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:center bottom;transform-origin:center bottom}.custom-theme .el-zoom-in-bottom-enter,.custom-theme .el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.custom-theme .el-zoom-in-left-enter-active,.custom-theme .el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s;transition:transform .3s cubic-bezier(.23,1,.32,1) .1s,opacity .3s cubic-bezier(.23,1,.32,1) .1s,-webkit-transform .3s cubic-bezier(.23,1,.32,1) .1s;-webkit-transform-origin:top left;transform-origin:top left}.custom-theme .el-zoom-in-left-enter,.custom-theme .el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.custom-theme .collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.custom-theme .horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.custom-theme .el-list-enter-active,.custom-theme .el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.custom-theme .el-list-enter,.custom-theme .el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.custom-theme .el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}@font-face{font-family:element-icons;src:url(fonts/element-icons.woff?t=1508751886602) format("woff"),url(fonts/element-icons.ttf?t=1508751886602) format("truetype");font-weight:400;font-style:normal}.custom-theme [class*=" el-icon-"],.custom-theme [class^=el-icon-]{font-family:element-icons!important;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}.custom-theme .el-icon-upload:before{content:"\e60d"}.custom-theme .el-icon-error:before{content:"\e62c"}.custom-theme .el-icon-success:before{content:"\e62d"}.custom-theme .el-icon-warning:before{content:"\e62e"}.custom-theme .el-icon-sort-down:before{content:"\e630"}.custom-theme .el-icon-sort-up:before{content:"\e631"}.custom-theme .el-icon-arrow-left:before{content:"\e600"}.custom-theme .el-icon-circle-plus:before{content:"\e601"}.custom-theme .el-icon-circle-plus-outline:before{content:"\e602"}.custom-theme .el-icon-arrow-down:before{content:"\e603"}.custom-theme .el-icon-arrow-right:before{content:"\e604"}.custom-theme .el-icon-arrow-up:before{content:"\e605"}.custom-theme .el-icon-back:before{content:"\e606"}.custom-theme .el-icon-circle-close:before{content:"\e607"}.custom-theme .el-icon-date:before{content:"\e608"}.custom-theme .el-icon-circle-close-outline:before{content:"\e609"}.custom-theme .el-icon-caret-left:before{content:"\e60a"}.custom-theme .el-icon-caret-bottom:before{content:"\e60b"}.custom-theme .el-icon-caret-top:before{content:"\e60c"}.custom-theme .el-icon-caret-right:before{content:"\e60e"}.custom-theme .el-icon-close:before{content:"\e60f"}.custom-theme .el-icon-d-arrow-left:before{content:"\e610"}.custom-theme .el-icon-check:before{content:"\e611"}.custom-theme .el-icon-delete:before{content:"\e612"}.custom-theme .el-icon-d-arrow-right:before{content:"\e613"}.custom-theme .el-icon-document:before{content:"\e614"}.custom-theme .el-icon-d-caret:before{content:"\e615"}.custom-theme .el-icon-edit-outline:before{content:"\e616"}.custom-theme .el-icon-download:before{content:"\e617"}.custom-theme .el-icon-goods:before{content:"\e618"}.custom-theme .el-icon-search:before{content:"\e619"}.custom-theme .el-icon-info:before{content:"\e61a"}.custom-theme .el-icon-message:before{content:"\e61b"}.custom-theme .el-icon-edit:before{content:"\e61c"}.custom-theme .el-icon-location:before{content:"\e61d"}.custom-theme .el-icon-loading:before{content:"\e61e"}.custom-theme .el-icon-location-outline:before{content:"\e61f"}.custom-theme .el-icon-menu:before{content:"\e620"}.custom-theme .el-icon-minus:before{content:"\e621"}.custom-theme .el-icon-bell:before{content:"\e622"}.custom-theme .el-icon-mobile-phone:before{content:"\e624"}.custom-theme .el-icon-news:before{content:"\e625"}.custom-theme .el-icon-more:before{content:"\e646"}.custom-theme .el-icon-more-outline:before{content:"\e626"}.custom-theme .el-icon-phone:before{content:"\e627"}.custom-theme .el-icon-phone-outline:before{content:"\e628"}.custom-theme .el-icon-picture:before{content:"\e629"}.custom-theme .el-icon-picture-outline:before{content:"\e62a"}.custom-theme .el-icon-plus:before{content:"\e62b"}.custom-theme .el-icon-printer:before{content:"\e62f"}.custom-theme .el-icon-rank:before{content:"\e632"}.custom-theme .el-icon-refresh:before{content:"\e633"}.custom-theme .el-icon-question:before{content:"\e634"}.custom-theme .el-icon-remove:before{content:"\e635"}.custom-theme .el-icon-share:before{content:"\e636"}.custom-theme .el-icon-star-on:before{content:"\e637"}.custom-theme .el-icon-setting:before{content:"\e638"}.custom-theme .el-icon-circle-check:before{content:"\e639"}.custom-theme .el-icon-service:before{content:"\e63a"}.custom-theme .el-icon-sold-out:before{content:"\e63b"}.custom-theme .el-icon-remove-outline:before{content:"\e63c"}.custom-theme .el-icon-star-off:before{content:"\e63d"}.custom-theme .el-icon-circle-check-outline:before{content:"\e63e"}.custom-theme .el-icon-tickets:before{content:"\e63f"}.custom-theme .el-icon-sort:before{content:"\e640"}.custom-theme .el-icon-zoom-in:before{content:"\e641"}.custom-theme .el-icon-time:before{content:"\e642"}.custom-theme .el-icon-view:before{content:"\e643"}.custom-theme .el-icon-upload2:before{content:"\e644"}.custom-theme .el-icon-zoom-out:before{content:"\e645"}.custom-theme .el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.custom-theme .el-icon--right{margin-left:5px}.custom-theme .el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}@keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}.custom-theme .el-popper .popper__arrow,.custom-theme .el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.custom-theme .el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.custom-theme .el-popper .popper__arrow::after{content:" ";border-width:6px}.custom-theme .el-popper[x-placement^=top]{margin-bottom:12px}.custom-theme .el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#e6ebf5;border-bottom-width:0}.custom-theme .el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.custom-theme .el-popper[x-placement^=bottom]{margin-top:12px}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#e6ebf5}.custom-theme .el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.custom-theme .el-popper[x-placement^=right]{margin-left:12px}.custom-theme .el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#e6ebf5;border-left-width:0}.custom-theme .el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.custom-theme .el-popper[x-placement^=left]{margin-right:12px}.custom-theme .el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#e6ebf5}.custom-theme .el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.custom-theme .el-select-dropdown{position:absolute;z-index:1001;border:solid 1px #dfe4ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#262729;background-color:#fff}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.custom-theme .el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\E611";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-theme .el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.custom-theme .el-select-dropdown .popper__arrow{-webkit-transform:translateX(-400%);transform:translateX(-400%)}.custom-theme .el-select-dropdown.is-arrow-fixed .popper__arrow{-webkit-transform:translateX(-200%);transform:translateX(-200%)}.custom-theme .el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.custom-theme .el-select-dropdown__wrap{max-height:274px}.custom-theme .el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.custom-theme .el-input{position:relative;font-size:14px;display:inline-block;width:100%}.custom-theme .el-input::-webkit-scrollbar{z-index:11;width:6px}.custom-theme .el-input::-webkit-scrollbar:horizontal{height:6px}.custom-theme .el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.custom-theme .el-input::-webkit-scrollbar-corner{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track{background:#fff}.custom-theme .el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.custom-theme .el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #d8dce5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#5a5e66;display:inline-block;font-size:inherit;height:40px;line-height:1;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.custom-theme .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input__inner:hover{border-color:#b4bccc}.custom-theme .el-input__inner:focus{outline:0;border-color:#262729}.custom-theme .el-input__suffix{position:absolute;height:100%;right:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s;pointer-events:none}.custom-theme .el-input__suffix-inner{pointer-events:all}.custom-theme .el-input__prefix{position:absolute;height:100%;left:5px;top:0;text-align:center;color:#b4bccc;-webkit-transition:all .3s;transition:all .3s}.custom-theme .el-input__icon{height:100%;width:25px;text-align:center;-webkit-transition:all .3s;transition:all .3s;line-height:40px}.custom-theme .el-input__icon:after{content:'';height:100%;width:0;display:inline-block;vertical-align:middle}.custom-theme .el-input__validateIcon{pointer-events:none}.custom-theme .el-input.is-active .el-input__inner{outline:0;border-color:#262729}.custom-theme .el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#dfe4ed;color:#b4bccc;cursor:not-allowed}.custom-theme .el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__inner::placeholder{color:#b4bccc}.custom-theme .el-input.is-disabled .el-input__icon{cursor:not-allowed}.custom-theme .el-input--suffix .el-input__inner{padding-right:30px}.custom-theme .el-input--prefix .el-input__inner{padding-left:30px}.custom-theme .el-input--medium{font-size:14px}.custom-theme .el-input--medium .el-input__inner{height:36px}.custom-theme .el-input--medium .el-input__icon{line-height:36px}.custom-theme .el-input--small{font-size:13px}.custom-theme .el-input--small .el-input__inner{height:32px}.custom-theme .el-input--small .el-input__icon{line-height:32px}.custom-theme .el-input--mini{font-size:12px}.custom-theme .el-input--mini .el-input__inner{height:28px}.custom-theme .el-input--mini .el-input__icon{line-height:28px}.custom-theme .el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate}.custom-theme .el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.custom-theme .el-input-group__append,.custom-theme .el-input-group__prepend{background-color:#f5f7fa;color:#0a76a4;vertical-align:middle;display:table-cell;position:relative;border:1px solid #d8dce5;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.custom-theme .el-input-group__append:focus,.custom-theme .el-input-group__prepend:focus{outline:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-select,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-select{display:inline-block;margin:-20px}.custom-theme .el-input-group__append button.el-button,.custom-theme .el-input-group__append div.el-select .el-input__inner,.custom-theme .el-input-group__append div.el-select:hover .el-input__inner,.custom-theme .el-input-group__prepend button.el-button,.custom-theme .el-input-group__prepend div.el-select .el-input__inner,.custom-theme .el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.custom-theme .el-input-group__append .el-button,.custom-theme .el-input-group__append .el-input,.custom-theme .el-input-group__prepend .el-button,.custom-theme .el-input-group__prepend .el-input{font-size:inherit}.custom-theme .el-input-group__prepend{border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}
Download .txt
gitextract_nol__jqe/

├── .gitignore
├── 500.html
├── README.md
├── backend/
│   ├── __init__.py
│   ├── celery.service
│   ├── common/
│   │   ├── JSONRenderer.py
│   │   ├── __init__.py
│   │   ├── dispath.py
│   │   ├── django.py
│   │   ├── exceptions.py
│   │   ├── models.py
│   │   ├── pagination.py
│   │   ├── status.py
│   │   └── views.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── celery.py
│   │   ├── settings/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   ├── dev.py
│   │   │   ├── mac.py
│   │   │   └── prod.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── dev_requirements.txt
│   ├── init.sh
│   ├── manage.py
│   ├── notices/
│   │   ├── __init__.py
│   │   ├── management/
│   │   │   ├── __init__.py
│   │   │   └── commands/
│   │   │       ├── __init__.py
│   │   │       └── init_notice.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── oms.ini
│   ├── oms.service
│   ├── requirements.txt
│   ├── systems/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── management/
│   │   │   ├── __init__.py
│   │   │   └── commands/
│   │   │       ├── __init__.py
│   │   │       └── init_sys.py
│   │   ├── menus.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── permissions.py
│   │   ├── serializers.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── tickets/
│   │   ├── __init__.py
│   │   ├── filters.py
│   │   ├── management/
│   │   │   ├── __init__.py
│   │   │   └── commands/
│   │   │       ├── __init__.py
│   │   │       └── init_ticket.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── tools/
│   │   ├── __init__.py
│   │   ├── filesize.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── storage.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── get_realip.py
│   │   ├── index.py
│   │   ├── mysql.py
│   │   ├── sendmail.py
│   │   ├── sendskype.py
│   │   ├── test.py
│   │   ├── time.py
│   │   └── verifys.py
│   └── workflows/
│       ├── __init__.py
│       ├── admin.py
│       ├── management/
│       │   ├── __init__.py
│       │   └── commands/
│       │       ├── __init__.py
│       │       ├── init_leave.py
│       │       └── init_wf.py
│       ├── migrations/
│       │   ├── 0001_initial.py
│       │   └── __init__.py
│       ├── models.py
│       ├── serializers.py
│       ├── urls.py
│       └── views.py
├── frontend/
│   ├── .editorconfig
│   ├── .eslintignore
│   ├── .eslintrc.js
│   ├── .travis.yml
│   ├── LICENSE
│   ├── babel.config.js
│   ├── build/
│   │   └── index.js
│   ├── jest.config.js
│   ├── jsconfig.json
│   ├── package.json
│   ├── plop-templates/
│   │   ├── component/
│   │   │   ├── index.hbs
│   │   │   └── prompt.js
│   │   ├── store/
│   │   │   ├── index.hbs
│   │   │   └── prompt.js
│   │   ├── utils.js
│   │   └── view/
│   │       ├── index.hbs
│   │       └── prompt.js
│   ├── plopfile.js
│   ├── postcss.config.js
│   ├── public/
│   │   └── index.html
│   ├── src/
│   │   ├── App.vue
│   │   ├── api/
│   │   │   ├── all.js
│   │   │   ├── auths.js
│   │   │   └── common.js
│   │   ├── assets/
│   │   │   └── custom-theme/
│   │   │       └── index.css
│   │   ├── components/
│   │   │   ├── Breadcrumb/
│   │   │   │   └── index.vue
│   │   │   ├── Charts/
│   │   │   │   ├── keyboard.vue
│   │   │   │   ├── lineMarker.vue
│   │   │   │   ├── mixChart.vue
│   │   │   │   └── mixins/
│   │   │   │       └── resize.js
│   │   │   ├── DndList/
│   │   │   │   └── index.vue
│   │   │   ├── Hamburger/
│   │   │   │   └── index.vue
│   │   │   ├── HeaderSearch/
│   │   │   │   └── index.vue
│   │   │   ├── Kanban/
│   │   │   │   └── index.vue
│   │   │   ├── LangSelect/
│   │   │   │   └── index.vue
│   │   │   ├── MDinput/
│   │   │   │   └── index.vue
│   │   │   ├── Pagination/
│   │   │   │   └── index.vue
│   │   │   ├── PanThumb/
│   │   │   │   └── index.vue
│   │   │   ├── RightPanel/
│   │   │   │   └── index.vue
│   │   │   ├── Screenfull/
│   │   │   │   └── index.vue
│   │   │   ├── Share/
│   │   │   │   └── DropdownMenu.vue
│   │   │   ├── Sticky/
│   │   │   │   └── index.vue
│   │   │   ├── SvgIcon/
│   │   │   │   └── index.vue
│   │   │   ├── TextHoverEffect/
│   │   │   │   └── Mallki.vue
│   │   │   ├── ThemePicker/
│   │   │   │   └── index.vue
│   │   │   └── TreeSelect/
│   │   │       └── index.vue
│   │   ├── directive/
│   │   │   ├── clipboard/
│   │   │   │   ├── clipboard.js
│   │   │   │   └── index.js
│   │   │   ├── sticky.js
│   │   │   └── waves/
│   │   │       ├── index.js
│   │   │       ├── waves.css
│   │   │       └── waves.js
│   │   ├── filters/
│   │   │   └── index.js
│   │   ├── icons/
│   │   │   ├── index.js
│   │   │   └── svgo.yml
│   │   ├── lang/
│   │   │   ├── en.js
│   │   │   ├── index.js
│   │   │   └── zh.js
│   │   ├── layout/
│   │   │   ├── components/
│   │   │   │   ├── AppMain.vue
│   │   │   │   ├── Navbar.vue
│   │   │   │   ├── Settings/
│   │   │   │   │   └── index.vue
│   │   │   │   ├── Sidebar/
│   │   │   │   │   ├── Item.vue
│   │   │   │   │   ├── Link.vue
│   │   │   │   │   ├── Logo.vue
│   │   │   │   │   ├── SidebarItem.vue
│   │   │   │   │   └── index.vue
│   │   │   │   ├── TagsView/
│   │   │   │   │   ├── ScrollPane.vue
│   │   │   │   │   └── index.vue
│   │   │   │   └── index.js
│   │   │   ├── index.vue
│   │   │   └── mixin/
│   │   │       └── ResizeHandler.js
│   │   ├── main.js
│   │   ├── permission.js
│   │   ├── router/
│   │   │   ├── index.js
│   │   │   └── modules/
│   │   │       └── components.js
│   │   ├── settings.js
│   │   ├── store/
│   │   │   ├── getters.js
│   │   │   ├── index.js
│   │   │   └── modules/
│   │   │       ├── app.js
│   │   │       ├── permission.js
│   │   │       ├── settings.js
│   │   │       ├── tagsView.js
│   │   │       └── user.js
│   │   ├── styles/
│   │   │   ├── btn.scss
│   │   │   ├── csshake.scss
│   │   │   ├── element-ui.scss
│   │   │   ├── element-variables.scss
│   │   │   ├── index.scss
│   │   │   ├── mixin.scss
│   │   │   ├── sidebar.scss
│   │   │   ├── transition.scss
│   │   │   └── variables.scss
│   │   ├── utils/
│   │   │   ├── auth.js
│   │   │   ├── clipboard.js
│   │   │   ├── get-page-title.js
│   │   │   ├── i18n.js
│   │   │   ├── index.js
│   │   │   ├── open-window.js
│   │   │   ├── permission.js
│   │   │   ├── request.js
│   │   │   ├── scroll-to.js
│   │   │   └── validate.js
│   │   ├── vendor/
│   │   │   ├── Export2Excel.js
│   │   │   └── Export2Zip.js
│   │   └── views/
│   │       ├── components-demo/
│   │       │   ├── clipboard.vue
│   │       │   ├── dnd-list.vue
│   │       │   ├── drag-kanban.vue
│   │       │   ├── mixin.vue
│   │       │   └── sticky.vue
│   │       ├── dashboard/
│   │       │   ├── components/
│   │       │   │   ├── BarChart.vue
│   │       │   │   ├── BoxCard.vue
│   │       │   │   ├── LineChart.vue
│   │       │   │   ├── PanelGroup.vue
│   │       │   │   ├── PieChart.vue
│   │       │   │   ├── RaddarChart.vue
│   │       │   │   ├── TodoList/
│   │       │   │   │   ├── Todo.vue
│   │       │   │   │   ├── index.scss
│   │       │   │   │   └── index.vue
│   │       │   │   ├── TransactionTable.vue
│   │       │   │   └── mixins/
│   │       │   │       └── resize.js
│   │       │   └── index.vue
│   │       ├── error-page/
│   │       │   ├── 401.vue
│   │       │   └── 404.vue
│   │       ├── icons/
│   │       │   ├── element-icons.js
│   │       │   ├── index.vue
│   │       │   └── svg-icons.js
│   │       ├── login/
│   │       │   └── index.vue
│   │       ├── notice/
│   │       │   ├── mail.vue
│   │       │   └── telegram.vue
│   │       ├── sys/
│   │       │   ├── group.vue
│   │       │   ├── menu.vue
│   │       │   ├── role.vue
│   │       │   └── user.vue
│   │       ├── ticket/
│   │       │   ├── all_ticket.vue
│   │       │   ├── my_ticket.vue
│   │       │   ├── new_ticket.vue
│   │       │   ├── s_ticket.vue
│   │       │   ├── todo_ticket.vue
│   │       │   └── u_ticket.vue
│   │       ├── tool/
│   │       │   ├── audit.vue
│   │       │   └── test.vue
│   │       └── workflow/
│   │           ├── pages/
│   │           │   ├── customfield.vue
│   │           │   ├── state.vue
│   │           │   └── transition.vue
│   │           ├── wfconf.vue
│   │           ├── wfset.vue
│   │           └── wftype.vue
│   └── vue.config.js
└── oms_nginx.conf
Download .txt
SYMBOL INDEX (409 symbols across 76 files)

FILE: backend/common/JSONRenderer.py
  class CustomJSONRenderer (line 7) | class CustomJSONRenderer(JSONRenderer):
    method render (line 8) | def render(self, data, accepted_media_type=None, renderer_context=None):

FILE: backend/common/dispath.py
  class JsonResponse (line 9) | class JsonResponse(Response):
    method __init__ (line 15) | def __init__(self, data=None, code=None, desc=None,

FILE: backend/common/django.py
  class DisableCSRF (line 6) | class DisableCSRF(MiddlewareMixin):
    method process_request (line 7) | def process_request(self, request):

FILE: backend/common/exceptions.py
  function JSONExceptionHandler (line 8) | def JSONExceptionHandler(exc, context):
  class ExceptionX_Result (line 21) | class ExceptionX_Result:
  class ExceptionX (line 27) | class ExceptionX:
    method ToString (line 30) | def ToString(e):
    method PasreRaise (line 44) | def PasreRaise(e):

FILE: backend/common/models.py
  class BaseModel (line 7) | class BaseModel(models.Model):
    class Meta (line 12) | class Meta:

FILE: backend/common/pagination.py
  function _positive_int (line 12) | def _positive_int(integer_string, strict=False, cutoff=None):
  class StandardResultsSetPagination (line 26) | class StandardResultsSetPagination(PageNumberPagination):
    method get_paginated_response (line 35) | def get_paginated_response(self, data):
    method get_page_size (line 43) | def get_page_size(self, request):
  class CustomLimitOffsetPagination (line 56) | class CustomLimitOffsetPagination(LimitOffsetPagination):
    method get_offset (line 57) | def get_offset(self, request):

FILE: backend/common/status.py
  function is_informational (line 7) | def is_informational(code):
  function is_success (line 11) | def is_success(code):
  function is_redirect (line 15) | def is_redirect(code):
  function is_client_error (line 19) | def is_client_error(code):
  function is_server_error (line 23) | def is_server_error(code):

FILE: backend/common/views.py
  class ModelViewSet (line 18) | class ModelViewSet(viewsets.ModelViewSet):
    method __init__ (line 20) | def __init__(self, *args, **kwargs):
    method watch_audit_log (line 24) | def watch_audit_log(self, request):
    method create (line 42) | def create(self, request, *args, **kwargs):
    method perform_create (line 58) | def perform_create(self, serializer):
    method list (line 61) | def list(self, request, *args, **kwargs):
    method retrieve (line 76) | def retrieve(self, request, *args, **kwargs):
    method update (line 84) | def update(self, request, *args, **kwargs):
    method perform_update (line 99) | def perform_update(self, serializer):
    method partial_update (line 103) | def partial_update(self, request, *args, **kwargs):
    method destroy (line 107) | def destroy(self, request, *args, **kwargs):
    method perform_destroy (line 113) | def perform_destroy(self, instance):
  class FKModelViewSet (line 117) | class FKModelViewSet(ModelViewSet):
    method transer (line 119) | def transer(self, instance=None, id=None):
    method perform_create (line 127) | def perform_create(self, serializer):
    method perform_update (line 131) | def perform_update(self, serializer):
    method perform_destroy (line 135) | def perform_destroy(self, instance):
    method create (line 138) | def create(self, request, *args, **kwargs):
    method update (line 149) | def update(self, request, *args, **kwargs):
    method destroy (line 165) | def destroy(self, request, *args, **kwargs):
  class BulkModelMixin (line 173) | class BulkModelMixin(ModelViewSet):
    method bulk_create (line 176) | def bulk_create(self, request, *args, **kwargs):
    method bulk_delete (line 206) | def bulk_delete(self, request, *args, **kwargs):
    method bulk_update (line 234) | def bulk_update(self, request, *args, **kwargs):

FILE: backend/notices/management/commands/init_notice.py
  class Command (line 9) | class Command(BaseCommand):
    method handle (line 12) | def handle(self, *args, **options):

FILE: backend/notices/migrations/0001_initial.py
  class Migration (line 6) | class Migration(migrations.Migration):

FILE: backend/notices/models.py
  class MailBot (line 13) | class MailBot(BaseModel):
    method __str__ (line 21) | def __str__(self):
    class Meta (line 24) | class Meta:
  class TelegramBot (line 29) | class TelegramBot(BaseModel):
    method __str__ (line 36) | def __str__(self):
    class Meta (line 39) | class Meta:

FILE: backend/notices/serializers.py
  class MailBotSerializer (line 8) | class MailBotSerializer(serializers.ModelSerializer):
    class Meta (line 9) | class Meta:
  class TelegramBotSerializer (line 14) | class TelegramBotSerializer(serializers.ModelSerializer):
    class Meta (line 15) | class Meta:

FILE: backend/notices/views.py
  class NoticeViewSet (line 11) | class NoticeViewSet(ModelViewSet):
    method send (line 17) | def send(self, request, *args, **kwargs):
  class MailBotViewSet (line 60) | class MailBotViewSet(ModelViewSet):
  class TelegramBotViewSet (line 68) | class TelegramBotViewSet(ModelViewSet):

FILE: backend/systems/management/commands/init_sys.py
  class Command (line 10) | class Command(BaseCommand):
    method handle (line 13) | def handle(self, *args, **options):

FILE: backend/systems/menus.py
  function get_menus_by_user (line 9) | def get_menus_by_user(user):
  function find_menu_daddy (line 34) | def find_menu_daddy(menuid, menuMap):
  function set_menu (line 44) | def set_menu(menus, parent_id):
  function init_menu (line 90) | def init_menu(menu):

FILE: backend/systems/migrations/0001_initial.py
  class Migration (line 8) | class Migration(migrations.Migration):

FILE: backend/systems/models.py
  class Menu (line 25) | class Menu(BaseModel):
    method __str__ (line 39) | def __str__(self):
    class Meta (line 42) | class Meta:
  class Role (line 48) | class Role(BaseModel):
    method __str__ (line 56) | def __str__(self):
    class Meta (line 59) | class Meta:
  class Group (line 65) | class Group(BaseModel, Group):
    method __str__ (line 71) | def __str__(self):
    class Meta (line 74) | class Meta:
  class PermissionsMixin (line 82) | class PermissionsMixin(models.Model):
    class Meta (line 107) | class Meta:
  class UserManager (line 112) | class UserManager(BaseUserManager):
    method create_user (line 113) | def create_user(self, username, password=None):
    method create_superuser (line 128) | def create_superuser(self, username, password):
  class User (line 137) | class User(BaseModel, PermissionsMixin, AbstractBaseUser):
    method is_staff (line 148) | def is_staff(self):
    method has_perm (line 151) | def has_perm(self, perm, obj=None):
    method has_module_perms (line 154) | def has_module_perms(self, app_label):
    method __str__ (line 157) | def __str__(self):
    class Meta (line 160) | class Meta:

FILE: backend/systems/permissions.py
  function check_permission (line 24) | def check_permission(request, perm):
  class IsOwnerRoles (line 42) | class IsOwnerRoles(BasePermission):
    method has_permission (line 44) | def has_permission(self, request, view):
    method has_object_permission (line 50) | def has_object_permission(self, request, view, obj):

FILE: backend/systems/serializers.py
  class UserReadSerializer (line 9) | class UserReadSerializer(serializers.ModelSerializer):
    class Meta (line 10) | class Meta:
  class UserSerializer (line 16) | class UserSerializer(serializers.ModelSerializer):
    class Meta (line 17) | class Meta:
    method create (line 22) | def create(self, validated_data):
    method update (line 37) | def update(self, instance, validated_data):
  class RoleSerializer (line 55) | class RoleSerializer(serializers.ModelSerializer):
    class Meta (line 56) | class Meta:
  class GroupSerializer (line 61) | class GroupSerializer(serializers.ModelSerializer):
    class Meta (line 64) | class Meta:
  class PermissionSerializer (line 69) | class PermissionSerializer(serializers.ModelSerializer):
    class Meta (line 70) | class Meta:
  class MenuSerializer (line 75) | class MenuSerializer(serializers.ModelSerializer):
    class Meta (line 76) | class Meta:
    method create (line 80) | def create(self, validated_data):

FILE: backend/systems/views.py
  class UserViewSet (line 16) | class UserViewSet(BulkModelMixin):
  class GroupViewSet (line 24) | class GroupViewSet(BulkModelMixin):
  class RoleViewSet (line 32) | class RoleViewSet(BulkModelMixin):
  class PermissionViewSet (line 40) | class PermissionViewSet(ModelViewSet):
  class MenuViewSet (line 48) | class MenuViewSet(BulkModelMixin):
  class AuthViewSet (line 56) | class AuthViewSet(ModelViewSet):
    method getuserinfo (line 61) | def getuserinfo(self, request):
    method getmenubutons (line 85) | def getmenubutons(self, request):
  class ObtainJSONWebToken (line 107) | class ObtainJSONWebToken(JSONWebTokenAPIView):
    method post (line 110) | def post(self, request, *args, **kwargs):

FILE: backend/tickets/filters.py
  class TicketFilter (line 8) | class TicketFilter(filters.FilterSet):
    class Meta (line 9) | class Meta:

FILE: backend/tickets/management/commands/init_ticket.py
  class Command (line 9) | class Command(BaseCommand):
    method handle (line 12) | def handle(self, *args, **options):

FILE: backend/tickets/migrations/0001_initial.py
  class Migration (line 8) | class Migration(migrations.Migration):

FILE: backend/tickets/models.py
  class Ticket (line 10) | class Ticket(BaseModel):
    method __str__ (line 24) | def __str__(self):
    class Meta (line 27) | class Meta:
  class TicketFlowLog (line 43) | class TicketFlowLog(BaseModel):
    class Meta (line 55) | class Meta:
  class TicketCustomField (line 78) | class TicketCustomField(BaseModel):
    class Meta (line 86) | class Meta:
  class TicketUser (line 91) | class TicketUser(BaseModel):
    method __str__ (line 100) | def __str__(self):
    class Meta (line 103) | class Meta:

FILE: backend/tickets/serializers.py
  class TicketReadSerializer (line 11) | class TicketReadSerializer(serializers.ModelSerializer):
    class Meta (line 12) | class Meta:
  class TicketSerializer (line 18) | class TicketSerializer(serializers.ModelSerializer):
    class Meta (line 19) | class Meta:
    method create (line 23) | def create(self, validated_data):
    method update (line 80) | def update(self, instance, validated_data):
  class TicketFlowLogReadSerializer (line 118) | class TicketFlowLogReadSerializer(serializers.ModelSerializer):
    class Meta (line 119) | class Meta:
  class TicketFlowLogSerializer (line 125) | class TicketFlowLogSerializer(serializers.ModelSerializer):
    class Meta (line 126) | class Meta:
  class TicketCustomFieldReadSerializer (line 131) | class TicketCustomFieldReadSerializer(serializers.ModelSerializer):
    class Meta (line 132) | class Meta:
  class TicketCustomFieldSerializer (line 138) | class TicketCustomFieldSerializer(serializers.ModelSerializer):
    class Meta (line 139) | class Meta:
  class TicketUserSerializer (line 144) | class TicketUserSerializer(serializers.ModelSerializer):
    class Meta (line 145) | class Meta:

FILE: backend/tickets/views.py
  class TicketViewSet (line 9) | class TicketViewSet(BulkModelMixin):
    method get_serializer_class (line 16) | def get_serializer_class(self):
    method get_queryset (line 21) | def get_queryset(self):
  class TicketFlowLogViewSet (line 33) | class TicketFlowLogViewSet(BulkModelMixin):
    method get_serializer_class (line 39) | def get_serializer_class(self):
  class TicketCustomFieldViewSet (line 45) | class TicketCustomFieldViewSet(BulkModelMixin):
    method get_serializer_class (line 50) | def get_serializer_class(self):
  class TicketUserViewSet (line 56) | class TicketUserViewSet(BulkModelMixin):

FILE: backend/tools/filesize.py
  function convert_size (line 7) | def convert_size(size_bytes):

FILE: backend/tools/migrations/0001_initial.py
  class Migration (line 7) | class Migration(migrations.Migration):

FILE: backend/tools/models.py
  class Upload (line 11) | class Upload(BaseModel):
    method save (line 20) | def save(self, *args, **kwargs):
    method __str__ (line 28) | def __str__(self):
    class Meta (line 31) | class Meta:
  class FileUpload (line 36) | class FileUpload(models.Model):
    class Meta (line 39) | class Meta:
  class RequestEvent (line 44) | class RequestEvent(BaseModel):
    class Meta (line 51) | class Meta:
  class SimpleModel (line 57) | class SimpleModel(models.Model):

FILE: backend/tools/serializers.py
  class UploadSerializer (line 8) | class UploadSerializer(serializers.ModelSerializer):
    class Meta (line 9) | class Meta:
  class FileUploadSerializer (line 14) | class FileUploadSerializer(serializers.ModelSerializer):
    class Meta (line 15) | class Meta:
  class RequestEventSerializer (line 20) | class RequestEventSerializer(serializers.ModelSerializer):
    class Meta (line 21) | class Meta:
  class SimpleSerializer (line 25) | class SimpleSerializer(serializers.ModelSerializer):
    class Meta (line 26) | class Meta(object):

FILE: backend/tools/storage.py
  class PathAndRename (line 10) | class PathAndRename(object):
    method __init__ (line 11) | def __init__(self, sub_path):
    method __call__ (line 14) | def __call__(self, instance, file):

FILE: backend/tools/views.py
  class UploadViewSet (line 10) | class UploadViewSet(ModelViewSet):
  class FileUploadViewSet (line 16) | class FileUploadViewSet(ModelViewSet):
  class RequestEventViewSet (line 22) | class RequestEventViewSet(ModelViewSet):
  class SimpleViewSet (line 29) | class SimpleViewSet(BulkModelMixin):

FILE: backend/utils/get_realip.py
  function get_local_ip (line 13) | def get_local_ip():

FILE: backend/utils/index.py
  function gen_time_pid (line 8) | def gen_time_pid(prefix):
  function diff_times_in_seconds (line 13) | def diff_times_in_seconds(t1, t2):

FILE: backend/utils/mysql.py
  class MYSQL (line 7) | class MYSQL:
    method __init__ (line 8) | def __init__(self, db, sql):
    method insert (line 19) | def insert(self):
    method select (line 26) | def select(self):
    method update (line 33) | def update(self):

FILE: backend/utils/sendmail.py
  function send_mail (line 18) | def send_mail(to_list, cc_list, sub, content):

FILE: backend/utils/sendskype.py
  function skype_bot (line 15) | def skype_bot(user, content):

FILE: backend/utils/time.py
  function utc2local (line 8) | def utc2local(utc_st):
  function local2utc (line 20) | def local2utc(local_st):
  function string2timestamp (line 31) | def string2timestamp(strValue):
  function timestamp2string (line 47) | def timestamp2string(timeStamp):

FILE: backend/utils/verifys.py
  function is_valid_domain (line 8) | def is_valid_domain(value):
  function is_domain (line 18) | def is_domain(domain):
  function is_ip (line 25) | def is_ip(address):

FILE: backend/workflows/management/commands/init_leave.py
  class Command (line 11) | class Command(BaseCommand):
    method handle (line 14) | def handle(self, *args, **options):

FILE: backend/workflows/management/commands/init_wf.py
  class Command (line 9) | class Command(BaseCommand):
    method handle (line 12) | def handle(self, *args, **options):

FILE: backend/workflows/migrations/0001_initial.py
  class Migration (line 8) | class Migration(migrations.Migration):

FILE: backend/workflows/models.py
  class WorkflowType (line 9) | class WorkflowType(BaseModel):
    method __str__ (line 15) | def __str__(self):
    class Meta (line 18) | class Meta:
  class Workflow (line 24) | class Workflow(BaseModel):
    method __str__ (line 42) | def __str__(self):
    class Meta (line 45) | class Meta:
  class CustomField (line 68) | class CustomField(BaseModel):
    method __str__ (line 86) | def __str__(self):
    class Meta (line 89) | class Meta:
  class State (line 110) | class State(BaseModel):
    method __str__ (line 126) | def __str__(self):
    class Meta (line 129) | class Meta:
  class Transition (line 158) | class Transition(BaseModel):
    method __str__ (line 174) | def __str__(self):
    class Meta (line 177) | class Meta:
  class WorkflowBpmn (line 189) | class WorkflowBpmn(BaseModel):
    class Meta (line 194) | class Meta:

FILE: backend/workflows/serializers.py
  class WorkflowReadSerializer (line 8) | class WorkflowReadSerializer(serializers.ModelSerializer):
    class Meta (line 9) | class Meta:
  class WorkflowSerializer (line 15) | class WorkflowSerializer(serializers.ModelSerializer):
    class Meta (line 16) | class Meta:
    method create (line 20) | def create(self, validated_data):
  class WorkflowTypeSerializer (line 44) | class WorkflowTypeSerializer(serializers.ModelSerializer):
    method get_workflow_list (line 48) | def get_workflow_list(self, instance):
    class Meta (line 52) | class Meta:
  class StateReadSerializer (line 57) | class StateReadSerializer(serializers.ModelSerializer):
    class Meta (line 58) | class Meta:
  class StateSerializer (line 64) | class StateSerializer(serializers.ModelSerializer):
    class Meta (line 65) | class Meta:
  class TransitionReadSerializer (line 70) | class TransitionReadSerializer(serializers.ModelSerializer):
    class Meta (line 71) | class Meta:
  class TransitionSerializer (line 77) | class TransitionSerializer(serializers.ModelSerializer):
    class Meta (line 78) | class Meta:
  class CustomFieldSerializer (line 83) | class CustomFieldSerializer(serializers.ModelSerializer):
    class Meta (line 84) | class Meta:
  class WorkflowBpmnSerializer (line 89) | class WorkflowBpmnSerializer(serializers.ModelSerializer):
    class Meta (line 90) | class Meta:

FILE: backend/workflows/views.py
  class WorkflowTypeViewSet (line 9) | class WorkflowTypeViewSet(BulkModelMixin):
  class WorkflowViewSet (line 16) | class WorkflowViewSet(BulkModelMixin):
    method get_serializer_class (line 22) | def get_serializer_class(self):
  class StateViewSet (line 42) | class StateViewSet(BulkModelMixin):
  class TransitionViewSet (line 50) | class TransitionViewSet(BulkModelMixin):
    method get_serializer_class (line 56) | def get_serializer_class(self):
  class CustomFieldViewSet (line 62) | class CustomFieldViewSet(BulkModelMixin):
  class WorkflowBpmnViewSet (line 70) | class WorkflowBpmnViewSet(BulkModelMixin):

FILE: frontend/plop-templates/component/prompt.js
  method validate (line 31) | validate(value) {

FILE: frontend/plop-templates/store/prompt.js
  method validate (line 31) | validate(value) {
  method actions (line 39) | actions(data) {

FILE: frontend/plop-templates/view/prompt.js
  method validate (line 31) | validate(value) {

FILE: frontend/src/api/auths.js
  function login (line 3) | function login(data) {
  function getInfo (line 11) | function getInfo() {
  function requestMenuButton (line 18) | function requestMenuButton(menucode) {
  function changepwd (line 26) | function changepwd(data) {
  function getuser_by_group (line 34) | function getuser_by_group(data) {
  function getuser_by_roles (line 42) | function getuser_by_roles(data) {

FILE: frontend/src/api/common.js
  class Request (line 3) | class Request {
    method constructor (line 4) | constructor(apiurl) {
    method requestPost (line 8) | requestPost(data) {
    method requestDelete (line 16) | requestDelete(id) {
    method requestPut (line 23) | requestPut(id, data) {
    method requestGet (line 31) | requestGet(query) {
    method requestBulkPost (line 39) | requestBulkPost(data) {
    method requestBulkPut (line 47) | requestBulkPut(data) {
    method requestBulkDelete (line 55) | requestBulkDelete(data) {

FILE: frontend/src/components/Charts/mixins/resize.js
  method data (line 4) | data() {
  method mounted (line 10) | mounted() {
  method activated (line 13) | activated() {
  method beforeDestroy (line 22) | beforeDestroy() {
  method deactivated (line 25) | deactivated() {
  method $_sidebarResizeHandler (line 31) | $_sidebarResizeHandler(e) {
  method initListener (line 36) | initListener() {
  method destroyListener (line 45) | destroyListener() {
  method resize (line 51) | resize() {

FILE: frontend/src/directive/clipboard/clipboard.js
  method bind (line 8) | bind(el, binding) {
  method update (line 29) | update(el, binding) {
  method unbind (line 39) | unbind(el, binding) {

FILE: frontend/src/directive/sticky.js
  method inserted (line 5) | inserted(el, binding) {
  method unbind (line 84) | unbind() {

FILE: frontend/src/directive/waves/waves.js
  function handleClick (line 5) | function handleClick(el, binding) {
  method bind (line 60) | bind(el, binding) {
  method update (line 63) | update(el, binding) {
  method unbind (line 67) | unbind(el) {

FILE: frontend/src/filters/index.js
  function pluralize (line 4) | function pluralize(time, label) {
  function timeAgo (line 11) | function timeAgo(time) {
  function numberFormatter (line 23) | function numberFormatter(num, digits) {
  function toThousandFilter (line 40) | function toThousandFilter(num) {
  function parseDate (line 45) | function parseDate(datestr) {
  function diffDate (line 53) | function diffDate(date) {
  function menuTypeFilter (line 60) | function menuTypeFilter(val) {
  function operateTypeFilter (line 70) | function operateTypeFilter(val) {
  function FieldTypeFilter (line 82) | function FieldTypeFilter(val) {
  function StateTypeFilter (line 103) | function StateTypeFilter(val) {
  function TransitionTypeFilter (line 113) | function TransitionTypeFilter(val) {
  function AttributeTypeFilter (line 122) | function AttributeTypeFilter(val) {
  function TransitionNameFilter (line 136) | function TransitionNameFilter(val) {
  function AvatarFilter (line 148) | function AvatarFilter(val) {

FILE: frontend/src/lang/index.js
  function getLanguage (line 21) | function getLanguage() {

FILE: frontend/src/layout/mixin/ResizeHandler.js
  constant WIDTH (line 4) | const WIDTH = 992 // refer to Bootstrap's responsive design
  method $route (line 8) | $route(route) {
  method beforeMount (line 14) | beforeMount() {
  method beforeDestroy (line 17) | beforeDestroy() {
  method mounted (line 20) | mounted() {
  method $_isMobile (line 30) | $_isMobile() {
  method $_resizeHandler (line 34) | $_resizeHandler() {

FILE: frontend/src/router/index.js
  function resetRouter (line 80) | function resetRouter() {

FILE: frontend/src/store/modules/app.js
  method toggleSideBar (line 43) | toggleSideBar({ commit }) {
  method closeSideBar (line 46) | closeSideBar({ commit }, { withoutAnimation }) {
  method toggleDevice (line 49) | toggleDevice({ commit }, device) {
  method setLanguage (line 52) | setLanguage({ commit }, language) {
  method setSize (line 55) | setSize({ commit }, size) {

FILE: frontend/src/store/modules/permission.js
  function hasPermission (line 11) | function hasPermission(roles, route) {
  function filterAsyncRoutes (line 24) | function filterAsyncRoutes(routes, roles) {
  method generateRoutes2 (line 54) | generateRoutes2({ commit }, roles) {
  method generateRoutes (line 85) | generateRoutes({ commit }, data) {
  function convertRouter (line 105) | function convertRouter(asyncRouterMap) {
  function generateRouter (line 143) | function generateRouter(item, isParent) {

FILE: frontend/src/store/modules/settings.js
  method changeSetting (line 23) | changeSetting({ commit }, data) {

FILE: frontend/src/store/modules/tagsView.js
  method addView (line 72) | addView({ dispatch }, view) {
  method addVisitedView (line 76) | addVisitedView({ commit }, view) {
  method addCachedView (line 79) | addCachedView({ commit }, view) {
  method delView (line 83) | delView({ dispatch, state }, view) {
  method delVisitedView (line 93) | delVisitedView({ commit, state }, view) {
  method delCachedView (line 99) | delCachedView({ commit, state }, view) {
  method delOthersViews (line 106) | delOthersViews({ dispatch, state }, view) {
  method delOthersVisitedViews (line 116) | delOthersVisitedViews({ commit, state }, view) {
  method delOthersCachedViews (line 122) | delOthersCachedViews({ commit, state }, view) {
  method delAllViews (line 129) | delAllViews({ dispatch, state }, view) {
  method delAllVisitedViews (line 139) | delAllVisitedViews({ commit, state }) {
  method delAllCachedViews (line 145) | delAllCachedViews({ commit, state }) {
  method updateVisitedView (line 152) | updateVisitedView({ commit }, view) {

FILE: frontend/src/store/modules/user.js
  method login (line 37) | login({ commit }, userInfo) {
  method getInfo (line 53) | getInfo({ commit, state }) {
  method logout (line 80) | logout({ commit, state }) {
  method resetToken (line 91) | resetToken({ commit }) {

FILE: frontend/src/utils/auth.js
  function getToken (line 5) | function getToken() {
  function setToken (line 9) | function setToken(token) {
  function removeToken (line 13) | function removeToken() {

FILE: frontend/src/utils/clipboard.js
  function clipboardSuccess (line 4) | function clipboardSuccess() {
  function clipboardError (line 12) | function clipboardError() {
  function handleClipboard (line 19) | function handleClipboard(text, event) {

FILE: frontend/src/utils/get-page-title.js
  function getPageTitle (line 5) | function getPageTitle(pageTitle) {

FILE: frontend/src/utils/i18n.js
  function generateTitle (line 2) | function generateTitle(title) {

FILE: frontend/src/utils/index.js
  function parseTime (line 11) | function parseTime(time, cFormat) {
  function formatTime (line 59) | function formatTime(time, option) {
  function getQueryObject (line 101) | function getQueryObject(url) {
  function byteLength (line 120) | function byteLength(str) {
  function cleanArray (line 136) | function cleanArray(actual) {
  function param (line 150) | function param(json) {
  function param2Obj (line 164) | function param2Obj(url) {
  function html2Text (line 184) | function html2Text(val) {
  function objectMerge (line 196) | function objectMerge(target, source) {
  function toggleClass (line 218) | function toggleClass(element, className) {
  function getTime (line 238) | function getTime(type) {
  function debounce (line 252) | function debounce(func, wait, immediate) {
  function deepClone (line 294) | function deepClone(source) {
  function uniqueArr (line 313) | function uniqueArr(arr) {
  function createUniqueString (line 320) | function createUniqueString() {
  function hasClass (line 332) | function hasClass(ele, cls) {
  function addClass (line 341) | function addClass(ele, cls) {
  function removeClass (line 350) | function removeClass(ele, cls) {
  function GenDatetime (line 357) | function GenDatetime(date) {
  function groupBy (line 368) | function groupBy(list, key) {

FILE: frontend/src/utils/open-window.js
  function openWindow (line 8) | function openWindow(url, title, w, h) {

FILE: frontend/src/utils/permission.js
  function checkPermission (line 8) | function checkPermission(value) {
  function checkAuth (line 33) | function checkAuth(arr, value) {
  function checkAuthAdd (line 42) | function checkAuthAdd(arr) {
  function checkAuthDel (line 45) | function checkAuthDel(arr) {
  function checkAuthView (line 48) | function checkAuthView(arr) {
  function checkAuthUpdate (line 51) | function checkAuthUpdate(arr) {

FILE: frontend/src/utils/scroll-to.js
  function move (line 19) | function move(amount) {
  function position (line 25) | function position() {
  function scrollTo (line 34) | function scrollTo(to, duration, callback) {

FILE: frontend/src/utils/validate.js
  function isExternal (line 9) | function isExternal(path) {
  function validUsername (line 17) | function validUsername(str) {
  function validURL (line 26) | function validURL(url) {
  function validLowerCase (line 35) | function validLowerCase(str) {
  function validUpperCase (line 44) | function validUpperCase(str) {
  function validAlphabets (line 53) | function validAlphabets(str) {
  function validEmail (line 62) | function validEmail(email) {
  function isString (line 71) | function isString(str) {
  function isArray (line 82) | function isArray(arg) {

FILE: frontend/src/vendor/Export2Excel.js
  function generateArray (line 5) | function generateArray(table) {
  function datenum (line 55) | function datenum(v, date1904) {
  function sheet_from_array_of_arrays (line 61) | function sheet_from_array_of_arrays(data, opts) {
  function Workbook (line 103) | function Workbook() {
  function s2ab (line 109) | function s2ab(s) {
  function export_table_to_excel (line 116) | function export_table_to_excel(id) {
  function export_json_to_excel (line 147) | function export_json_to_excel({

FILE: frontend/src/vendor/Export2Zip.js
  function export_txt_to_zip (line 5) | function export_txt_to_zip(th, jsonData, txtName, zipName) {

FILE: frontend/src/views/dashboard/components/mixins/resize.js
  method data (line 4) | data() {
  method mounted (line 10) | mounted() {
  method beforeDestroy (line 19) | beforeDestroy() {
  method activated (line 25) | activated() {
  method deactivated (line 29) | deactivated() {
  method $_initResizeEvent (line 36) | $_initResizeEvent() {
  method $_destroyResizeEvent (line 39) | $_destroyResizeEvent() {
  method $_sidebarResizeHandler (line 42) | $_sidebarResizeHandler(e) {
  method $_initSidebarResizeEvent (line 47) | $_initSidebarResizeEvent() {
  method $_destroySidebarResizeEvent (line 51) | $_destroySidebarResizeEvent() {

FILE: frontend/vue.config.js
  function resolve (line 6) | function resolve(dir) {
  method chainWebpack (line 60) | chainWebpack(config) {
Condensed preview — 242 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,079K chars).
[
  {
    "path": ".gitignore",
    "chars": 105,
    "preview": "*.pyc\n.idea/\n.DS_Store\n.python-version\nupload/\n*.log\nnode_modules/\npackage-lock.json\n.vscode/\nvenv/\n*.db\n"
  },
  {
    "path": "500.html",
    "chars": 3596,
    "preview": "<html>\n<head>\n<style type=\"text/css\">\n*{\n  padding: 0;\n  margin: 0;\n  box-sizing: border-box;\n}\n\n*::before,\n*::after {\n "
  },
  {
    "path": "README.md",
    "chars": 1296,
    "preview": "# django + vue 工作流管理系统\n包含 `用户`、`角色`、`菜单`、`权限` 管理, 这是基础的工作流系统,初始化会生成请假工作流, 也可以自行配置其他工作流比如,发布工单等。\n\n[comment]: <> (- 后端mode"
  },
  {
    "path": "backend/__init__.py",
    "chars": 42,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n"
  },
  {
    "path": "backend/celery.service",
    "chars": 348,
    "preview": "[Unit]\nDescription=start celery worker\n\n[Service]\nExecStart=/bin/bash -c 'cd /opt/projects/one-oms/backend; /root/.pyenv"
  },
  {
    "path": "backend/common/JSONRenderer.py",
    "chars": 883,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom rest_framework.renderers import JSONRenderer\n\n\nclass CustomJSONRenderer(J"
  },
  {
    "path": "backend/common/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/common/dispath.py",
    "chars": 1334,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport six\nfrom rest_framework.serializers import Serializer\nfrom rest_framewo"
  },
  {
    "path": "backend/common/django.py",
    "chars": 231,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom django.utils.deprecation import MiddlewareMixin\n\nclass DisableCSRF(Middlew"
  },
  {
    "path": "backend/common/exceptions.py",
    "chars": 1635,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom rest_framework.views import exception_handler\nfrom common import status\n\n"
  },
  {
    "path": "backend/common/models.py",
    "chars": 395,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.db import models\n\n\nclass BaseModel(models.Model):\n    create_time "
  },
  {
    "path": "backend/common/pagination.py",
    "chars": 1671,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom collections import OrderedDict\n\nfrom rest_framework.pagination import Pag"
  },
  {
    "path": "backend/common/status.py",
    "chars": 2099,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom __future__ import unicode_literals\n\n\ndef is_informational(code):\n    retu"
  },
  {
    "path": "backend/common/views.py",
    "chars": 9043,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom __future__ import print_function, unicode_literals\n\nimport json\nfrom rest"
  },
  {
    "path": "backend/core/__init__.py",
    "chars": 216,
    "preview": "from __future__ import absolute_import, unicode_literals\n# This will make sure the app is always imported when\n# Django "
  },
  {
    "path": "backend/core/celery.py",
    "chars": 812,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom __future__ import absolute_import, unicode_literals\nimport os\nfrom celery"
  },
  {
    "path": "backend/core/settings/__init__.py",
    "chars": 297,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport platform\nfrom .base import *\n\nos_type = platform.system()\n\nif os_type ="
  },
  {
    "path": "backend/core/settings/base.py",
    "chars": 4965,
    "preview": "\"\"\"\nDjango settings for core project.\n\nGenerated by 'django-admin startproject' using Django 2.1.1.\n\nFor more informatio"
  },
  {
    "path": "backend/core/settings/dev.py",
    "chars": 979,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport os\n\nAPP_ENV = 'dev'\n\n# SECURITY WARNING: keep the secret key used in pr"
  },
  {
    "path": "backend/core/settings/mac.py",
    "chars": 979,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport os\n\nAPP_ENV = 'dev'\n\n# SECURITY WARNING: keep the secret key used in pr"
  },
  {
    "path": "backend/core/settings/prod.py",
    "chars": 605,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport os\n\nAPP_ENV = 'prod'\n\n# SECURITY WARNING: keep the secret key used in p"
  },
  {
    "path": "backend/core/urls.py",
    "chars": 1342,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom django.conf.urls import url, include\nfrom django.conf.urls.static import s"
  },
  {
    "path": "backend/core/wsgi.py",
    "chars": 385,
    "preview": "\"\"\"\nWSGI config for core project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor m"
  },
  {
    "path": "backend/dev_requirements.txt",
    "chars": 416,
    "preview": "asgiref==3.2.3\ncertifi==2019.11.28\nchardet==3.0.4\ncoreapi==2.3.3\ncoreschema==0.0.4\nDjango==3.0.3\ndjango-cors-headers==3."
  },
  {
    "path": "backend/init.sh",
    "chars": 372,
    "preview": "#!/bin/bash\n\napps=(systems tools notices workflows tickets)\nrm -rf core.db\nfor app in ${apps[@]};do\n  rm -rf $app/migrat"
  },
  {
    "path": "backend/manage.py",
    "chars": 742,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport os\nimport sys\n\nif __name__ == '__main__':\n    os.environ.setdefault('DJ"
  },
  {
    "path": "backend/notices/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/notices/management/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/notices/management/commands/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/notices/management/commands/init_notice.py",
    "chars": 1079,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.core.management.base import BaseCommand, CommandError\nfrom systems"
  },
  {
    "path": "backend/notices/migrations/0001_initial.py",
    "chars": 2470,
    "preview": "# Generated by Django 3.0.3 on 2021-06-27 00:05\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.M"
  },
  {
    "path": "backend/notices/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/notices/models.py",
    "chars": 1344,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom django.db import models\nfrom common.models import BaseModel\n\nnotice_type ="
  },
  {
    "path": "backend/notices/serializers.py",
    "chars": 363,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom notices.models import *\nfrom rest_framework import serializers\n\n\nclass Mai"
  },
  {
    "path": "backend/notices/urls.py",
    "chars": 401,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n\nfrom django.conf.urls import url\nfrom rest_framework import routers\nfrom noti"
  },
  {
    "path": "backend/notices/views.py",
    "chars": 2523,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom notices.serializers import *\nfrom common.views import ModelViewSet, JsonRe"
  },
  {
    "path": "backend/oms.ini",
    "chars": 258,
    "preview": "[uwsgi]\nproject = core\nbase = /data/app/one/backend\n\nchdir = %(base)\nmodule = %(project).wsgi:application\n\nmaster = true"
  },
  {
    "path": "backend/oms.service",
    "chars": 339,
    "preview": "[Unit]\nDescription=uWSGI instance to serve one-oms\n\n[Service]\nType=simple\nUser=root\nGroup=root\nWorkingDirectory=/data/ap"
  },
  {
    "path": "backend/requirements.txt",
    "chars": 448,
    "preview": "asgiref==3.2.3\ncertifi==2019.11.28\nchardet==3.0.4\ncoreapi==2.3.3\ncoreschema==0.0.4\nDjango==3.0.3\ndjango-cors-headers==3."
  },
  {
    "path": "backend/systems/__init__.py",
    "chars": 41,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\n"
  },
  {
    "path": "backend/systems/admin.py",
    "chars": 168,
    "preview": "from django.contrib import admin\nfrom systems.models import *\n\nadmin.site.register(Menu)\nadmin.site.register(Role)\nadmin"
  },
  {
    "path": "backend/systems/management/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/systems/management/commands/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/systems/management/commands/init_sys.py",
    "chars": 3600,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.core.management.base import BaseCommand, CommandError\nfrom systems"
  },
  {
    "path": "backend/systems/menus.py",
    "chars": 3692,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom systems.models import *\nfrom itertools import chain\n\n\n# 获取管理员权限下所有菜单\ndef "
  },
  {
    "path": "backend/systems/migrations/0001_initial.py",
    "chars": 6647,
    "preview": "# Generated by Django 3.0.3 on 2021-06-27 00:05\n\nimport django.contrib.auth.models\nfrom django.db import migrations, mod"
  },
  {
    "path": "backend/systems/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/systems/models.py",
    "chars": 5533,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom django.db import models\nfrom django.contrib.auth.models import Permission,"
  },
  {
    "path": "backend/systems/permissions.py",
    "chars": 1423,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n\nfrom rest_framework.permissions import BasePermission\nfrom systems.models imp"
  },
  {
    "path": "backend/systems/serializers.py",
    "chars": 2392,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom systems.models import *\nfrom systems.menus import init_menu\nfrom rest_fram"
  },
  {
    "path": "backend/systems/urls.py",
    "chars": 902,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n\nfrom django.conf.urls import url, include\nfrom rest_framework import routers\n"
  },
  {
    "path": "backend/systems/views.py",
    "chars": 4433,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom systems.serializers import *\nfrom common.views import ModelViewSet, FKMode"
  },
  {
    "path": "backend/tickets/__init__.py",
    "chars": 42,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n"
  },
  {
    "path": "backend/tickets/filters.py",
    "chars": 439,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom tickets.models import *\nfrom django_filters import rest_framework as filt"
  },
  {
    "path": "backend/tickets/management/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/tickets/management/commands/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/tickets/management/commands/init_ticket.py",
    "chars": 2118,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.core.management.base import BaseCommand, CommandError\nfrom systems"
  },
  {
    "path": "backend/tickets/migrations/0001_initial.py",
    "chars": 5565,
    "preview": "# Generated by Django 3.0.3 on 2021-06-27 00:05\n\nfrom django.conf import settings\nfrom django.db import migrations, mode"
  },
  {
    "path": "backend/tickets/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/tickets/models.py",
    "chars": 3138,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.db import models\nfrom common.models import BaseModel\nfrom workflow"
  },
  {
    "path": "backend/tickets/serializers.py",
    "chars": 5437,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom tickets.models import *\nfrom workflows.models import *\nfrom rest_framewor"
  },
  {
    "path": "backend/tickets/urls.py",
    "chars": 520,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n\nfrom django.conf.urls import url, include\nfrom rest_framework import routers\n"
  },
  {
    "path": "backend/tickets/views.py",
    "chars": 1968,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom tickets.serializers import *\nfrom tickets.filters import *\nfrom common.vi"
  },
  {
    "path": "backend/tools/__init__.py",
    "chars": 41,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\n"
  },
  {
    "path": "backend/tools/filesize.py",
    "chars": 349,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nimport math\n\n\ndef convert_size(size_bytes):\n    if size_bytes == 0:\n        ret"
  },
  {
    "path": "backend/tools/migrations/0001_initial.py",
    "chars": 3469,
    "preview": "# Generated by Django 3.0.3 on 2021-06-27 00:05\n\nfrom django.db import migrations, models\nimport tools.storage\n\n\nclass M"
  },
  {
    "path": "backend/tools/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/tools/models.py",
    "chars": 2293,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom django.db import models\nfrom common.models import BaseModel\nfrom tools.fil"
  },
  {
    "path": "backend/tools/serializers.py",
    "chars": 623,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom rest_framework import serializers\nfrom tools.models import *\n\n\nclass Uploa"
  },
  {
    "path": "backend/tools/storage.py",
    "chars": 490,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nimport os\nfrom django.utils.deconstruct import deconstructible\nfrom re import s"
  },
  {
    "path": "backend/tools/urls.py",
    "chars": 466,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n\nfrom django.conf.urls import url\nfrom rest_framework import routers\nfrom tool"
  },
  {
    "path": "backend/tools/views.py",
    "chars": 1017,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom rest_framework import viewsets, permissions\nfrom tools.models import *\nfro"
  },
  {
    "path": "backend/utils/__init__.py",
    "chars": 41,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\n"
  },
  {
    "path": "backend/utils/get_realip.py",
    "chars": 811,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n\nimport requests\nimport socket\nimport json\n\n#获取外网ip信息\noutput = requests.get('h"
  },
  {
    "path": "backend/utils/index.py",
    "chars": 609,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nfrom datetime import datetime, timedelta\nimport time\n\n\ndef gen_time_pid(prefix)"
  },
  {
    "path": "backend/utils/mysql.py",
    "chars": 1491,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport MySQLdb\n\n\nclass MYSQL:\n    def __init__(self, db, sql):\n        self.sq"
  },
  {
    "path": "backend/utils/sendmail.py",
    "chars": 1653,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nimport sys\nimport smtplib\nfrom email.mime.text import MIMEText\nfrom email.mime."
  },
  {
    "path": "backend/utils/sendskype.py",
    "chars": 454,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n# 登录skype\nfrom skpy import Skype\n\n# skype账号\nSK_ACOUNT = {\n    'sk_user': 'itim"
  },
  {
    "path": "backend/utils/test.py",
    "chars": 83,
    "preview": "#!/bin/bash\n\nid=$1\necho $id\ncp -r site-10 site-$id\ncd site-$id\nsed -i \"s/10/$id/\" *"
  },
  {
    "path": "backend/utils/time.py",
    "chars": 1516,
    "preview": "# -*- coding: utf-8 -*-\n# author: timor\n\nimport time\nimport datetime\n\n\ndef utc2local(utc_st):\n    \"\"\"\n    UTC时间转本地时间 +8:"
  },
  {
    "path": "backend/utils/verifys.py",
    "chars": 925,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nimport re\nimport IPy\n\n\ndef is_valid_domain(value):\n    domain_pattern = re.com"
  },
  {
    "path": "backend/workflows/__init__.py",
    "chars": 42,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n"
  },
  {
    "path": "backend/workflows/admin.py",
    "chars": 99,
    "preview": "from django.contrib import admin\nfrom workflows.models import *\n\nadmin.site.register(WorkflowBpmn)\n"
  },
  {
    "path": "backend/workflows/management/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/workflows/management/commands/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/workflows/management/commands/init_leave.py",
    "chars": 11898,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.core.management.base import BaseCommand, CommandError\nfrom django."
  },
  {
    "path": "backend/workflows/management/commands/init_wf.py",
    "chars": 1153,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.core.management.base import BaseCommand, CommandError\nfrom systems"
  },
  {
    "path": "backend/workflows/migrations/0001_initial.py",
    "chars": 11455,
    "preview": "# Generated by Django 3.0.3 on 2021-06-27 00:05\n\nfrom django.conf import settings\nfrom django.db import migrations, mode"
  },
  {
    "path": "backend/workflows/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "backend/workflows/models.py",
    "chars": 7795,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom django.db import models\nfrom common.models import BaseModel\nfrom systems."
  },
  {
    "path": "backend/workflows/serializers.py",
    "chars": 2780,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom workflows.models import *\nfrom rest_framework import serializers\n\n\nclass "
  },
  {
    "path": "backend/workflows/urls.py",
    "chars": 637,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\n\nfrom django.conf.urls import url, include\nfrom rest_framework import routers\n"
  },
  {
    "path": "backend/workflows/views.py",
    "chars": 2442,
    "preview": "# -*- coding: utf-8 -*-\n# author: itimor\n\nfrom itertools import chain\nfrom workflows.serializers import *\nfrom common.vi"
  },
  {
    "path": "frontend/.editorconfig",
    "chars": 244,
    "preview": "# https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert"
  },
  {
    "path": "frontend/.eslintignore",
    "chars": 34,
    "preview": "build/*.js\nsrc/assets\npublic\ndist\n"
  },
  {
    "path": "frontend/.eslintrc.js",
    "chars": 5120,
    "preview": "module.exports = {\n  root: true,\n  parserOptions: {\n    parser: 'babel-eslint',\n    sourceType: 'module'\n  },\n  env: {\n "
  },
  {
    "path": "frontend/.travis.yml",
    "chars": 81,
    "preview": "language: node_js\nnode_js: 10\nscript: npm run test\nnotifications:\n  email: false\n"
  },
  {
    "path": "frontend/LICENSE",
    "chars": 1075,
    "preview": "MIT License\n\nCopyright (c) 2017-present PanJiaChen\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "frontend/babel.config.js",
    "chars": 53,
    "preview": "module.exports = {\n  presets: [\n    '@vue/app'\n  ]\n}\n"
  },
  {
    "path": "frontend/build/index.js",
    "chars": 892,
    "preview": "const { run } = require('runjs')\nconst chalk = require('chalk')\nconst config = require('../vue.config.js')\nconst rawArgv"
  },
  {
    "path": "frontend/jest.config.js",
    "chars": 766,
    "preview": "module.exports = {\n  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],\n  transform: {\n    '^.+\\\\.vue$': 'vue-jest',\n  "
  },
  {
    "path": "frontend/jsconfig.json",
    "chars": 137,
    "preview": "{ \n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"paths\": {\n        \"@/*\": [\"src/*\"]\n    }\n  },\n  \"exclude\": [\"node_mo"
  },
  {
    "path": "frontend/package.json",
    "chars": 2434,
    "preview": "{\n  \"name\": \"vue-element-admin\",\n  \"version\": \"4.0.0\",\n  \"description\": \"A magical vue admin. An out-of-box UI solution "
  },
  {
    "path": "frontend/plop-templates/component/index.hbs",
    "chars": 302,
    "preview": "{{#if template}}\n<template>\n  <div />\n</template>\n{{/if}}\n\n{{#if script}}\n<script>\nexport default {\n  name: '{{ properCa"
  },
  {
    "path": "frontend/plop-templates/component/prompt.js",
    "chars": 1202,
    "preview": "const { notEmpty } = require('../utils.js')\n\nmodule.exports = {\n  description: 'generate vue component',\n  prompts: [{\n "
  },
  {
    "path": "frontend/plop-templates/store/index.hbs",
    "chars": 185,
    "preview": "{{#if state}}\nconst state = {}\n{{/if}}\n\n{{#if mutations}}\nconst mutations = {}\n{{/if}}\n\n{{#if actions}}\nconst actions = "
  },
  {
    "path": "frontend/plop-templates/store/prompt.js",
    "chars": 1319,
    "preview": "const { notEmpty } = require('../utils.js')\n\nmodule.exports = {\n  description: 'generate store',\n  prompts: [{\n    type:"
  },
  {
    "path": "frontend/plop-templates/utils.js",
    "chars": 154,
    "preview": "exports.notEmpty = name => {\n  return v => {\n    if (!v || v.trim === '') {\n      return `${name} is required`\n    } els"
  },
  {
    "path": "frontend/plop-templates/view/index.hbs",
    "chars": 302,
    "preview": "{{#if template}}\n<template>\n  <div />\n</template>\n{{/if}}\n\n{{#if script}}\n<script>\nexport default {\n  name: '{{ properCa"
  },
  {
    "path": "frontend/plop-templates/view/prompt.js",
    "chars": 1163,
    "preview": "const { notEmpty } = require('../utils.js')\n\nmodule.exports = {\n  description: 'generate a view',\n  prompts: [{\n    type"
  },
  {
    "path": "frontend/plopfile.js",
    "chars": 379,
    "preview": "const viewGenerator = require('./plop-templates/view/prompt')\nconst componentGenerator = require('./plop-templates/compo"
  },
  {
    "path": "frontend/postcss.config.js",
    "chars": 59,
    "preview": "module.exports = {\n  plugins: {\n    autoprefixer: {}\n  }\n}\n"
  },
  {
    "path": "frontend/public/index.html",
    "chars": 530,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrom"
  },
  {
    "path": "frontend/src/App.vue",
    "chars": 389,
    "preview": "<template>\n  <div id=\"app\">\n    <router-view />\n    <el-backtop :visibility-height=\"100\">\n      <el-tooltip placement=\"t"
  },
  {
    "path": "frontend/src/api/all.js",
    "chars": 1168,
    "preview": "import Request from '@/api/common'\n\n// auth\nimport * as auths from '@/api/auths'\nexport const auth = auths\n\n// systems\ne"
  },
  {
    "path": "frontend/src/api/auths.js",
    "chars": 818,
    "preview": "import request from '@/utils/request'\n\nexport function login(data) {\n  return request({\n    url: '/sys/auth/jwt-token-au"
  },
  {
    "path": "frontend/src/api/common.js",
    "chars": 996,
    "preview": "import request from '@/utils/request'\n\nexport default class Request {\n  constructor(apiurl) {\n    this.apiurl = apiurl;\n"
  },
  {
    "path": "frontend/src/assets/custom-theme/index.css",
    "chars": 423600,
    "preview": "@charset \"UTF-8\";.custom-theme .fade-in-linear-enter-active,.custom-theme .fade-in-linear-leave-active{-webkit-transitio"
  },
  {
    "path": "frontend/src/components/Breadcrumb/index.vue",
    "chars": 2255,
    "preview": "<template>\n  <el-breadcrumb class=\"app-breadcrumb\" separator=\"/\">\n    <transition-group name=\"breadcrumb\">\n      <el-bre"
  },
  {
    "path": "frontend/src/components/Charts/keyboard.vue",
    "chars": 3249,
    "preview": "<template>\n  <div :id=\"id\" :class=\"className\" :style=\"{height:height,width:width}\" />\n</template>\n\n<script>\nimport echar"
  },
  {
    "path": "frontend/src/components/Charts/lineMarker.vue",
    "chars": 5365,
    "preview": "<template>\n  <div :id=\"id\" :class=\"className\" :style=\"{height:height,width:width}\" />\n</template>\n\n<script>\nimport echar"
  },
  {
    "path": "frontend/src/components/Charts/mixChart.vue",
    "chars": 5586,
    "preview": "<template>\n  <div :id=\"id\" :class=\"className\" :style=\"{height:height,width:width}\" />\n</template>\n\n<script>\nimport echar"
  },
  {
    "path": "frontend/src/components/Charts/mixins/resize.js",
    "chars": 1418,
    "preview": "import { debounce } from '@/utils'\n\nexport default {\n  data() {\n    return {\n      $_sidebarElm: null,\n      $_resizeHan"
  },
  {
    "path": "frontend/src/components/DndList/index.vue",
    "chars": 3706,
    "preview": "<template>\n  <div class=\"dndList\">\n    <div :style=\"{width:width1}\" class=\"dndList-list\">\n      <h3>{{ list1Title }}</h3"
  },
  {
    "path": "frontend/src/components/Hamburger/index.vue",
    "chars": 1156,
    "preview": "<template>\n  <div style=\"padding: 0 15px;\" @click=\"toggleClick\">\n    <svg\n      :class=\"{'is-active':isActive}\"\n      cl"
  },
  {
    "path": "frontend/src/components/HeaderSearch/index.vue",
    "chars": 4440,
    "preview": "<template>\n  <div :class=\"{'show':show}\" class=\"header-search\">\n    <svg-icon class-name=\"search-icon\" icon-class=\"searc"
  },
  {
    "path": "frontend/src/components/Kanban/index.vue",
    "chars": 1896,
    "preview": "<template>\n  <div class=\"board-column\">\n    <div class=\"board-column-header\">\n      {{ headerText }}\n    </div>\n    <dra"
  },
  {
    "path": "frontend/src/components/LangSelect/index.vue",
    "chars": 855,
    "preview": "<template>\n  <el-dropdown trigger=\"click\" class=\"international\" @command=\"handleSetLanguage\">\n    <div>\n      <svg-icon "
  },
  {
    "path": "frontend/src/components/MDinput/index.vue",
    "chars": 8451,
    "preview": "<template>\n  <div :class=\"computedClasses\" class=\"material-input__component\">\n    <div :class=\"{iconClass:icon}\">\n      "
  },
  {
    "path": "frontend/src/components/Pagination/index.vue",
    "chars": 1882,
    "preview": "<template>\n  <div :class=\"{'hidden':hidden}\" class=\"pagination-container\">\n    <el-pagination\n      :background=\"backgro"
  },
  {
    "path": "frontend/src/components/PanThumb/index.vue",
    "chars": 2850,
    "preview": "<template>\n  <div :style=\"{zIndex:zIndex,height:height,width:width}\" class=\"pan-item\">\n    <div class=\"pan-info\">\n      "
  },
  {
    "path": "frontend/src/components/RightPanel/index.vue",
    "chars": 2930,
    "preview": "<template>\n  <div ref=\"rightPanel\" :class=\"{show:show}\" class=\"rightPanel-container\">\n    <div class=\"rightPanel-backgro"
  },
  {
    "path": "frontend/src/components/Screenfull/index.vue",
    "chars": 1049,
    "preview": "<template>\n  <div>\n    <svg-icon :icon-class=\"isFullscreen?'exit-fullscreen':'fullscreen'\" @click=\"click\" />\n  </div>\n</"
  },
  {
    "path": "frontend/src/components/Share/DropdownMenu.vue",
    "chars": 2092,
    "preview": "<template>\n  <div :class=\"{active:isActive}\" class=\"share-dropdown-menu\">\n    <div class=\"share-dropdown-menu-wrapper\">\n"
  },
  {
    "path": "frontend/src/components/Sticky/index.vue",
    "chars": 1932,
    "preview": "<template>\n  <div :style=\"{height:height+'px',zIndex:zIndex}\">\n    <div\n      :class=\"className\"\n      :style=\"{top:(isS"
  },
  {
    "path": "frontend/src/components/SvgIcon/index.vue",
    "chars": 1312,
    "preview": "<template>\n  <div v-if=\"isExternal\" :style=\"styleExternalIcon\" class=\"svg-external-icon svg-icon\" v-on=\"$listeners\" />\n "
  },
  {
    "path": "frontend/src/components/TextHoverEffect/Mallki.vue",
    "chars": 2315,
    "preview": "<template>\n  <a :class=\"className\" class=\"link--mallki\" href=\"#\">\n    {{ text }}\n    <span :data-letters=\"text\" />\n    <"
  },
  {
    "path": "frontend/src/components/ThemePicker/index.vue",
    "chars": 4915,
    "preview": "<template>\n  <el-color-picker\n    v-model=\"theme\"\n    :predefine=\"['#409EFF', '#1890ff', '#304156','#212121','#11a983', "
  },
  {
    "path": "frontend/src/components/TreeSelect/index.vue",
    "chars": 3809,
    "preview": "<template>\n  <el-select\n    :placeholder=\"props.placeholder\"\n    :value=\"valueTitle\"\n    :clearable=\"clearable\"\n    @cle"
  },
  {
    "path": "frontend/src/directive/clipboard/clipboard.js",
    "chars": 1546,
    "preview": "// Inspired by https://github.com/Inndy/vue-clipboard2\nconst Clipboard = require('clipboard')\nif (!Clipboard) {\n  throw "
  },
  {
    "path": "frontend/src/directive/clipboard/index.js",
    "chars": 260,
    "preview": "import Clipboard from './clipboard'\n\nconst install = function(Vue) {\n  Vue.directive('Clipboard', Clipboard)\n}\n\nif (wind"
  },
  {
    "path": "frontend/src/directive/sticky.js",
    "chars": 2492,
    "preview": "const vueSticky = {}\nlet listenAction\nvueSticky.install = Vue => {\n  Vue.directive('sticky', {\n    inserted(el, binding)"
  },
  {
    "path": "frontend/src/directive/waves/index.js",
    "chars": 228,
    "preview": "import waves from './waves'\n\nconst install = function(Vue) {\n  Vue.directive('waves', waves)\n}\n\nif (window.Vue) {\n  wind"
  },
  {
    "path": "frontend/src/directive/waves/waves.css",
    "chars": 825,
    "preview": ".waves-ripple {\n    position: absolute;\n    border-radius: 100%;\n    background-color: rgba(0, 0, 0, 0.15);\n    backgrou"
  },
  {
    "path": "frontend/src/directive/waves/waves.js",
    "chars": 2161,
    "preview": "import './waves.css'\n\nconst context = '@@wavesContext'\n\nfunction handleClick(el, binding) {\n  function handle(e) {\n    c"
  },
  {
    "path": "frontend/src/filters/index.js",
    "chars": 2764,
    "preview": "// set function parseTime,formatTime to filter\nexport { parseTime, formatTime } from '@/utils'\n\nfunction pluralize(time,"
  },
  {
    "path": "frontend/src/icons/index.js",
    "chars": 288,
    "preview": "import Vue from 'vue'\nimport SvgIcon from '@/components/SvgIcon'// svg component\n\n// register globally\nVue.component('sv"
  },
  {
    "path": "frontend/src/icons/svgo.yml",
    "chars": 248,
    "preview": "# replace default config\n\n# multipass: true\n# full: true\n\nplugins:\n\n  # - name\n  #\n  # or:\n  # - name: false\n  # - name:"
  },
  {
    "path": "frontend/src/lang/en.js",
    "chars": 5112,
    "preview": "export default {\n  systemTitle: 'OMS',\n  \n  route: {\n    login: 'Login',\n    dashboard: 'Dashboard',\n    sys: 'sysManage"
  },
  {
    "path": "frontend/src/lang/index.js",
    "chars": 1034,
    "preview": "import Vue from 'vue'\nimport VueI18n from 'vue-i18n'\nimport Cookies from 'js-cookie'\nimport elementEnLocale from 'elemen"
  },
  {
    "path": "frontend/src/lang/zh.js",
    "chars": 3178,
    "preview": "export default {\n  systemTitle: '后台管理系统',\n\n  route: {\n    login: '登录',\n    dashboard: '首页',\n    sys: '系统管理',\n    group: "
  },
  {
    "path": "frontend/src/layout/components/AppMain.vue",
    "chars": 969,
    "preview": "<template>\n  <section class=\"app-main\">\n    <transition name=\"fade-transform\" mode=\"out-in\">\n      <keep-alive :include="
  },
  {
    "path": "frontend/src/layout/components/Navbar.vue",
    "chars": 3883,
    "preview": "<template>\n  <div class=\"navbar\">\n    <hamburger\n      id=\"hamburger-container\"\n      :is-active=\"sidebar.opened\"\n      "
  },
  {
    "path": "frontend/src/layout/components/Settings/index.vue",
    "chars": 2323,
    "preview": "<template>\n  <div class=\"drawer-container\">\n    <div>\n      <h3 class=\"drawer-title\">{{ $t('settings.title') }}</h3>\n\n  "
  },
  {
    "path": "frontend/src/layout/components/Sidebar/Item.vue",
    "chars": 468,
    "preview": "<script>\nexport default {\n  name: 'MenuItem',\n  functional: true,\n  props: {\n    icon: {\n      type: String,\n      defau"
  },
  {
    "path": "frontend/src/layout/components/Sidebar/Link.vue",
    "chars": 567,
    "preview": "\n<template>\n  <!-- eslint-disable vue/require-component-is -->\n  <component v-bind=\"linkProps(to)\">\n    <slot />\n  </com"
  },
  {
    "path": "frontend/src/layout/components/Sidebar/Logo.vue",
    "chars": 1840,
    "preview": "<template>\n  <div class=\"sidebar-logo-container\" :class=\"{'collapse':collapse}\">\n    <transition name=\"sidebarLogoFade\">"
  },
  {
    "path": "frontend/src/layout/components/Sidebar/SidebarItem.vue",
    "chars": 2634,
    "preview": "<template>\n  <div v-if=\"!item.hidden\" class=\"menu-wrapper\">\n    <template v-if=\"hasOneShowingChild(item.children,item) &"
  },
  {
    "path": "frontend/src/layout/components/Sidebar/index.vue",
    "chars": 1390,
    "preview": "<template>\n  <div :class=\"{'has-logo':showLogo}\">\n    <logo v-if=\"showLogo\" :collapse=\"isCollapse\" />\n    <el-scrollbar "
  },
  {
    "path": "frontend/src/layout/components/TagsView/ScrollPane.vue",
    "chars": 2391,
    "preview": "<template>\n  <el-scrollbar ref=\"scrollContainer\" :vertical=\"false\" class=\"scroll-container\" @wheel.native.prevent=\"handl"
  },
  {
    "path": "frontend/src/layout/components/TagsView/index.vue",
    "chars": 7805,
    "preview": "<template>\n  <div id=\"tags-view-container\" class=\"tags-view-container\">\n    <scroll-pane ref=\"scrollPane\" class=\"tags-vi"
  },
  {
    "path": "frontend/src/layout/components/index.js",
    "chars": 257,
    "preview": "export { default as AppMain } from './AppMain'\nexport { default as Navbar } from './Navbar'\nexport { default as Settings"
  },
  {
    "path": "frontend/src/layout/index.vue",
    "chars": 2268,
    "preview": "<template>\n  <div :class=\"classObj\" class=\"app-wrapper\">\n    <div v-if=\"device==='mobile'&&sidebar.opened\" class=\"drawer"
  },
  {
    "path": "frontend/src/layout/mixin/ResizeHandler.js",
    "chars": 1235,
    "preview": "import store from '@/store'\n\nconst { body } = document\nconst WIDTH = 992 // refer to Bootstrap's responsive design\n\nexpo"
  },
  {
    "path": "frontend/src/main.js",
    "chars": 867,
    "preview": "import Vue from 'vue'\n\nimport Cookies from 'js-cookie'\n\nimport 'normalize.css/normalize.css' // a modern alternative to "
  },
  {
    "path": "frontend/src/permission.js",
    "chars": 2478,
    "preview": "import router from './router'\nimport store from './store'\nimport { Message } from 'element-ui'\nimport NProgress from 'np"
  },
  {
    "path": "frontend/src/router/index.js",
    "chars": 2735,
    "preview": "import Vue from 'vue'\nimport Router from 'vue-router'\n\nVue.use(Router)\n\n/* Layout */\nimport Layout from '@/layout'\n\n/* R"
  },
  {
    "path": "frontend/src/router/modules/components.js",
    "chars": 1195,
    "preview": "/** When your routing table is too long, you can split it into small modules **/\n\nimport Layout from '@/layout'\n\nconst c"
  },
  {
    "path": "frontend/src/settings.js",
    "chars": 857,
    "preview": "// import i18n from '@/lang'\n\nmodule.exports = {\n  // title: i18n.t('systemTitle'),\n  title: '后台管理系统',\n\n  /**\n   * @type"
  },
  {
    "path": "frontend/src/store/getters.js",
    "chars": 624,
    "preview": "const getters = {\n  sidebar: state => state.app.sidebar,\n  language: state => state.app.language,\n  size: state => state"
  },
  {
    "path": "frontend/src/store/index.js",
    "chars": 687,
    "preview": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport getters from './getters'\n\nVue.use(Vuex)\n\n// https://webpack.js.org/"
  },
  {
    "path": "frontend/src/store/modules/app.js",
    "chars": 1511,
    "preview": "import Cookies from 'js-cookie'\nimport { getLanguage } from '@/lang/index'\n\nconst state = {\n  sidebar: {\n    opened: Coo"
  },
  {
    "path": "frontend/src/store/modules/permission.js",
    "chars": 5184,
    "preview": "import { asyncRoutes, constantRoutes } from '@/router'\n// import { getMenus } from '@/api/user'\n/* Layout */\nimport Layo"
  },
  {
    "path": "frontend/src/store/modules/settings.js",
    "chars": 638,
    "preview": "import variables from '@/styles/element-variables.scss'\nimport defaultSettings from '@/settings'\n\nconst { showSettings, "
  },
  {
    "path": "frontend/src/store/modules/tagsView.js",
    "chars": 4154,
    "preview": "const state = {\n  visitedViews: [],\n  cachedViews: []\n}\n\nconst mutations = {\n  ADD_VISITED_VIEW: (state, view) => {\n    "
  },
  {
    "path": "frontend/src/store/modules/user.js",
    "chars": 2409,
    "preview": "import { auth } from '@/api/all'\nimport { getToken, setToken, removeToken } from '@/utils/auth'\nimport { resetRouter } f"
  },
  {
    "path": "frontend/src/styles/btn.scss",
    "chars": 1407,
    "preview": "@import './variables.scss';\n\n@mixin colorBtn($color) {\n  background: $color;\n\n  &:hover {\n    color: $color;\n\n    &:befo"
  },
  {
    "path": "frontend/src/styles/csshake.scss",
    "chars": 3078,
    "preview": "// Variables\n$prefix: 'shake' !default;\n$trigger: 'shake-trigger' !default;\n\n// Placeholders\n%shake {\n  display: inherit"
  },
  {
    "path": "frontend/src/styles/element-ui.scss",
    "chars": 1445,
    "preview": "// cover some element-ui styles\n\n.el-breadcrumb__inner,\n.el-breadcrumb__inner a {\n  font-weight: 400 !important;\n}\n\n.el-"
  },
  {
    "path": "frontend/src/styles/element-variables.scss",
    "chars": 782,
    "preview": "/**\n* I think element-ui's default theme color is too light for long-term use.\n* So I modified the default color and you"
  },
  {
    "path": "frontend/src/styles/index.scss",
    "chars": 3116,
    "preview": "@import \"./variables.scss\";\n@import \"./mixin.scss\";\n@import \"./transition.scss\";\n@import \"./element-ui.scss\";\n@import \"."
  },
  {
    "path": "frontend/src/styles/mixin.scss",
    "chars": 1311,
    "preview": "@mixin clearfix {\n  &:after {\n    content: \"\";\n    display: table;\n    clear: both;\n  }\n}\n\n@mixin scrollBar {\n  &::-webk"
  },
  {
    "path": "frontend/src/styles/sidebar.scss",
    "chars": 3701,
    "preview": "#app {\n\n  .main-container {\n    min-height: 100%;\n    transition: margin-left .28s;\n    margin-left: $sideBarWidth;\n    "
  },
  {
    "path": "frontend/src/styles/transition.scss",
    "chars": 714,
    "preview": "// global transition css\n\n/* fade */\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.28s;\n}\n\n.fade-ente"
  },
  {
    "path": "frontend/src/styles/variables.scss",
    "chars": 771,
    "preview": "// base color\n$blue:#324157;\n$light-blue:#3A71A8;\n$red:#C03639;\n$pink: #E65D6E;\n$green: #30B08F;\n$tiffany: #4AB7BD;\n$yel"
  },
  {
    "path": "frontend/src/utils/auth.js",
    "chars": 271,
    "preview": "import Cookies from 'js-cookie'\n\nconst TokenKey = 'Admin-Token'\n\nexport function getToken() {\n  return Cookies.get(Token"
  },
  {
    "path": "frontend/src/utils/clipboard.js",
    "chars": 633,
    "preview": "import Vue from 'vue'\nimport Clipboard from 'clipboard'\n\nfunction clipboardSuccess() {\n  Vue.prototype.$message({\n    me"
  },
  {
    "path": "frontend/src/utils/get-page-title.js",
    "chars": 234,
    "preview": "import defaultSettings from '@/settings'\n\nconst title = defaultSettings.title || 'Vue Element Admin'\n\nexport default fun"
  },
  {
    "path": "frontend/src/utils/i18n.js",
    "chars": 338,
    "preview": "// translate router.meta.title, be used in breadcrumb sidebar tagsview\nexport function generateTitle(title) {\n  const ha"
  },
  {
    "path": "frontend/src/utils/index.js",
    "chars": 8930,
    "preview": "/**\n * Created by PanJiaChen on 16/11/18.\n */\n\n/**\n * Parse the time to string\n * @param {(Object|string|number)} time\n "
  },
  {
    "path": "frontend/src/utils/open-window.js",
    "chars": 1173,
    "preview": "/**\n *Created by PanJiaChen on 16/11/29.\n * @param {Sting} url\n * @param {Sting} title\n * @param {Number} w\n * @param {N"
  },
  {
    "path": "frontend/src/utils/permission.js",
    "chars": 1276,
    "preview": "import store from '@/store'\n\n/**\n * @param {Array} value\n * @returns {Boolean}\n * @example see @/views/permission/direct"
  },
  {
    "path": "frontend/src/utils/request.js",
    "chars": 2320,
    "preview": "import axios from 'axios'\nimport { MessageBox, Message } from 'element-ui'\nimport store from '@/store'\nimport { getToken"
  },
  {
    "path": "frontend/src/utils/scroll-to.js",
    "chars": 1714,
    "preview": "Math.easeInOutQuad = function(t, b, c, d) {\n  t /= d / 2\n  if (t < 1) {\n    return c / 2 * t * t + b\n  }\n  t--\n  return "
  },
  {
    "path": "frontend/src/utils/validate.js",
    "chars": 1932,
    "preview": "/**\n * Created by PanJiaChen on 16/11/18.\n */\n\n/**\n * @param {string} path\n * @returns {Boolean}\n */\nexport function isE"
  },
  {
    "path": "frontend/src/vendor/Export2Excel.js",
    "chars": 5299,
    "preview": "/* eslint-disable */\nimport { saveAs } from 'file-saver'\nimport XLSX from 'xlsx'\n\nfunction generateArray(table) {\n  var "
  },
  {
    "path": "frontend/src/vendor/Export2Zip.js",
    "chars": 594,
    "preview": "/* eslint-disable */\nimport { saveAs } from 'file-saver'\nimport JSZip from 'jszip'\n\nexport function export_txt_to_zip(th"
  },
  {
    "path": "frontend/src/views/components-demo/clipboard.vue",
    "chars": 1411,
    "preview": "<template>\n  <div class=\"app-container\">\n    <el-tabs v-model=\"activeName\">\n      <el-tab-pane label=\"use clipboard  dir"
  },
  {
    "path": "frontend/src/views/components-demo/dnd-list.vue",
    "chars": 1234,
    "preview": "<template>\n  <div class=\"components-container\">\n    <aside>\n      drag-list base on\n      <a\n        href=\"https://githu"
  },
  {
    "path": "frontend/src/views/components-demo/drag-kanban.vue",
    "chars": 1401,
    "preview": "<template>\n  <div class=\"components-container board\">\n    <Kanban :key=\"1\" :list=\"list1\" :group=\"group\" class=\"kanban to"
  }
]

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

About this extraction

This page contains the full source code of the itimor/one-workflow GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 242 files (993.9 KB), approximately 282.2k tokens, and a symbol index with 409 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!