├── .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 ├── .env.development ├── .env.production ├── .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 │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── api │ │ ├── all.js │ │ ├── auths.js │ │ └── common.js │ ├── assets │ │ ├── 401_images │ │ │ └── 401.gif │ │ ├── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ ├── custom-theme │ │ │ ├── fonts │ │ │ │ ├── element-icons.ttf │ │ │ │ └── element-icons.woff │ │ │ └── index.css │ │ └── panel-bg.png │ ├── 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 │ │ ├── svg │ │ │ ├── 404.svg │ │ │ ├── all_ticket.svg │ │ │ ├── audit.svg │ │ │ ├── bug.svg │ │ │ ├── component.svg │ │ │ ├── dashboard.svg │ │ │ ├── exit-fullscreen.svg │ │ │ ├── eye-open.svg │ │ │ ├── eye.svg │ │ │ ├── fullscreen.svg │ │ │ ├── group.svg │ │ │ ├── icon.svg │ │ │ ├── international.svg │ │ │ ├── language.svg │ │ │ ├── list.svg │ │ │ ├── mail.svg │ │ │ ├── menu.svg │ │ │ ├── message.svg │ │ │ ├── money.svg │ │ │ ├── my_ticket.svg │ │ │ ├── new_ticket.svg │ │ │ ├── notice.svg │ │ │ ├── password.svg │ │ │ ├── rocket.svg │ │ │ ├── role.svg │ │ │ ├── search.svg │ │ │ ├── shopping.svg │ │ │ ├── sys.svg │ │ │ ├── telegram.svg │ │ │ ├── ticket.svg │ │ │ ├── todo_ticket.svg │ │ │ ├── tool.svg │ │ │ ├── user.svg │ │ │ ├── wfset.svg │ │ │ ├── wftype.svg │ │ │ └── workflow.svg │ │ └── 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 ├── gifs ├── all.png ├── edit.png ├── leave.png ├── new.png ├── role.png └── role_edit.png └── oms_nginx.conf /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/ 3 | .DS_Store 4 | .python-version 5 | upload/ 6 | *.log 7 | node_modules/ 8 | package-lock.json 9 | .vscode/ 10 | venv/ 11 | *.db 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django + vue 工作流管理系统 2 | 包含 `用户`、`角色`、`菜单`、`权限` 管理, 这是基础的工作流系统,初始化会生成请假工作流, 也可以自行配置其他工作流比如,发布工单等。 3 | 4 | [comment]: <> (- 后端model参考: [loonflow](https://github.com/blackholll/loonflow), 非常不错的一个项目) 5 | [comment]: <> (- 前端设计参考: [花裤衩 vue-element-admin](https://github.com/PanJiaChen/vue-element-admin), 大神作品没得说) 6 | ## 开发环境 7 | ### 后端 8 | 安装依赖 9 | ```bash 10 | cd backend 11 | pip install -r dev_requirements.txt 12 | ``` 13 | 14 | 初始化系统 15 | - 生成管理员账号 `admin 123456` 16 | ```bash 17 | python manage.py migrate 18 | python manage.py init_sys 19 | ``` 20 | 21 | 生成工作流 22 | - 用户 `ops`,`ops_tl`,`dev`,`dev_tl`,`hr`,`hr_tl` 23 | - 密码 `123456` 24 | 25 | ```bash 26 | python manage.py init_wf 27 | python manage.py init_ticket 28 | python manage.py init_leave 29 | ``` 30 | 31 | 运行 32 | ```bash 33 | python manage.py runserver 34 | ``` 35 | 36 | ### 前端 37 | ```bash 38 | cd frontend 39 | npm install 40 | npm run dev 41 | ``` 42 | 43 | ## 开始使用 44 | 使用 `admin` 登录 45 | ### 给所有角色分配工作流权限 46 | ![role](https://github.com/itimor/one-workflow/raw/master/gifs/role.png) 47 | 48 | ### 分配菜单 和 数据 权限 49 | ![role_edit](https://github.com/itimor/one-workflow/raw/master/gifs/role_edit.png) 50 | 51 | ### 配置假期工作流 52 | ![role](https://github.com/itimor/one-workflow/raw/master/gifs/leave.png) 53 | 54 | ### 新建工单 55 | ![role](https://github.com/itimor/one-workflow/raw/master/gifs/new.png) 56 | 57 | ### 编辑工单 58 | ![role](https://github.com/itimor/one-workflow/raw/master/gifs/edit.png) 59 | 60 | ### 所有工单 61 | ![role](https://github.com/itimor/one-workflow/raw/master/gifs/all.png) -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | -------------------------------------------------------------------------------- /backend/celery.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=start celery worker 3 | 4 | [Service] 5 | 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' 6 | #非正常dead,自动重启 7 | Restart=on-failure 8 | #3秒后启动 9 | RestartSec=3s 10 | KillSignal=SIGQUIT 11 | Type=simple 12 | NotifyAccess=all 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /backend/common/JSONRenderer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from rest_framework.renderers import JSONRenderer 5 | 6 | 7 | class CustomJSONRenderer(JSONRenderer): 8 | def render(self, data, accepted_media_type=None, renderer_context=None): 9 | response_data = {} 10 | object_list = 'results' 11 | try: 12 | meta_dict = getattr(renderer_context.get('view').get_serializer().Meta, 'meta_dict') 13 | except: 14 | meta_dict = dict() 15 | 16 | try: 17 | data.get('paginated_results') 18 | response_data['meta'] = data['meta'] 19 | response_data[object_list] = data['results'] 20 | except: 21 | response_data[object_list] = data 22 | response_data['meta'] = dict() 23 | response_data['meta'].update(meta_dict) 24 | 25 | response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context) 26 | return response 27 | -------------------------------------------------------------------------------- /backend/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/common/__init__.py -------------------------------------------------------------------------------- /backend/common/dispath.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import six 5 | from rest_framework.serializers import Serializer 6 | from rest_framework.response import Response 7 | 8 | 9 | class JsonResponse(Response): 10 | """ 11 | An HttpResponse that allows its data to be rendered into 12 | arbitrary media types. 13 | """ 14 | 15 | def __init__(self, data=None, code=None, desc=None, 16 | status=None, 17 | template_name=None, headers=None, 18 | exception=False, content_type=None): 19 | """ 20 | Alters the init arguments slightly. 21 | For example, drop 'template_name', and instead use 'data'. 22 | Setting 'renderer' and 'media_type' will typically be deferred, 23 | For example being set automatically by the `APIView`. 24 | """ 25 | super().__init__(None, status=status) 26 | 27 | if isinstance(data, Serializer): 28 | msg = ( 29 | 'You passed a Serializer instance as data, but ' 30 | 'probably meant to pass serialized `.data` or ' 31 | '`.error`. representation.' 32 | ) 33 | raise AssertionError(msg) 34 | 35 | self.data = data 36 | self.template_name = template_name 37 | self.exception = exception 38 | self.content_type = content_type 39 | 40 | if headers: 41 | for name, value in six.iteritems(headers): 42 | self[name] = value 43 | -------------------------------------------------------------------------------- /backend/common/django.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from django.utils.deprecation import MiddlewareMixin 5 | 6 | class DisableCSRF(MiddlewareMixin): 7 | def process_request(self, request): 8 | setattr(request, '_dont_enforce_csrf_checks', True) 9 | -------------------------------------------------------------------------------- /backend/common/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from rest_framework.views import exception_handler 5 | from common import status 6 | 7 | 8 | def JSONExceptionHandler(exc, context): 9 | response = exception_handler(exc, context) 10 | 11 | if response is not None: 12 | resp = { 13 | 'code': status.HTTP_400_BAD_REQUEST, 14 | 'result': dict(response.data) 15 | } 16 | response.data = resp 17 | 18 | return response 19 | 20 | 21 | class ExceptionX_Result: 22 | exceptionType = None 23 | exceptionTitle = None 24 | exceptionDetail = None 25 | 26 | 27 | class ExceptionX: 28 | 29 | @staticmethod 30 | def ToString(e): 31 | result = ExceptionX_Result 32 | tempStr = str(type(e)) 33 | tempStrArray = tempStr.split("'") 34 | result.exceptionTitle = tempStrArray[1] 35 | result.exceptionType = tempStrArray[0][1:] 36 | result.exceptionDetail = str(e) 37 | 38 | if result.exceptionDetail[0] == "<": 39 | if result.exceptionDetail[result.exceptionDetail.__len__() - 1] == ">": 40 | result.exceptionDetail = result.exceptionDetail[1:result.exceptionDetail.__len__() - 1] 41 | return result 42 | 43 | @staticmethod 44 | def PasreRaise(e): 45 | result = ExceptionX_Result 46 | tempStr = str(type(e)) 47 | tempStrArray = tempStr.split("'") 48 | result.exceptionTitle = tempStrArray[1] 49 | result.exceptionType = tempStrArray[0][1:] 50 | result.exceptionDetail = str(e) 51 | 52 | if result.exceptionDetail[0] == "<": 53 | if result.exceptionDetail[result.exceptionDetail.__len__() - 1] == ">": 54 | result.exceptionDetail = result.exceptionDetail[1:result.exceptionDetail.__len__() - 1] 55 | return result.exceptionDetail 56 | -------------------------------------------------------------------------------- /backend/common/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from django.db import models 5 | 6 | 7 | class BaseModel(models.Model): 8 | create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') 9 | update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') 10 | memo = models.TextField(blank=True, verbose_name='备注') 11 | 12 | class Meta: 13 | ordering = ['-create_time'] 14 | abstract = True 15 | 16 | -------------------------------------------------------------------------------- /backend/common/pagination.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from collections import OrderedDict 5 | 6 | from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination 7 | 8 | from common import status 9 | from common.dispath import JsonResponse 10 | 11 | 12 | def _positive_int(integer_string, strict=False, cutoff=None): 13 | """ 14 | 分页大小为零不分页 15 | """ 16 | ret = int(integer_string) 17 | if ret < 0: 18 | raise ValueError() 19 | if (ret == 0) and strict: 20 | return None 21 | if cutoff: 22 | return min(ret, cutoff) 23 | return ret 24 | 25 | 26 | class StandardResultsSetPagination(PageNumberPagination): 27 | """ 28 | 配置分页规则 29 | """ 30 | page_size = 20 31 | page_size_query_param = 'limit' 32 | page_query_param = 'page' 33 | max_page_size = 1000 34 | 35 | def get_paginated_response(self, data): 36 | return JsonResponse(OrderedDict([ 37 | ('count', self.page.paginator.count), 38 | ('next', self.get_next_link()), 39 | ('previous', self.get_previous_link()), 40 | ('results', data) 41 | ], code=status.HTTP_200_OK)) 42 | 43 | def get_page_size(self, request): 44 | if self.page_size_query_param: 45 | try: 46 | return _positive_int( 47 | request.query_params[self.page_size_query_param], 48 | strict=True, 49 | cutoff=self.max_page_size 50 | ) 51 | except (KeyError, ValueError): 52 | return None 53 | return self.page_size 54 | 55 | 56 | class CustomLimitOffsetPagination(LimitOffsetPagination): 57 | def get_offset(self, request): 58 | try: 59 | return (int(request.query_params['offset']) - 1) * int(request.query_params['limit']) 60 | except (KeyError, ValueError): 61 | return 1 62 | -------------------------------------------------------------------------------- /backend/common/status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from __future__ import unicode_literals 5 | 6 | 7 | def is_informational(code): 8 | return 100 <= code <= 199 9 | 10 | 11 | def is_success(code): 12 | return 200 <= code <= 299 13 | 14 | 15 | def is_redirect(code): 16 | return 300 <= code <= 399 17 | 18 | 19 | def is_client_error(code): 20 | return 400 <= code <= 499 21 | 22 | 23 | def is_server_error(code): 24 | return 500 <= code <= 599 25 | 26 | 27 | HTTP_100_CONTINUE = 10000 28 | HTTP_101_SWITCHING_PROTOCOLS = 10100 29 | HTTP_200_OK = 20000 30 | HTTP_201_CREATED = 20100 31 | HTTP_202_ACCEPTED = 20200 32 | HTTP_203_NON_AUTHORITATIVE_INFORMATION = 20300 33 | HTTP_204_NO_CONTENT = 20400 34 | HTTP_205_RESET_CONTENT = 20500 35 | HTTP_206_PARTIAL_CONTENT = 20600 36 | HTTP_207_MULTI_STATUS = 20700 37 | HTTP_300_MULTIPLE_CHOICES = 30000 38 | HTTP_301_MOVED_PERMANENTLY = 30100 39 | HTTP_302_FOUND = 30200 40 | HTTP_303_SEE_OTHER = 30300 41 | HTTP_304_NOT_MODIFIED = 30400 42 | HTTP_305_USE_PROXY = 30500 43 | HTTP_306_RESERVED = 30600 44 | HTTP_307_TEMPORARY_REDIRECT = 30700 45 | HTTP_400_BAD_REQUEST = 40000 46 | HTTP_401_UNAUTHORIZED = 40100 47 | HTTP_402_PAYMENT_REQUIRED = 40200 48 | HTTP_403_FORBIDDEN = 40300 49 | HTTP_404_NOT_FOUND = 40400 50 | HTTP_405_METHOD_NOT_ALLOWED = 40500 51 | HTTP_406_NOT_ACCEPTABLE = 40600 52 | HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 40700 53 | HTTP_408_REQUEST_TIMEOUT = 40800 54 | HTTP_409_CONFLICT = 40900 55 | HTTP_410_GONE = 41000 56 | HTTP_411_LENGTH_REQUIRED = 41100 57 | HTTP_412_PRECONDITION_FAILED = 41200 58 | HTTP_413_REQUEST_ENTITY_TOO_LARGE = 41300 59 | HTTP_414_REQUEST_URI_TOO_LONG = 41400 60 | HTTP_415_UNSUPPORTED_MEDIA_TYPE = 41500 61 | HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 41600 62 | HTTP_417_EXPECTATION_FAILED = 41700 63 | HTTP_422_UNPROCESSABLE_ENTITY = 42200 64 | HTTP_423_LOCKED = 42300 65 | HTTP_424_FAILED_DEPENDENCY = 42400 66 | HTTP_428_PRECONDITION_REQUIRED = 42800 67 | HTTP_429_TOO_MANY_REQUESTS = 42900 68 | HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 43100 69 | HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 45100 70 | HTTP_500_INTERNAL_SERVER_ERROR = 50000 71 | HTTP_501_NOT_IMPLEMENTED = 50100 72 | HTTP_502_BAD_GATEWAY = 50200 73 | HTTP_503_SERVICE_UNAVAILABLE = 50300 74 | HTTP_504_GATEWAY_TIMEOUT = 50400 75 | HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 50500 76 | HTTP_507_INSUFFICIENT_STORAGE = 50700 77 | HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 51100 78 | -------------------------------------------------------------------------------- /backend/core/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | # This will make sure the app is always imported when 3 | # Django starts so that shared_task will use this app. 4 | # import pymysql 5 | # 6 | # pymysql.install_as_MySQLdb() 7 | -------------------------------------------------------------------------------- /backend/core/celery.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from __future__ import absolute_import, unicode_literals 5 | import os 6 | from celery import Celery 7 | # set the default Django settings module for the 'celery' program. 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 9 | celery_app = Celery('core', result_backend='django-db') 10 | # Using a string here means the worker doesn't have to serialize 11 | # the configuration object to child processes. 12 | # - namespace='CELERY' means all celery-related configuration keys 13 | # should have a `CELERY_` prefix. 14 | celery_app.config_from_object('django.conf:settings', namespace='CELERY') 15 | # Load task modules from all registered Django app configs. 16 | celery_app.autodiscover_tasks() 17 | celery_app.loader.override_backends['django-db'] = 'django_celery_results.backends.database:DatabaseBackend' -------------------------------------------------------------------------------- /backend/core/settings/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import platform 5 | from .base import * 6 | 7 | os_type = platform.system() 8 | 9 | if os_type == 'Windows': 10 | print('进入 dev ') 11 | from .dev import * 12 | elif os_type == 'Linux': 13 | print('进入 prod ') 14 | from .prod import * 15 | else: 16 | print('进入 mac') 17 | from .mac import * 18 | -------------------------------------------------------------------------------- /backend/core/settings/dev.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import os 5 | 6 | APP_ENV = 'dev' 7 | 8 | # SECURITY WARNING: keep the secret key used in production secret! 9 | SECRET_KEY = '64318ob@vbou7h50)b0a_pfda4d$bw2nhl4h*m$qo0_e_fxw=658!z*x' 10 | 11 | # SECURITY WARNING: don't run with debug turned on in production! 12 | DEBUG = True 13 | 14 | ALLOWED_HOSTS = ['*'] 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | # sqlite 20 | DATABASES = { 21 | 'default': { 22 | 'ENGINE': 'django.db.backends.sqlite3', 23 | 'NAME': os.path.join(BASE_DIR, '../core.db'), 24 | } 25 | } 26 | 27 | # mysql 28 | # DATABASES = { 29 | # 'default': { 30 | # 'ENGINE': 'django.db.backends.mysql', 31 | # 'NAME': 'one', 32 | # 'USER': 'root', 33 | # 'PASSWORD': 'momo520', 34 | # 'HOST': '1.1.1.11', 35 | # 'OPTIONS': { 36 | # "init_command": "SET foreign_key_checks=0;", 37 | # } 38 | # } 39 | # } 40 | 41 | # 加载 mysql 42 | # import pymysql 43 | # pymysql.install_as_MySQLdb() 44 | -------------------------------------------------------------------------------- /backend/core/settings/mac.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import os 5 | 6 | APP_ENV = 'dev' 7 | 8 | # SECURITY WARNING: keep the secret key used in production secret! 9 | SECRET_KEY = '64318ob@vbou7h50)b0a_pfda4d$bw2nhl4h*m$qo0_e_fxw=658!z*x' 10 | 11 | # SECURITY WARNING: don't run with debug turned on in production! 12 | DEBUG = True 13 | 14 | ALLOWED_HOSTS = ['*'] 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | # sqlite 20 | DATABASES = { 21 | 'default': { 22 | 'ENGINE': 'django.db.backends.sqlite3', 23 | 'NAME': os.path.join(BASE_DIR, '../core.db'), 24 | } 25 | } 26 | 27 | # mysql 28 | # DATABASES = { 29 | # 'default': { 30 | # 'ENGINE': 'django.db.backends.mysql', 31 | # 'NAME': 'one', 32 | # 'USER': 'root', 33 | # 'PASSWORD': 'momo520', 34 | # 'HOST': '1.1.1.11', 35 | # 'OPTIONS': { 36 | # "init_command": "SET foreign_key_checks=0;", 37 | # } 38 | # } 39 | # } 40 | 41 | # 加载 mysql 42 | # import pymysql 43 | # pymysql.install_as_MySQLdb() 44 | -------------------------------------------------------------------------------- /backend/core/settings/prod.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import os 5 | 6 | APP_ENV = 'prod' 7 | 8 | # SECURITY WARNING: keep the secret key used in production secret! 9 | SECRET_KEY = '64318ob@vbou7h50)b0a_pfda4d$bw2nhl4h*m$qo0_e_fxw=658!z*x' 10 | 11 | # SECURITY WARNING: don't run with debug turned on in production! 12 | DEBUG = False 13 | 14 | ALLOWED_HOSTS = ['*'] 15 | 16 | # mysql 17 | DATABASES = { 18 | 'default': { 19 | 'ENGINE': 'django.db.backends.mysql', 20 | 'NAME': 'one', 21 | 'USER': 'root', 22 | 'PASSWORD': 'TY%pwd123', 23 | 'HOST': 'localhost', 24 | 'OPTIONS': { 25 | "init_command": "SET foreign_key_checks=0;", 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/core/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from django.conf.urls import url, include 5 | from django.conf.urls.static import static 6 | from django.views.generic.base import TemplateView 7 | from core import settings 8 | 9 | urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \ 10 | [ 11 | # 工具管理 12 | url(r'api/tool/', include(('tools.urls', 'tools'), namespace="tools")), 13 | # 系统管理 14 | url(r'api/sys/', include(('systems.urls', 'systems'), namespace="systems")), 15 | # 工作流管理 16 | url(r'api/workflow/', include(('workflows.urls', 'workflows'), namespace="workflows")), 17 | # 工单管理 18 | url(r'api/ticket/', include(('tickets.urls', 'tickets'), namespace="tickets")), 19 | # 通知管理 20 | url(r'api/notice/', include(('notices.urls', 'notices'), namespace="notices")), 21 | ] 22 | 23 | if settings.APP_ENV == 'prod': 24 | from rest_framework.documentation import include_docs_urls 25 | 26 | urlpatterns += [ 27 | # api文档 28 | url(r'^docs/', include_docs_urls(title='X Document')), 29 | # 静态模板 30 | url(r'', TemplateView.as_view(template_name="index.html")), 31 | ] 32 | else: 33 | from django.contrib import admin 34 | 35 | urlpatterns += [ 36 | # 管理后台 37 | url(r'^admin/', admin.site.urls), 38 | ] 39 | -------------------------------------------------------------------------------- /backend/core/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for core project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/dev_requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.2.3 2 | certifi==2019.11.28 3 | chardet==3.0.4 4 | coreapi==2.3.3 5 | coreschema==0.0.4 6 | Django==3.0.3 7 | django-cors-headers==3.2.1 8 | django-filter==2.2.0 9 | django-rest-auth==0.9.5 10 | djangorestframework==3.11.0 11 | djangorestframework-jwt==1.11.0 12 | idna==2.8 13 | IPy==1.0 14 | itypes==1.1.0 15 | Jinja2==2.10.3 16 | MarkupSafe==1.1.1 17 | PyMySQL==0.9.3 18 | PyJWT==1.7.1 19 | pytz==2019.3 20 | requests==2.22.0 21 | six==1.14.0 22 | sqlparse==0.3.0 23 | uritemplate==3.0.1 24 | urllib3==1.25.8 25 | -------------------------------------------------------------------------------- /backend/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apps=(systems tools notices workflows tickets) 4 | rm -rf core.db 5 | for app in ${apps[@]};do 6 | rm -rf $app/migrations 7 | done 8 | 9 | for app in ${apps[@]};do 10 | echo $app 11 | python manage.py makemigrations $app 12 | done 13 | 14 | python manage.py migrate 15 | python manage.py init_sys 16 | python manage.py init_wf 17 | python manage.py init_ticket 18 | python manage.py init_leave 19 | python manage.py runserver -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import os 5 | import sys 6 | 7 | if __name__ == '__main__': 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 9 | 10 | # 排错 “The maximum column size is 767 bytes” 11 | from django.db.backends.mysql.schema import DatabaseSchemaEditor 12 | DatabaseSchemaEditor.sql_create_table += " ROW_FORMAT=DYNAMIC" 13 | 14 | try: 15 | from django.core.management import execute_from_command_line 16 | except ImportError as exc: 17 | raise ImportError( 18 | "Couldn't import Django. Are you sure it's installed and " 19 | "available on your PYTHONPATH environment variable? Did you " 20 | "forget to activate a virtual environment?" 21 | ) from exc 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /backend/notices/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/notices/__init__.py -------------------------------------------------------------------------------- /backend/notices/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/notices/management/__init__.py -------------------------------------------------------------------------------- /backend/notices/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/notices/management/commands/__init__.py -------------------------------------------------------------------------------- /backend/notices/management/commands/init_notice.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from systems.models import * 6 | from systems.menus import init_menu 7 | 8 | 9 | class Command(BaseCommand): 10 | help = '初始化工作流' 11 | 12 | def handle(self, *args, **options): 13 | topmenu = Menu.objects.get(name='top', code='top') 14 | self.stdout.write(self.style.SUCCESS('############ 初始化通知菜单 ###########')) 15 | noticemenu = Menu.objects.create(name='通知管理', code='notice', curl='/notice', icon='notice', sequence=5, type=1, 16 | parent_id=topmenu.id) 17 | menumodel = Menu.objects.create(name='mail通知', code='mail', curl='/mail', icon='mail', sequence=10, type=2, 18 | parent_id=noticemenu.id) 19 | init_menu(menumodel) 20 | menumodel = Menu.objects.create(name='telegram通知', code='telegram', curl='/telegram', icon='telegram', 21 | sequence=20, type=2, parent_id=noticemenu.id) 22 | init_menu(menumodel) 23 | self.stdout.write(self.style.SUCCESS('初始化完成')) 24 | -------------------------------------------------------------------------------- /backend/notices/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2021-06-27 00:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='MailBot', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 19 | ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), 20 | ('memo', models.TextField(blank=True, verbose_name='备注')), 21 | ('type', models.CharField(choices=[('mail', 'mail'), ('telegram', 'telegram')], default=0, max_length=10, verbose_name='通知类型')), 22 | ('name', models.CharField(max_length=112, unique=True, verbose_name='名称')), 23 | ('host', models.CharField(max_length=112, verbose_name='主机')), 24 | ('user', models.CharField(max_length=112, verbose_name='账号')), 25 | ('password', models.CharField(max_length=112, verbose_name='密码')), 26 | ('to', models.CharField(max_length=112, verbose_name='接收者')), 27 | ], 28 | options={ 29 | 'verbose_name': '邮件机器人', 30 | 'verbose_name_plural': '邮件机器人', 31 | }, 32 | ), 33 | migrations.CreateModel( 34 | name='TelegramBot', 35 | fields=[ 36 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 37 | ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 38 | ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), 39 | ('memo', models.TextField(blank=True, verbose_name='备注')), 40 | ('type', models.CharField(choices=[('mail', 'mail'), ('telegram', 'telegram')], default=0, max_length=10, verbose_name='通知类型')), 41 | ('name', models.CharField(max_length=112, unique=True, verbose_name='名称')), 42 | ('uid', models.CharField(max_length=112, verbose_name='账号id')), 43 | ('token', models.CharField(max_length=112, verbose_name='token')), 44 | ('chat_id', models.CharField(max_length=112, verbose_name='chat_id')), 45 | ], 46 | options={ 47 | 'verbose_name': 'tg机器人', 48 | 'verbose_name_plural': 'tg机器人', 49 | }, 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /backend/notices/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/notices/migrations/__init__.py -------------------------------------------------------------------------------- /backend/notices/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from django.db import models 5 | from common.models import BaseModel 6 | 7 | notice_type = { 8 | 'mail': 'mail', 9 | 'telegram': 'telegram', 10 | } 11 | 12 | 13 | class MailBot(BaseModel): 14 | type = models.CharField(max_length=10, choices=tuple(notice_type.items()), default=0, verbose_name='通知类型') 15 | name = models.CharField(max_length=112, unique=True, verbose_name='名称') 16 | host = models.CharField(max_length=112, verbose_name='主机') 17 | user = models.CharField(max_length=112, verbose_name='账号') 18 | password = models.CharField(max_length=112, verbose_name='密码') 19 | to = models.CharField(max_length=112, verbose_name='接收者') 20 | 21 | def __str__(self): 22 | return self.name 23 | 24 | class Meta: 25 | verbose_name = "邮件机器人" 26 | verbose_name_plural = verbose_name 27 | 28 | 29 | class TelegramBot(BaseModel): 30 | type = models.CharField(max_length=10, choices=tuple(notice_type.items()), default=0, verbose_name='通知类型') 31 | name = models.CharField(max_length=112, unique=True, verbose_name='名称') 32 | uid = models.CharField(max_length=112, verbose_name='账号id') 33 | token = models.CharField(max_length=112, verbose_name='token') 34 | chat_id = models.CharField(max_length=112, verbose_name='chat_id') 35 | 36 | def __str__(self): 37 | return self.name 38 | 39 | class Meta: 40 | verbose_name = "tg机器人" 41 | verbose_name_plural = verbose_name 42 | -------------------------------------------------------------------------------- /backend/notices/serializers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from notices.models import * 5 | from rest_framework import serializers 6 | 7 | 8 | class MailBotSerializer(serializers.ModelSerializer): 9 | class Meta: 10 | model = MailBot 11 | fields = '__all__' 12 | 13 | 14 | class TelegramBotSerializer(serializers.ModelSerializer): 15 | class Meta: 16 | model = TelegramBot 17 | fields = '__all__' 18 | -------------------------------------------------------------------------------- /backend/notices/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | 5 | from django.conf.urls import url 6 | from rest_framework import routers 7 | from notices.views import NoticeViewSet, MailBotViewSet, TelegramBotViewSet 8 | 9 | router = routers.DefaultRouter() 10 | 11 | router.register(r'notice', NoticeViewSet) 12 | router.register(r'mail', MailBotViewSet) 13 | router.register(r'telegram', TelegramBotViewSet) 14 | 15 | urlpatterns = [ 16 | ] 17 | 18 | urlpatterns += router.urls 19 | -------------------------------------------------------------------------------- /backend/oms.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | project = core 3 | base = /data/app/one/backend 4 | 5 | chdir = %(base) 6 | module = %(project).wsgi:application 7 | 8 | master = true 9 | processes = 5 10 | enable-threads = true 11 | 12 | socket = %(base)/%(project).sock 13 | chmod-socket = 666 14 | vacuum = true 15 | logto = /data/logs/django/one.log 16 | -------------------------------------------------------------------------------- /backend/oms.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=uWSGI instance to serve one-oms 3 | 4 | [Service] 5 | Type=simple 6 | User=root 7 | Group=root 8 | WorkingDirectory=/data/app/one/backend 9 | ExecStart=/root/.pyenv/versions/boce/bin/uwsgi --ini oms.ini --touch-reload=/etc/nginx/uwsgi_params 10 | Restart=on-failure 11 | KillSignal=SIGQUIT 12 | Type=notify 13 | NotifyAccess=all 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.2.3 2 | certifi==2019.11.28 3 | chardet==3.0.4 4 | coreapi==2.3.3 5 | coreschema==0.0.4 6 | Django==3.0.3 7 | django-cors-headers==3.2.1 8 | django-filter==2.2.0 9 | django-rest-auth==0.9.5 10 | djangorestframework==3.11.0 11 | djangorestframework-jwt==1.11.0 12 | idna==2.8 13 | IPy==1.0 14 | itypes==1.1.0 15 | Jinja2==2.10.3 16 | MarkupSafe==1.1.1 17 | python-telegram-bot==12.6.1 18 | mysqlclient==1.4.6 19 | PyJWT==1.7.1 20 | pytz==2019.3 21 | requests==2.22.0 22 | six==1.14.0 23 | sqlparse==0.3.0 24 | uritemplate==3.0.1 25 | urllib3==1.25.8 26 | -------------------------------------------------------------------------------- /backend/systems/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | -------------------------------------------------------------------------------- /backend/systems/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from systems.models import * 3 | 4 | admin.site.register(Menu) 5 | admin.site.register(Role) 6 | admin.site.register(Group) 7 | admin.site.register(User) 8 | -------------------------------------------------------------------------------- /backend/systems/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/systems/management/__init__.py -------------------------------------------------------------------------------- /backend/systems/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/systems/management/commands/__init__.py -------------------------------------------------------------------------------- /backend/systems/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/systems/migrations/__init__.py -------------------------------------------------------------------------------- /backend/systems/permissions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | 5 | from rest_framework.permissions import BasePermission 6 | from systems.models import * 7 | from itertools import chain 8 | 9 | parse_method_action = { 10 | 'GET': 'view', 11 | 'POST': 'add', 12 | 'PUT': 'change', 13 | 'PATCH': 'change', 14 | 'DELETE': 'delete', 15 | } 16 | 17 | ignore_path = [ 18 | '/api/sys/auth/jwt-token-auth/', 19 | '/api/sys/auth/getuserinfo/', 20 | '/api/sys/auth/getmenubutons/', 21 | ] 22 | 23 | 24 | def check_permission(request, perm): 25 | user = User.objects.get(username=request.user) 26 | 27 | if user.is_admin: 28 | return True 29 | 30 | if request.path in ignore_path: 31 | return True 32 | 33 | user_roles = user.roles.all() 34 | group_roles = user.group.roles.all() 35 | all_roles = sorted(chain(user_roles, group_roles), key=lambda t: t.id, reverse=True) 36 | perms = Permission.objects.filter(role__in=all_roles) 37 | for i in perms: 38 | if i.codename == perm: 39 | return True 40 | 41 | 42 | class IsOwnerRoles(BasePermission): 43 | 44 | def has_permission(self, request, view): 45 | app = view.get_view_name().split() 46 | object = ''.join(app[:-1]).lower() 47 | perm = 'view_{}'.format(object) 48 | return check_permission(request, perm) 49 | 50 | def has_object_permission(self, request, view, obj): 51 | app_label = obj._meta.app_label 52 | model = obj._meta.object_name.lower() 53 | perm = '{}_{}'.format(parse_method_action[request.method], model) 54 | return check_permission(request, perm) 55 | -------------------------------------------------------------------------------- /backend/systems/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | 5 | from django.conf.urls import url, include 6 | from rest_framework import routers 7 | from rest_auth.views import PasswordChangeView 8 | from systems.views import UserViewSet, GroupViewSet, RoleViewSet, PermissionViewSet, MenuViewSet, AuthViewSet, \ 9 | ObtainJSONWebToken 10 | 11 | router = routers.DefaultRouter() 12 | 13 | router.register(r'user', UserViewSet) 14 | router.register(r'group', GroupViewSet) 15 | router.register(r'role', RoleViewSet) 16 | router.register(r'perm', PermissionViewSet) 17 | router.register(r'menu', MenuViewSet) 18 | router.register(r'auth', AuthViewSet) 19 | 20 | urlpatterns = [ 21 | url(r'^auth/changepwd/', PasswordChangeView.as_view(), name='changepwd'), 22 | # token认证 23 | url(r'^auth/jwt-token-auth/', ObtainJSONWebToken.as_view(), name='rest_framework_token'), 24 | url(r'^auth/api-token-auth/', include('rest_framework.urls', namespace='rest_framework')), 25 | ] 26 | 27 | urlpatterns += router.urls 28 | -------------------------------------------------------------------------------- /backend/tickets/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | -------------------------------------------------------------------------------- /backend/tickets/filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from tickets.models import * 5 | from django_filters import rest_framework as filters 6 | 7 | 8 | class TicketFilter(filters.FilterSet): 9 | class Meta: 10 | model = Ticket 11 | 12 | fields = { 13 | 'id': ['exact'], 14 | 'name': ['exact'], 15 | 'participant': ['exact'], 16 | 'create_user__username': ['exact'], 17 | "transition__attribute_type": ['exact', "lt"], 18 | } 19 | -------------------------------------------------------------------------------- /backend/tickets/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/tickets/management/__init__.py -------------------------------------------------------------------------------- /backend/tickets/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/tickets/management/commands/__init__.py -------------------------------------------------------------------------------- /backend/tickets/management/commands/init_ticket.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from systems.models import * 6 | from systems.menus import init_menu 7 | 8 | 9 | class Command(BaseCommand): 10 | help = '初始化工作流' 11 | 12 | def handle(self, *args, **options): 13 | topmenu = Menu.objects.get(name='top', code='top') 14 | self.stdout.write(self.style.SUCCESS('############ 初始化工单菜单 ###########')) 15 | ticketmenu = Menu.objects.create(name='工单系统', code='ticket', curl='/ticket', icon='ticket', sequence=4, type=1, 16 | parent_id=topmenu.id) 17 | menumodel = Menu.objects.create(name='新建工单', code='new_ticket', curl='/new_ticket', icon='new_ticket', sequence=10, type=2, 18 | parent_id=ticketmenu.id) 19 | init_menu(menumodel) 20 | menumodel = Menu.objects.create(name='编辑工单', code='u_ticket', curl='/u_ticket/:id', icon='u_ticket', sequence=10, type=2, 21 | hidden=True, active_menu='/new_ticket', parent_id=ticketmenu.id) 22 | init_menu(menumodel) 23 | menumodel = Menu.objects.create(name='审批工单', code='s_ticket', curl='/s_ticket/:id', icon='s_ticket', sequence=10, type=2, 24 | hidden=True, active_menu='/todo_ticket', parent_id=ticketmenu.id) 25 | init_menu(menumodel) 26 | menumodel = Menu.objects.create(name='我的工单', code='my_ticket', curl='/my_ticket', icon='my_ticket', sequence=30, type=2, 27 | no_cache=True, parent_id=ticketmenu.id) 28 | init_menu(menumodel) 29 | menumodel = Menu.objects.create(name='我的待办', code='todo_ticket', curl='/todo_ticket', icon='todo_ticket', sequence=40, type=2, 30 | no_cache=True, parent_id=ticketmenu.id) 31 | init_menu(menumodel) 32 | menumodel = Menu.objects.create(name='所有工单', code='all_ticket', curl='/all_ticket', icon='all_ticket', sequence=90, type=2, 33 | no_cache=True, parent_id=ticketmenu.id) 34 | init_menu(menumodel) 35 | self.stdout.write(self.style.SUCCESS('初始化完成')) 36 | -------------------------------------------------------------------------------- /backend/tickets/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/tickets/migrations/__init__.py -------------------------------------------------------------------------------- /backend/tickets/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | 5 | from django.conf.urls import url, include 6 | from rest_framework import routers 7 | from tickets.views import TicketViewSet, TicketFlowLogViewSet, TicketCustomFieldViewSet, TicketUserViewSet 8 | 9 | router = routers.DefaultRouter() 10 | 11 | router.register(r'ticket', TicketViewSet) 12 | router.register('ticketflowlog', TicketFlowLogViewSet) 13 | router.register(r'ticketcustomfield', TicketCustomFieldViewSet) 14 | router.register(r'ticketuser', TicketUserViewSet) 15 | 16 | urlpatterns = [ 17 | ] 18 | 19 | urlpatterns += router.urls 20 | -------------------------------------------------------------------------------- /backend/tickets/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | from tickets.serializers import * 5 | from tickets.filters import * 6 | from common.views import ModelViewSet, FKModelViewSet, JsonResponse, BulkModelMixin 7 | 8 | 9 | class TicketViewSet(BulkModelMixin): 10 | queryset = Ticket.objects.all() 11 | serializer_class = TicketSerializer 12 | filterset_class = TicketFilter 13 | search_fields = ['name'] 14 | ordering_fields = ['state'] 15 | 16 | def get_serializer_class(self): 17 | if self.action in ['list', 'retrieve'] or self.resultData: 18 | return TicketReadSerializer 19 | return TicketSerializer 20 | 21 | def get_queryset(self): 22 | try: 23 | user = User.objects.get(username=self.request.user) 24 | if user.is_admin: 25 | return Ticket.objects.all() 26 | else: 27 | return Ticket.objects.filter(relation__icontains=self.request.user).distinct() 28 | except Exception as e: 29 | print(e) 30 | return Ticket.objects.all() 31 | 32 | 33 | class TicketFlowLogViewSet(BulkModelMixin): 34 | queryset = TicketFlowLog.objects.all() 35 | serializer_class = TicketFlowLogSerializer 36 | search_fields = ['ticket'] 37 | filter_fields = ['ticket', 'state'] 38 | 39 | def get_serializer_class(self): 40 | if self.action in ['list', 'retrieve'] or self.resultData: 41 | return TicketFlowLogReadSerializer 42 | return TicketFlowLogSerializer 43 | 44 | 45 | class TicketCustomFieldViewSet(BulkModelMixin): 46 | queryset = TicketCustomField.objects.all() 47 | serializer_class = TicketCustomFieldSerializer 48 | filter_fields = ['ticket', 'customfield'] 49 | 50 | def get_serializer_class(self): 51 | if self.action in ['list', 'retrieve'] or self.resultData: 52 | return TicketCustomFieldReadSerializer 53 | return TicketCustomFieldSerializer 54 | 55 | 56 | class TicketUserViewSet(BulkModelMixin): 57 | queryset = TicketUser.objects.all() 58 | serializer_class = TicketUserSerializer 59 | search_fields = ['username'] 60 | filter_fields = ['username', 'in_process', 'worked'] 61 | -------------------------------------------------------------------------------- /backend/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | -------------------------------------------------------------------------------- /backend/tools/filesize.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | import math 5 | 6 | 7 | def convert_size(size_bytes): 8 | if size_bytes == 0: 9 | return "0B" 10 | size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") 11 | i = int(math.floor(math.log(size_bytes, 1024))) 12 | p = math.pow(1024, i) 13 | s = round(size_bytes / p, 2) 14 | return "%s %s" % (s, size_name[i]) 15 | -------------------------------------------------------------------------------- /backend/tools/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itimor/one-workflow/805035def06b88b8e3f616441a435742d3ba0bf2/backend/tools/migrations/__init__.py -------------------------------------------------------------------------------- /backend/tools/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from django.db import models 5 | from common.models import BaseModel 6 | from tools.filesize import convert_size 7 | from tools.storage import PathAndRename 8 | import os 9 | 10 | 11 | class Upload(BaseModel): 12 | username = models.CharField(max_length=20, verbose_name=u'上传用户') 13 | file = models.FileField(upload_to=PathAndRename("./"), blank=True, verbose_name=u'上传文件') 14 | archive = models.CharField(max_length=201, default=u'其他', null=True, blank=True, verbose_name=u'文件归档') 15 | filename = models.CharField(max_length=201, null=True, blank=True, verbose_name=u'文件名') 16 | filepath = models.CharField(max_length=201, null=True, blank=True, verbose_name=u'文件路径') 17 | type = models.CharField(max_length=100, null=True, blank=True, verbose_name=u'文件类型') 18 | size = models.CharField(max_length=20, null=True, blank=True, verbose_name=u'文件大小') 19 | 20 | def save(self, *args, **kwargs): 21 | from re import sub 22 | self.size = '{}'.format(convert_size(self.file.size)) 23 | filename = os.path.splitext(self.file.name) 24 | self.filename = '{}-{}{}'.format(sub('\W+', '', filename[0]), self.create_time, filename[1]).replace(' ', '_') 25 | self.filepath = '{}/{}'.format(self.archive, self.filename) 26 | super(Upload, self).save(*args, **kwargs) 27 | 28 | def __str__(self): 29 | return self.filepath 30 | 31 | class Meta: 32 | verbose_name = u'文件上传' 33 | verbose_name_plural = u'文件上传' 34 | 35 | 36 | class FileUpload(models.Model): 37 | file = models.FileField(upload_to=("./tmp"), blank=True, verbose_name=u'上传文件') 38 | 39 | class Meta: 40 | verbose_name = u'文件上传' 41 | verbose_name_plural = u'文件上传' 42 | 43 | 44 | class RequestEvent(BaseModel): 45 | url = models.CharField(max_length=255, null=False, db_index=True, verbose_name='请求URI') 46 | method = models.CharField(max_length=20, null=False, db_index=True, verbose_name='请求方法') 47 | query_string = models.TextField(verbose_name='请求内容') 48 | user = models.CharField(max_length=255, null=True, verbose_name='用户') 49 | remote_ip = models.CharField(max_length=50, null=True, db_index=True, verbose_name='请求IP') 50 | 51 | class Meta: 52 | ordering = ['-create_time'] 53 | verbose_name = '请求事件' 54 | verbose_name_plural = verbose_name 55 | 56 | 57 | class SimpleModel(models.Model): 58 | name = models.CharField(max_length=255, unique=True, verbose_name='名称') 59 | -------------------------------------------------------------------------------- /backend/tools/serializers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from rest_framework import serializers 5 | from tools.models import * 6 | 7 | 8 | class UploadSerializer(serializers.ModelSerializer): 9 | class Meta: 10 | model = Upload 11 | fields = '__all__' 12 | 13 | 14 | class FileUploadSerializer(serializers.ModelSerializer): 15 | class Meta: 16 | model = FileUpload 17 | fields = '__all__' 18 | 19 | 20 | class RequestEventSerializer(serializers.ModelSerializer): 21 | class Meta: 22 | model = RequestEvent 23 | fields = '__all__' 24 | 25 | class SimpleSerializer(serializers.ModelSerializer): 26 | class Meta(object): 27 | model = SimpleModel 28 | fields = '__all__' 29 | -------------------------------------------------------------------------------- /backend/tools/storage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | import os 5 | from django.utils.deconstruct import deconstructible 6 | from re import sub 7 | 8 | 9 | @deconstructible 10 | class PathAndRename(object): 11 | def __init__(self, sub_path): 12 | self.path = sub_path 13 | 14 | def __call__(self, instance, file): 15 | filename = os.path.splitext(file) 16 | last_filename = "%s-%s%s" % (sub('\W+', '', filename[0]), instance.create_time, filename[1]) 17 | return os.path.join(self.path, instance.archive, last_filename) 18 | -------------------------------------------------------------------------------- /backend/tools/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | 5 | from django.conf.urls import url 6 | from rest_framework import routers 7 | from tools.views import UploadViewSet, FileUploadViewSet, RequestEventViewSet, SimpleViewSet 8 | 9 | router = routers.DefaultRouter() 10 | 11 | router.register(r'upload', UploadViewSet) 12 | router.register(r'fileupload', FileUploadViewSet) 13 | router.register(r'audit', RequestEventViewSet) 14 | router.register('simple', SimpleViewSet) 15 | 16 | urlpatterns = [ 17 | ] 18 | 19 | urlpatterns += router.urls 20 | -------------------------------------------------------------------------------- /backend/tools/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from rest_framework import viewsets, permissions 5 | from tools.models import * 6 | from tools.serializers import * 7 | from common.views import ModelViewSet, FKModelViewSet, JsonResponse, BulkModelMixin 8 | 9 | 10 | class UploadViewSet(ModelViewSet): 11 | queryset = Upload.objects.all().order_by("-create_time") 12 | serializer_class = UploadSerializer 13 | filter_fields = ('username', 'type',) 14 | 15 | 16 | class FileUploadViewSet(ModelViewSet): 17 | permission_classes = [permissions.AllowAny] 18 | queryset = FileUpload.objects.all() 19 | serializer_class = FileUploadSerializer 20 | 21 | 22 | class RequestEventViewSet(ModelViewSet): 23 | queryset = RequestEvent.objects.all() 24 | serializer_class = RequestEventSerializer 25 | search_fields = ['url', 'query_string', 'user', 'remote_ip'] 26 | filter_fields = ['method'] 27 | 28 | 29 | class SimpleViewSet(BulkModelMixin): 30 | queryset = SimpleModel.objects.all() 31 | serializer_class = SimpleSerializer 32 | permission_classes = [permissions.AllowAny] 33 | filter_fields = ['id', 'name'] 34 | 35 | -------------------------------------------------------------------------------- /backend/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | -------------------------------------------------------------------------------- /backend/utils/get_realip.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | 5 | import requests 6 | import socket 7 | import json 8 | 9 | #获取外网ip信息 10 | output = requests.get('https://ifconfig.me/all.json').json() 11 | 12 | # 获取本机计算机ip 13 | def get_local_ip(): 14 | try: 15 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 16 | s.connect(('8.8.8.8', 80)) 17 | ip = s.getsockname()[0] 18 | finally: 19 | s.close() 20 | 21 | return ip 22 | 23 | output['local_ip'] = get_local_ip() 24 | 25 | ## output 26 | """ 27 | { 28 | "ip_addr": "203.177.78.226", 29 | "remote_host": "unavailable", 30 | "user_agent": "python-requests/2.22.0", 31 | "port": 38542, 32 | "method": "GET", 33 | "encoding": "gzip, deflate", 34 | "mime": "*/*", 35 | "via": "1.1 google", 36 | "forwarded": "203.177.78.226, 216.239.32.21" 37 | "local_ip": "172.16.51.115" 38 | }""" 39 | 40 | with open('d:/ooxx.log', 'a+') as fn: 41 | print(output) 42 | fn.write(json.dumps(output)) 43 | -------------------------------------------------------------------------------- /backend/utils/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | from datetime import datetime, timedelta 5 | import time 6 | 7 | 8 | def gen_time_pid(prefix): 9 | pid = '{}_{}'.format(prefix, datetime.now().strftime('%Y%m%d%H%M%S') + str(time.time()).replace('.', '')[-3:]) 10 | return pid 11 | 12 | 13 | def diff_times_in_seconds(t1, t2): 14 | h1, m1, s1 = t1.hour, t1.minute, t1.second 15 | h2, m2, s2 = t2.hour, t2.minute, t2.second 16 | t1_secs = s1 + 60 * (m1 + 60 * h1) 17 | t2_secs = s2 + 60 * (m2 + 60 * h2) 18 | tc = str(timedelta(seconds=(t2_secs - t1_secs))) 19 | return tc 20 | 21 | 22 | if __name__ == '__main__': 23 | prefix = 'xxoo' 24 | print(gen_time_pid(prefix)) 25 | -------------------------------------------------------------------------------- /backend/utils/mysql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import MySQLdb 5 | 6 | 7 | class MYSQL: 8 | def __init__(self, db, sql): 9 | self.sql = sql 10 | self.conn = MySQLdb.connect( 11 | host=db["host"], 12 | port=db["port"], 13 | user=db["user"], 14 | passwd=db["passwd"], 15 | db=db["db"], 16 | charset='utf8') 17 | self.cursor = self.conn.cursor() 18 | 19 | def insert(self): 20 | self.cursor.execute(self.sql) 21 | self.conn.commit() 22 | self.cursor.close() 23 | self.conn.close() 24 | return True 25 | 26 | def select(self): 27 | self.cursor.execute(self.sql) 28 | alldata = self.cursor.fetchall() 29 | self.cursor.close() 30 | self.conn.close() 31 | return alldata 32 | 33 | def update(self): 34 | self.cursor.execute(self.sql) 35 | self.conn.commit() 36 | self.cursor.close() 37 | self.conn.close() 38 | return True 39 | 40 | 41 | if __name__ == '__main__': 42 | xxljob_info = { 43 | "host": "localhost", 44 | "port": 3306, 45 | "user": "root", 46 | "passwd": "TY%pwd123", 47 | "db": "xxl_job", 48 | } 49 | jobapi = MYSQL(xxljob_info) 50 | sql = "select * from xxl_job_group" 51 | data = jobapi.insert(sql) 52 | rep_data = [] 53 | for item in data: 54 | json_data = { 55 | "id": item[0], 56 | "app_name": item[1], 57 | "title": item[2], 58 | "order": item[3], 59 | "address_type": item[4], 60 | "address_list": item[5], 61 | } 62 | rep_data.append(json_data) 63 | print(rep_data) 64 | -------------------------------------------------------------------------------- /backend/utils/sendmail.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | import sys 5 | import smtplib 6 | from email.mime.text import MIMEText 7 | from email.mime.multipart import MIMEMultipart 8 | from omsBackend.settings import MAIL_ACOUNT 9 | 10 | # 设置服务器名称、用户名、密码以及邮件后缀 11 | mail_host = MAIL_ACOUNT["mail_host"] 12 | mail_user = MAIL_ACOUNT["mail_user"] 13 | mail_pass = MAIL_ACOUNT["mail_pass"] 14 | mail_postfix = MAIL_ACOUNT["mail_postfix"] 15 | 16 | 17 | # 发送邮件函数 18 | def send_mail(to_list, cc_list, sub, content): 19 | me = mail_user + "<" + mail_user + "@" + mail_postfix + ">" 20 | # f = open(context) 21 | # msg = MIMEText(f.read(),_charset="utf-8") 22 | # f.close() 23 | # msg = MIMEText(context) 24 | msg = MIMEMultipart('alternative') 25 | msg['Subject'] = sub 26 | msg['From'] = me 27 | msg['To'] = to_list 28 | msg['Cc'] = cc_list 29 | list = msg['Cc'].split(',') 30 | list.append(msg['To']) 31 | context = MIMEText(content, _subtype='html', _charset='utf-8') # 解决乱码 32 | msg.attach(context) 33 | try: 34 | send_smtp = smtplib.SMTP() 35 | send_smtp.connect(mail_host, 587) 36 | send_smtp.starttls() 37 | send_smtp.login(mail_user, mail_pass) 38 | 39 | send_smtp.sendmail(me, list, msg.as_string()) 40 | send_smtp.close() 41 | return {"code": 'success', "msg": "通知邮件发送成功"} 42 | except Exception as e: 43 | print(e) 44 | return {"code": 'error', "msg": "通知邮件发送失败"} 45 | 46 | 47 | if __name__ == '__main__': 48 | to_list = sys.argv[1] # 收件人列表 '111@126.com' 49 | cc_list = sys.argv[2] # 抄送人列表 '111@126.com;222@126.com;' 50 | sub = sys.argv[3] 51 | context = sys.argv[4] 52 | if send_mail(to_list, cc_list, sub, context): 53 | print({"code": 'success', "msg": "通知邮件发送成功"}) 54 | else: 55 | print({"code": 'error', "msg": "通知邮件发送失败"}) -------------------------------------------------------------------------------- /backend/utils/sendskype.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | # 登录skype 5 | from skpy import Skype 6 | 7 | # skype账号 8 | SK_ACOUNT = { 9 | 'sk_user': 'itimor@126.com', 10 | 'sk_pass': 'xxx' 11 | } 12 | SK = Skype(SK_ACOUNT["sk_user"], SK_ACOUNT["sk_pass"]) 13 | 14 | 15 | def skype_bot(user, content): 16 | chat = SK.chats[user] 17 | chat.sendMsg(content) 18 | 19 | 20 | if __name__ == '__main__': 21 | skypeid = 'live:dafaricky123' 22 | user = '8:' + skypeid # skypeid 前面需要加 8 23 | skype_bot(user, "hello,gay,你个逗比,不加好友,发你妹的消息啊") 24 | -------------------------------------------------------------------------------- /backend/utils/test.py: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | id=$1 4 | echo $id 5 | cp -r site-10 site-$id 6 | cd site-$id 7 | sed -i "s/10/$id/" * -------------------------------------------------------------------------------- /backend/utils/time.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: timor 3 | 4 | import time 5 | import datetime 6 | 7 | 8 | def utc2local(utc_st): 9 | """ 10 | UTC时间转本地时间 +8:00 11 | """ 12 | now_stamp = time.time() 13 | local_time = datetime.datetime.fromtimestamp(now_stamp) 14 | utc_time = datetime.datetime.utcfromtimestamp(now_stamp) 15 | offset = local_time - utc_time 16 | local_st = utc_st + offset 17 | return local_st 18 | 19 | 20 | def local2utc(local_st): 21 | """ 22 | 本地时间转UTC时间 -8:00 23 | """ 24 | time_struct = time.mktime(local_st.timetuple()) 25 | utc_st = datetime.datetime.utcfromtimestamp(time_struct) 26 | return utc_st 27 | 28 | 29 | # '2015-08-28 16:43:37.283' --> 1440751417.283 30 | # 或者 '2015-08-28 16:43:37' --> 1440751417.0 31 | def string2timestamp(strValue): 32 | try: 33 | d = datetime.datetime.strptime(strValue, "%Y-%m-%d %H:%M:%S.%f") 34 | t = d.timetuple() 35 | timeStamp = int(time.mktime(t)) 36 | timeStamp = float(str(timeStamp) + str("%06d" % d.microsecond)) / 1000000 37 | return int(timeStamp) 38 | except ValueError as e: 39 | d = datetime.datetime.strptime(strValue, "%Y-%m-%d %H:%M:%S") 40 | t = d.timetuple() 41 | timeStamp = int(time.mktime(t)) 42 | timeStamp = float(str(timeStamp) + str("%06d" % d.microsecond)) / 1000000 43 | return int(timeStamp) 44 | 45 | 46 | # 1440751417.283 --> '2015-08-28 16:43:37.283' 47 | def timestamp2string(timeStamp): 48 | try: 49 | d = datetime.datetime.fromtimestamp(timeStamp) 50 | str1 = d.strftime("%Y-%m-%d %H:%M:%S.%f") 51 | # 2015-08-28 16:43:37.283000' 52 | return str1 53 | except Exception as e: 54 | pass 55 | -------------------------------------------------------------------------------- /backend/utils/verifys.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: itimor 3 | 4 | import re 5 | import IPy 6 | 7 | 8 | def is_valid_domain(value): 9 | domain_pattern = re.compile( 10 | r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|' 11 | r'([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|' 12 | r'([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.' 13 | r'([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$' 14 | ) 15 | return True if domain_pattern.match(value) else False 16 | 17 | 18 | def is_domain(domain): 19 | domain_regex = re.compile( 20 | r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(? Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } -------------------------------------------------------------------------------- /frontend/plop-templates/component/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if template}} 2 | 5 | {{/if}} 6 | 7 | {{#if script}} 8 | 20 | {{/if}} 21 | 22 | {{#if style}} 23 | 26 | {{/if}} 27 | -------------------------------------------------------------------------------- /frontend/plop-templates/component/prompt.js: -------------------------------------------------------------------------------- 1 | const { notEmpty } = require('../utils.js') 2 | 3 | module.exports = { 4 | description: 'generate vue component', 5 | prompts: [{ 6 | type: 'input', 7 | name: 'name', 8 | message: 'component name please', 9 | validate: notEmpty('name') 10 | }, 11 | { 12 | type: 'checkbox', 13 | name: 'blocks', 14 | message: 'Blocks:', 15 | choices: [{ 16 | name: '