From c5aa2a9bd68ffb90bd3c7bfbfc7b6ef6d67e7ef8 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 10 Aug 2021 09:59:04 +0800 Subject: [PATCH] first commit --- .gitattributes | 1 + .gitignore | 7 + LICENSE | 21 + README.md | 120 +++++ docker-compose.yml | 25 + hb_client/.editorconfig | 14 + hb_client/.env.development | 14 + hb_client/.env.production | 6 + hb_client/.env.staging | 8 + hb_client/.eslintignore | 4 + hb_client/.eslintrc.js | 198 +++++++ hb_client/.gitignore | 16 + hb_client/.travis.yml | 5 + hb_client/Dockerfile | 6 + hb_client/Dockerfile_dev | 7 + hb_client/LICENSE | 21 + hb_client/README-zh.md | 98 ++++ hb_client/README.md | 91 ++++ hb_client/babel.config.js | 5 + hb_client/build/index.js | 35 ++ hb_client/jest.config.js | 24 + hb_client/jsconfig.json | 9 + hb_client/mock/index.js | 67 +++ hb_client/mock/mock-server.js | 68 +++ hb_client/mock/table.js | 29 + hb_client/mock/user.js | 84 +++ hb_client/package.json | 68 +++ hb_client/postcss.config.js | 8 + hb_client/public/favicon.ico | Bin 0 -> 67646 bytes hb_client/public/index.html | 17 + hb_client/src/App.vue | 11 + hb_client/src/api/dict.js | 57 ++ hb_client/src/api/file.js | 18 + hb_client/src/api/org.js | 35 ++ hb_client/src/api/perm.js | 28 + hb_client/src/api/position.js | 31 ++ hb_client/src/api/role.js | 38 ++ hb_client/src/api/table.js | 9 + hb_client/src/api/task.js | 45 ++ hb_client/src/api/user.js | 70 +++ hb_client/src/assets/404_images/404.png | Bin 0 -> 98071 bytes hb_client/src/assets/404_images/404_cloud.png | Bin 0 -> 4766 bytes hb_client/src/components/Breadcrumb/index.vue | 78 +++ hb_client/src/components/Hamburger/index.vue | 44 ++ hb_client/src/components/Pagination/index.vue | 101 ++++ hb_client/src/components/SvgIcon/index.vue | 62 +++ hb_client/src/directive/el-table/adaptive.js | 42 ++ hb_client/src/directive/el-table/index.js | 13 + hb_client/src/icons/index.js | 9 + hb_client/src/icons/svg/404.svg | 1 + hb_client/src/icons/svg/bug.svg | 1 + hb_client/src/icons/svg/chart.svg | 1 + hb_client/src/icons/svg/clipboard.svg | 1 + hb_client/src/icons/svg/component.svg | 1 + hb_client/src/icons/svg/dashboard.svg | 1 + hb_client/src/icons/svg/documentation.svg | 1 + hb_client/src/icons/svg/drag.svg | 1 + hb_client/src/icons/svg/edit.svg | 1 + hb_client/src/icons/svg/education.svg | 1 + hb_client/src/icons/svg/email.svg | 1 + hb_client/src/icons/svg/example.svg | 1 + hb_client/src/icons/svg/excel.svg | 1 + hb_client/src/icons/svg/exit-fullscreen.svg | 1 + hb_client/src/icons/svg/eye-open.svg | 1 + hb_client/src/icons/svg/eye.svg | 1 + hb_client/src/icons/svg/form.svg | 1 + hb_client/src/icons/svg/fullscreen.svg | 1 + hb_client/src/icons/svg/guide.svg | 1 + hb_client/src/icons/svg/icon.svg | 1 + hb_client/src/icons/svg/international.svg | 1 + hb_client/src/icons/svg/language.svg | 1 + hb_client/src/icons/svg/link.svg | 1 + hb_client/src/icons/svg/list.svg | 1 + hb_client/src/icons/svg/lock.svg | 1 + hb_client/src/icons/svg/message.svg | 1 + hb_client/src/icons/svg/money.svg | 1 + hb_client/src/icons/svg/nested.svg | 1 + hb_client/src/icons/svg/password.svg | 1 + hb_client/src/icons/svg/pdf.svg | 1 + hb_client/src/icons/svg/people.svg | 1 + hb_client/src/icons/svg/peoples.svg | 1 + hb_client/src/icons/svg/position.svg | 1 + hb_client/src/icons/svg/qq.svg | 1 + hb_client/src/icons/svg/search.svg | 1 + hb_client/src/icons/svg/shopping.svg | 1 + hb_client/src/icons/svg/size.svg | 1 + hb_client/src/icons/svg/skill.svg | 1 + hb_client/src/icons/svg/star.svg | 1 + hb_client/src/icons/svg/tab.svg | 1 + hb_client/src/icons/svg/table.svg | 1 + hb_client/src/icons/svg/theme.svg | 1 + hb_client/src/icons/svg/tree-table.svg | 1 + hb_client/src/icons/svg/tree.svg | 1 + hb_client/src/icons/svg/user.svg | 1 + hb_client/src/icons/svg/wechat.svg | 1 + hb_client/src/icons/svg/zip.svg | 1 + hb_client/src/icons/svgo.yml | 22 + hb_client/src/layout/components/AppMain.vue | 40 ++ hb_client/src/layout/components/Navbar.vue | 147 ++++++ .../layout/components/Sidebar/FixiOSBug.js | 26 + .../src/layout/components/Sidebar/Item.vue | 29 + .../src/layout/components/Sidebar/Link.vue | 36 ++ .../src/layout/components/Sidebar/Logo.vue | 82 +++ .../layout/components/Sidebar/SidebarItem.vue | 95 ++++ .../src/layout/components/Sidebar/index.vue | 54 ++ hb_client/src/layout/components/index.js | 3 + hb_client/src/layout/index.vue | 93 ++++ hb_client/src/layout/mixin/ResizeHandler.js | 45 ++ hb_client/src/main.js | 44 ++ hb_client/src/permission.js | 73 +++ hb_client/src/router/index.js | 195 +++++++ hb_client/src/settings.js | 16 + hb_client/src/store/getters.js | 10 + hb_client/src/store/index.js | 21 + hb_client/src/store/modules/app.js | 48 ++ hb_client/src/store/modules/permission.js | 68 +++ hb_client/src/store/modules/settings.js | 31 ++ hb_client/src/store/modules/user.js | 108 ++++ hb_client/src/styles/element-ui.scss | 49 ++ hb_client/src/styles/index.scss | 124 +++++ hb_client/src/styles/mixin.scss | 28 + hb_client/src/styles/sidebar.scss | 209 ++++++++ hb_client/src/styles/transition.scss | 48 ++ hb_client/src/styles/variables.scss | 25 + hb_client/src/utils/auth.js | 25 + hb_client/src/utils/get-page-title.js | 10 + hb_client/src/utils/index.js | 384 ++++++++++++++ hb_client/src/utils/permission.js | 27 + hb_client/src/utils/request.js | 88 ++++ hb_client/src/utils/scroll-to.js | 58 ++ hb_client/src/utils/validate.js | 20 + hb_client/src/vendor/Export2Excel.js | 220 ++++++++ hb_client/src/vendor/Export2Zip.js | 24 + hb_client/src/views/404.vue | 228 ++++++++ hb_client/src/views/dashboard/index.vue | 32 ++ hb_client/src/views/form/index.vue | 85 +++ hb_client/src/views/login/index.vue | 237 +++++++++ hb_client/src/views/nested/menu1/index.vue | 7 + .../src/views/nested/menu1/menu1-1/index.vue | 7 + .../src/views/nested/menu1/menu1-2/index.vue | 7 + .../nested/menu1/menu1-2/menu1-2-1/index.vue | 5 + .../nested/menu1/menu1-2/menu1-2-2/index.vue | 5 + .../src/views/nested/menu1/menu1-3/index.vue | 5 + hb_client/src/views/nested/menu2/index.vue | 5 + hb_client/src/views/system/changepassword.vue | 78 +++ hb_client/src/views/system/dict.vue | 370 +++++++++++++ hb_client/src/views/system/file.vue | 134 +++++ hb_client/src/views/system/organization.vue | 225 ++++++++ hb_client/src/views/system/perm.vue | 239 +++++++++ hb_client/src/views/system/position.vue | 211 ++++++++ hb_client/src/views/system/role.vue | 301 +++++++++++ hb_client/src/views/system/task.vue | 410 ++++++++++++++ hb_client/src/views/system/user.vue | 378 +++++++++++++ hb_client/src/views/table/index.vue | 79 +++ hb_client/src/views/tree/index.vue | 78 +++ hb_client/tests/unit/.eslintrc.js | 5 + .../tests/unit/components/Breadcrumb.spec.js | 98 ++++ .../tests/unit/components/Hamburger.spec.js | 18 + .../tests/unit/components/SvgIcon.spec.js | 22 + hb_client/tests/unit/utils/formatTime.spec.js | 30 ++ hb_client/tests/unit/utils/parseTime.spec.js | 28 + hb_client/tests/unit/utils/validate.spec.js | 17 + hb_client/vue.config.js | 128 +++++ hb_server/.gitignore | 13 + hb_server/Dockerfile | 10 + hb_server/apps/crm/__init__.py | 0 hb_server/apps/crm/admin.py | 3 + hb_server/apps/crm/apps.py | 5 + hb_server/apps/crm/migrations/__init__.py | 0 hb_server/apps/crm/models.py | 3 + hb_server/apps/crm/tests.py | 3 + hb_server/apps/crm/views.py | 3 + hb_server/apps/monitor/__init__.py | 0 hb_server/apps/monitor/admin.py | 3 + hb_server/apps/monitor/apps.py | 6 + hb_server/apps/monitor/middleware.py | 1 + hb_server/apps/monitor/migrations/__init__.py | 0 hb_server/apps/monitor/models.py | 3 + hb_server/apps/monitor/tests.py | 3 + hb_server/apps/monitor/urls.py | 10 + hb_server/apps/monitor/views.py | 63 +++ hb_server/apps/system/__init__.py | 1 + hb_server/apps/system/admin.py | 11 + hb_server/apps/system/apps.py | 9 + hb_server/apps/system/authentication.py | 23 + hb_server/apps/system/filters.py | 11 + .../apps/system/migrations/0001_initial.py | 239 +++++++++ .../migrations/0002_auto_20210718_0918.py | 23 + hb_server/apps/system/migrations/__init__.py | 0 hb_server/apps/system/mixins.py | 48 ++ hb_server/apps/system/models.py | 218 ++++++++ hb_server/apps/system/permission.py | 97 ++++ hb_server/apps/system/permission_data.py | 98 ++++ hb_server/apps/system/serializers.py | 173 ++++++ hb_server/apps/system/signals.py | 12 + hb_server/apps/system/tasks.py | 9 + hb_server/apps/system/tests.py | 3 + hb_server/apps/system/urls.py | 19 + hb_server/apps/system/views.py | 351 ++++++++++++ hb_server/db.json | 498 ++++++++++++++++++ hb_server/log/.gitignore | 2 + hb_server/manage.py | 21 + hb_server/media/default/avatar.png | Bin 0 -> 22934 bytes hb_server/requirements.txt | 11 + hb_server/server/__init__.py | 5 + hb_server/server/asgi.py | 16 + hb_server/server/celery.py | 22 + hb_server/server/settings.py | 277 ++++++++++ hb_server/server/settings_dev.py | 19 + hb_server/server/settings_pro.py | 15 + hb_server/server/urls.py | 65 +++ hb_server/server/wsgi.py | 16 + hb_server/start.sh | 10 + hb_server/utils/__init__.py | 0 hb_server/utils/model.py | 87 +++ hb_server/utils/pagination.py | 16 + hb_server/utils/queryset.py | 60 +++ hb_server/utils/response.py | 61 +++ hb_server/utils/serializer.py | 39 ++ hb_server/utils/test.py | 4 + hb_server/utils/view.py | 51 ++ hb_server/utils/workflow.py | 36 ++ img/dict.png | Bin 0 -> 40884 bytes img/docs.png | Bin 0 -> 72543 bytes img/task.png | Bin 0 -> 64666 bytes img/user.png | Bin 0 -> 49852 bytes specification.md | 53 ++ 227 files changed, 10769 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 hb_client/.editorconfig create mode 100644 hb_client/.env.development create mode 100644 hb_client/.env.production create mode 100644 hb_client/.env.staging create mode 100644 hb_client/.eslintignore create mode 100644 hb_client/.eslintrc.js create mode 100644 hb_client/.gitignore create mode 100644 hb_client/.travis.yml create mode 100644 hb_client/Dockerfile create mode 100644 hb_client/Dockerfile_dev create mode 100644 hb_client/LICENSE create mode 100644 hb_client/README-zh.md create mode 100644 hb_client/README.md create mode 100644 hb_client/babel.config.js create mode 100644 hb_client/build/index.js create mode 100644 hb_client/jest.config.js create mode 100644 hb_client/jsconfig.json create mode 100644 hb_client/mock/index.js create mode 100644 hb_client/mock/mock-server.js create mode 100644 hb_client/mock/table.js create mode 100644 hb_client/mock/user.js create mode 100644 hb_client/package.json create mode 100644 hb_client/postcss.config.js create mode 100644 hb_client/public/favicon.ico create mode 100644 hb_client/public/index.html create mode 100644 hb_client/src/App.vue create mode 100644 hb_client/src/api/dict.js create mode 100644 hb_client/src/api/file.js create mode 100644 hb_client/src/api/org.js create mode 100644 hb_client/src/api/perm.js create mode 100644 hb_client/src/api/position.js create mode 100644 hb_client/src/api/role.js create mode 100644 hb_client/src/api/table.js create mode 100644 hb_client/src/api/task.js create mode 100644 hb_client/src/api/user.js create mode 100644 hb_client/src/assets/404_images/404.png create mode 100644 hb_client/src/assets/404_images/404_cloud.png create mode 100644 hb_client/src/components/Breadcrumb/index.vue create mode 100644 hb_client/src/components/Hamburger/index.vue create mode 100644 hb_client/src/components/Pagination/index.vue create mode 100644 hb_client/src/components/SvgIcon/index.vue create mode 100644 hb_client/src/directive/el-table/adaptive.js create mode 100644 hb_client/src/directive/el-table/index.js create mode 100644 hb_client/src/icons/index.js create mode 100644 hb_client/src/icons/svg/404.svg create mode 100644 hb_client/src/icons/svg/bug.svg create mode 100644 hb_client/src/icons/svg/chart.svg create mode 100644 hb_client/src/icons/svg/clipboard.svg create mode 100644 hb_client/src/icons/svg/component.svg create mode 100644 hb_client/src/icons/svg/dashboard.svg create mode 100644 hb_client/src/icons/svg/documentation.svg create mode 100644 hb_client/src/icons/svg/drag.svg create mode 100644 hb_client/src/icons/svg/edit.svg create mode 100644 hb_client/src/icons/svg/education.svg create mode 100644 hb_client/src/icons/svg/email.svg create mode 100644 hb_client/src/icons/svg/example.svg create mode 100644 hb_client/src/icons/svg/excel.svg create mode 100644 hb_client/src/icons/svg/exit-fullscreen.svg create mode 100644 hb_client/src/icons/svg/eye-open.svg create mode 100644 hb_client/src/icons/svg/eye.svg create mode 100644 hb_client/src/icons/svg/form.svg create mode 100644 hb_client/src/icons/svg/fullscreen.svg create mode 100644 hb_client/src/icons/svg/guide.svg create mode 100644 hb_client/src/icons/svg/icon.svg create mode 100644 hb_client/src/icons/svg/international.svg create mode 100644 hb_client/src/icons/svg/language.svg create mode 100644 hb_client/src/icons/svg/link.svg create mode 100644 hb_client/src/icons/svg/list.svg create mode 100644 hb_client/src/icons/svg/lock.svg create mode 100644 hb_client/src/icons/svg/message.svg create mode 100644 hb_client/src/icons/svg/money.svg create mode 100644 hb_client/src/icons/svg/nested.svg create mode 100644 hb_client/src/icons/svg/password.svg create mode 100644 hb_client/src/icons/svg/pdf.svg create mode 100644 hb_client/src/icons/svg/people.svg create mode 100644 hb_client/src/icons/svg/peoples.svg create mode 100644 hb_client/src/icons/svg/position.svg create mode 100644 hb_client/src/icons/svg/qq.svg create mode 100644 hb_client/src/icons/svg/search.svg create mode 100644 hb_client/src/icons/svg/shopping.svg create mode 100644 hb_client/src/icons/svg/size.svg create mode 100644 hb_client/src/icons/svg/skill.svg create mode 100644 hb_client/src/icons/svg/star.svg create mode 100644 hb_client/src/icons/svg/tab.svg create mode 100644 hb_client/src/icons/svg/table.svg create mode 100644 hb_client/src/icons/svg/theme.svg create mode 100644 hb_client/src/icons/svg/tree-table.svg create mode 100644 hb_client/src/icons/svg/tree.svg create mode 100644 hb_client/src/icons/svg/user.svg create mode 100644 hb_client/src/icons/svg/wechat.svg create mode 100644 hb_client/src/icons/svg/zip.svg create mode 100644 hb_client/src/icons/svgo.yml create mode 100644 hb_client/src/layout/components/AppMain.vue create mode 100644 hb_client/src/layout/components/Navbar.vue create mode 100644 hb_client/src/layout/components/Sidebar/FixiOSBug.js create mode 100644 hb_client/src/layout/components/Sidebar/Item.vue create mode 100644 hb_client/src/layout/components/Sidebar/Link.vue create mode 100644 hb_client/src/layout/components/Sidebar/Logo.vue create mode 100644 hb_client/src/layout/components/Sidebar/SidebarItem.vue create mode 100644 hb_client/src/layout/components/Sidebar/index.vue create mode 100644 hb_client/src/layout/components/index.js create mode 100644 hb_client/src/layout/index.vue create mode 100644 hb_client/src/layout/mixin/ResizeHandler.js create mode 100644 hb_client/src/main.js create mode 100644 hb_client/src/permission.js create mode 100644 hb_client/src/router/index.js create mode 100644 hb_client/src/settings.js create mode 100644 hb_client/src/store/getters.js create mode 100644 hb_client/src/store/index.js create mode 100644 hb_client/src/store/modules/app.js create mode 100644 hb_client/src/store/modules/permission.js create mode 100644 hb_client/src/store/modules/settings.js create mode 100644 hb_client/src/store/modules/user.js create mode 100644 hb_client/src/styles/element-ui.scss create mode 100644 hb_client/src/styles/index.scss create mode 100644 hb_client/src/styles/mixin.scss create mode 100644 hb_client/src/styles/sidebar.scss create mode 100644 hb_client/src/styles/transition.scss create mode 100644 hb_client/src/styles/variables.scss create mode 100644 hb_client/src/utils/auth.js create mode 100644 hb_client/src/utils/get-page-title.js create mode 100644 hb_client/src/utils/index.js create mode 100644 hb_client/src/utils/permission.js create mode 100644 hb_client/src/utils/request.js create mode 100644 hb_client/src/utils/scroll-to.js create mode 100644 hb_client/src/utils/validate.js create mode 100644 hb_client/src/vendor/Export2Excel.js create mode 100644 hb_client/src/vendor/Export2Zip.js create mode 100644 hb_client/src/views/404.vue create mode 100644 hb_client/src/views/dashboard/index.vue create mode 100644 hb_client/src/views/form/index.vue create mode 100644 hb_client/src/views/login/index.vue create mode 100644 hb_client/src/views/nested/menu1/index.vue create mode 100644 hb_client/src/views/nested/menu1/menu1-1/index.vue create mode 100644 hb_client/src/views/nested/menu1/menu1-2/index.vue create mode 100644 hb_client/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue create mode 100644 hb_client/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue create mode 100644 hb_client/src/views/nested/menu1/menu1-3/index.vue create mode 100644 hb_client/src/views/nested/menu2/index.vue create mode 100644 hb_client/src/views/system/changepassword.vue create mode 100644 hb_client/src/views/system/dict.vue create mode 100644 hb_client/src/views/system/file.vue create mode 100644 hb_client/src/views/system/organization.vue create mode 100644 hb_client/src/views/system/perm.vue create mode 100644 hb_client/src/views/system/position.vue create mode 100644 hb_client/src/views/system/role.vue create mode 100644 hb_client/src/views/system/task.vue create mode 100644 hb_client/src/views/system/user.vue create mode 100644 hb_client/src/views/table/index.vue create mode 100644 hb_client/src/views/tree/index.vue create mode 100644 hb_client/tests/unit/.eslintrc.js create mode 100644 hb_client/tests/unit/components/Breadcrumb.spec.js create mode 100644 hb_client/tests/unit/components/Hamburger.spec.js create mode 100644 hb_client/tests/unit/components/SvgIcon.spec.js create mode 100644 hb_client/tests/unit/utils/formatTime.spec.js create mode 100644 hb_client/tests/unit/utils/parseTime.spec.js create mode 100644 hb_client/tests/unit/utils/validate.spec.js create mode 100644 hb_client/vue.config.js create mode 100644 hb_server/.gitignore create mode 100644 hb_server/Dockerfile create mode 100644 hb_server/apps/crm/__init__.py create mode 100644 hb_server/apps/crm/admin.py create mode 100644 hb_server/apps/crm/apps.py create mode 100644 hb_server/apps/crm/migrations/__init__.py create mode 100644 hb_server/apps/crm/models.py create mode 100644 hb_server/apps/crm/tests.py create mode 100644 hb_server/apps/crm/views.py create mode 100644 hb_server/apps/monitor/__init__.py create mode 100644 hb_server/apps/monitor/admin.py create mode 100644 hb_server/apps/monitor/apps.py create mode 100644 hb_server/apps/monitor/middleware.py create mode 100644 hb_server/apps/monitor/migrations/__init__.py create mode 100644 hb_server/apps/monitor/models.py create mode 100644 hb_server/apps/monitor/tests.py create mode 100644 hb_server/apps/monitor/urls.py create mode 100644 hb_server/apps/monitor/views.py create mode 100644 hb_server/apps/system/__init__.py create mode 100644 hb_server/apps/system/admin.py create mode 100644 hb_server/apps/system/apps.py create mode 100644 hb_server/apps/system/authentication.py create mode 100644 hb_server/apps/system/filters.py create mode 100644 hb_server/apps/system/migrations/0001_initial.py create mode 100644 hb_server/apps/system/migrations/0002_auto_20210718_0918.py create mode 100644 hb_server/apps/system/migrations/__init__.py create mode 100644 hb_server/apps/system/mixins.py create mode 100644 hb_server/apps/system/models.py create mode 100644 hb_server/apps/system/permission.py create mode 100644 hb_server/apps/system/permission_data.py create mode 100644 hb_server/apps/system/serializers.py create mode 100644 hb_server/apps/system/signals.py create mode 100644 hb_server/apps/system/tasks.py create mode 100644 hb_server/apps/system/tests.py create mode 100644 hb_server/apps/system/urls.py create mode 100644 hb_server/apps/system/views.py create mode 100644 hb_server/db.json create mode 100644 hb_server/log/.gitignore create mode 100644 hb_server/manage.py create mode 100644 hb_server/media/default/avatar.png create mode 100644 hb_server/requirements.txt create mode 100644 hb_server/server/__init__.py create mode 100644 hb_server/server/asgi.py create mode 100644 hb_server/server/celery.py create mode 100644 hb_server/server/settings.py create mode 100644 hb_server/server/settings_dev.py create mode 100644 hb_server/server/settings_pro.py create mode 100644 hb_server/server/urls.py create mode 100644 hb_server/server/wsgi.py create mode 100644 hb_server/start.sh create mode 100644 hb_server/utils/__init__.py create mode 100644 hb_server/utils/model.py create mode 100644 hb_server/utils/pagination.py create mode 100644 hb_server/utils/queryset.py create mode 100644 hb_server/utils/response.py create mode 100644 hb_server/utils/serializer.py create mode 100644 hb_server/utils/test.py create mode 100644 hb_server/utils/view.py create mode 100644 hb_server/utils/workflow.py create mode 100644 img/dict.png create mode 100644 img/docs.png create mode 100644 img/task.png create mode 100644 img/user.png create mode 100644 specification.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6e51dee --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.vue linguist-language=python diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa116e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +unpackage/dist/* +node_modules/* +deploy.sh +package-lock.json +.idea/ +.vscode/ +server/static/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..09310e6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 blackholll + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4e677e --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# 简介 +基于RBAC模型权限控制的中小型应用的基础开发平台,前后端分离,后端采用django+django-rest-framework,前端采用vue+ElementUI,移动端采用uniapp+uView(可发布h5和小程序). + +JWT认证,可使用simple_history实现审计功能,支持swagger + +内置模块有组织机构\用户\角色\岗位\数据字典\文件库\定时任务 + +支持功能权限(控权到每个接口)和简单的数据权限(全部、本级及以下、同级及以下、本人等) + +## 部分截图 +![image](https://github.com/caoqianming/django-vue-admin/blob/master/img/user.png) +![image](https://github.com/caoqianming/django-vue-admin/blob/master/img/dict.png) +![image](https://github.com/caoqianming/django-vue-admin/blob/master/img/task.png) + +## 启动(以下是在windows下开发操作步骤) + + +### django后端 +定位到server文件夹 + +建立虚拟环境 `python -m venv venv` + +激活虚拟环境 `.\venv\scripts\activate` + +安装依赖包 `pip install -r requirements.txt` + +修改数据库连接 `server\settings_dev.py` + +同步数据库 `python manage.py migrate` + +可导入初始数据 `python manage.py loaddata db.json` 或直接使用sqlite数据库(超管账户密码均为admin) + +创建超级管理员 `python manage.py createsuperuser` + +运行服务 `python manage.py runserver 8000` + +### vue前端 +定位到client文件夹 + +安装node.js + +安装依赖包 `npm install --registry=https://registry.npm.taobao.org` + +运行服务 `npm run dev` + +### nginx +修改nginx.conf + +``` +listen 8012 +location /media { + proxy_pass http://localhost:8000; +} +location / { + proxy_pass http://localhost:9528; +} +``` + +运行nginx.exe + +### 运行 +打开localhost:8012即可访问 + +接口文档 localhost:8000/docs + +后台地址 localhost:8000/admin + +### docker-compose 方式运行 + +前端 `./client` 和后端 `./server` 目录下都有Dockerfile,如果需要单独构建镜像,可以自行构建。 + +这里主要说docker-compose启动这种方式。 + +按照注释修改docker-compose.yml文件。里面主要有两个服务,一个是`backend`后端,一个是`frontend`前端。 + +默认是用开发模式跑的后端和前端。如果需要单机部署,又想用docker-compose的话,改为生产模式性能会好些。 + + +启动 +``` +cd +docker-compose up -d +``` + +启动成功后,访问端口同前面的,接口8000端口,前端8012端口,如需改动,自己改docker-compose.yml + +如果要执行里面的命令 +docker-compose exec <服务名> <命令> + +举个栗子: + +如果我要执行后端生成数据变更命令。`python manage.py makemigrations` + +则用如下语句 + +``` +docker-compose exec backend python manage.py makemigrations +``` + +### 理念 +首先得会使用django-rest-framework, 理解vue-element-admin前端方案 + +本项目采用前端路由,后端根据用户角色读取用户权限代码返回给前端,由前端进行加载(核心代码是路由表中的perms属性以及checkpermission方法) + +后端功能权限的核心代码在server/apps/system/permission.py下重写了has_permission方法, 在APIView和ViewSet中定义perms权限代码 + +数据权限因为跟具体业务有关,简单定义了几个规则,重写了has_object_permission方法;根据需要使用即可 + +### 关于定时任务 +使用celery以及django_celery_beat包实现 + +需要安装redis并在默认端口启动, 并启动worker以及beat + +进入虚拟环境并启动worker: `celery -A server worker -l info -P eventlet`, linux系统不用加-P eventlet + +进入虚拟环境并启动beat: `celery -A server beat -l info` + +### 后续 +考虑增加一个简易的工作流模块 + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6fb9ee2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: "3" +services: + backend: + build: ./server + ports: + - "8000:80" + environment: + # 生产的话把DJANGO_ENV这个环境变量删了 执行docker-compose build backend 重新构建下镜像 + - DJANGO_ENV=dev + volumes: + - ./server:/code + links: + - redis + frontend: + build: + context: ./client + # 生产用这个 + # dockerfile: Dockerfile + # 开发的话用这个 + dockerfile: Dockerfile_dev + ports: + - "8012:80" + redis: + image: redis + command: redis-server --appendonly yes \ No newline at end of file diff --git a/hb_client/.editorconfig b/hb_client/.editorconfig new file mode 100644 index 0000000..ea6e20f --- /dev/null +++ b/hb_client/.editorconfig @@ -0,0 +1,14 @@ +# http://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 diff --git a/hb_client/.env.development b/hb_client/.env.development new file mode 100644 index 0000000..f0aff47 --- /dev/null +++ b/hb_client/.env.development @@ -0,0 +1,14 @@ +# just a flag +ENV = 'development' + +# base api +VUE_APP_BASE_API = 'http://localhost:8000/api' + +# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, +# to control whether the babel-plugin-dynamic-import-node plugin is enabled. +# It only does one thing by converting all import() to require(). +# This configuration can significantly increase the speed of hot updates, +# when you have a large number of pages. +# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js + +VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/hb_client/.env.production b/hb_client/.env.production new file mode 100644 index 0000000..8994f69 --- /dev/null +++ b/hb_client/.env.production @@ -0,0 +1,6 @@ +# just a flag +ENV = 'production' + +# base api +VUE_APP_BASE_API = '' + diff --git a/hb_client/.env.staging b/hb_client/.env.staging new file mode 100644 index 0000000..a8793a0 --- /dev/null +++ b/hb_client/.env.staging @@ -0,0 +1,8 @@ +NODE_ENV = production + +# just a flag +ENV = 'staging' + +# base api +VUE_APP_BASE_API = '/stage-api' + diff --git a/hb_client/.eslintignore b/hb_client/.eslintignore new file mode 100644 index 0000000..e6529fc --- /dev/null +++ b/hb_client/.eslintignore @@ -0,0 +1,4 @@ +build/*.js +src/assets +public +dist diff --git a/hb_client/.eslintrc.js b/hb_client/.eslintrc.js new file mode 100644 index 0000000..c977505 --- /dev/null +++ b/hb_client/.eslintrc.js @@ -0,0 +1,198 @@ +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'] + } +} diff --git a/hb_client/.gitignore b/hb_client/.gitignore new file mode 100644 index 0000000..9ad28d2 --- /dev/null +++ b/hb_client/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +tests/**/coverage/ + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/hb_client/.travis.yml b/hb_client/.travis.yml new file mode 100644 index 0000000..f4be7a0 --- /dev/null +++ b/hb_client/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: 10 +script: npm run test +notifications: + email: false diff --git a/hb_client/Dockerfile b/hb_client/Dockerfile new file mode 100644 index 0000000..35534e1 --- /dev/null +++ b/hb_client/Dockerfile @@ -0,0 +1,6 @@ +FROM node:10-alpine3.9 as builder +WORKDIR /code +COPY . . +RUN npm install --registry=https://registry.npm.taobao.org && npm run build:prod +FROM nginx:1.19.2-alpine +COPY --from=builder /code/dist /usr/share/nginx/html diff --git a/hb_client/Dockerfile_dev b/hb_client/Dockerfile_dev new file mode 100644 index 0000000..6f5a6ad --- /dev/null +++ b/hb_client/Dockerfile_dev @@ -0,0 +1,7 @@ +FROM node:10-alpine3.9 +ENV NODE_ENV=development +WORKDIR /code +COPY . . +RUN npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass &&\ + npm install --registry=https://registry.npm.taobao.org +ENTRYPOINT ["npm","run","dev:docker"] diff --git a/hb_client/LICENSE b/hb_client/LICENSE new file mode 100644 index 0000000..6151575 --- /dev/null +++ b/hb_client/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/hb_client/README-zh.md b/hb_client/README-zh.md new file mode 100644 index 0000000..d248632 --- /dev/null +++ b/hb_client/README-zh.md @@ -0,0 +1,98 @@ +# vue-admin-template + +> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。 + +[线上地址](http://panjiachen.github.io/vue-admin-template) + +[国内访问](https://panjiachen.gitee.io/vue-admin-template) + +目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。 + +## Extra + +如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) + +## 相关项目 + +- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) + +- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) + +- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) + +- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) + +写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: + +- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) +- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) +- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) +- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56) +- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836) + +## Build Setup + +```bash +# 克隆项目 +git clone https://github.com/PanJiaChen/vue-admin-template.git + +# 进入项目目录 +cd vue-admin-template + +# 安装依赖 +npm install + +# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 +npm install --registry=https://registry.npm.taobao.org + +# 启动服务 +npm run dev +``` + +浏览器访问 [http://localhost:9528](http://localhost:9528) + +## 发布 + +```bash +# 构建测试环境 +npm run build:stage + +# 构建生产环境 +npm run build:prod +``` + +## 其它 + +```bash +# 预览发布环境效果 +npm run preview + +# 预览发布环境效果 + 静态资源分析 +npm run preview -- --report + +# 代码格式检查 +npm run lint + +# 代码格式检查并自动修复 +npm run lint -- --fix +``` + +更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/) + +## Demo + +![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) + +## Browsers support + +Modern browsers and Internet Explorer 10+. + +| [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| --------- | --------- | --------- | --------- | +| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions + +## License + +[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. + +Copyright (c) 2017-present PanJiaChen diff --git a/hb_client/README.md b/hb_client/README.md new file mode 100644 index 0000000..b99f942 --- /dev/null +++ b/hb_client/README.md @@ -0,0 +1,91 @@ +# vue-admin-template + +English | [简体中文](./README-zh.md) + +> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint + +**Live demo:** http://panjiachen.github.io/vue-admin-template + + +**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`** + +## Build Setup + + +```bash +# clone the project +git clone https://github.com/PanJiaChen/vue-admin-template.git + +# enter the project directory +cd vue-admin-template + +# install dependency +npm install + +# develop +npm run dev +``` + +This will automatically open http://localhost:9528 + +## Build + +```bash +# build for test environment +npm run build:stage + +# build for production environment +npm run build:prod +``` + +## Advanced + +```bash +# preview the release environment effect +npm run preview + +# preview the release environment effect + static resource analysis +npm run preview -- --report + +# code format check +npm run lint + +# code format check and auto fix +npm run lint -- --fix +``` + +Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information + +## Demo + +![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) + +## Extra + +If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) + +For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour)) + +## Related Project + +- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) + +- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) + +- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) + +- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) + +## Browsers support + +Modern browsers and Internet Explorer 10+. + +| [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| --------- | --------- | --------- | --------- | +| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions + +## License + +[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. + +Copyright (c) 2017-present PanJiaChen diff --git a/hb_client/babel.config.js b/hb_client/babel.config.js new file mode 100644 index 0000000..ba17966 --- /dev/null +++ b/hb_client/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/app' + ] +} diff --git a/hb_client/build/index.js b/hb_client/build/index.js new file mode 100644 index 0000000..0c57de2 --- /dev/null +++ b/hb_client/build/index.js @@ -0,0 +1,35 @@ +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}`) +} diff --git a/hb_client/jest.config.js b/hb_client/jest.config.js new file mode 100644 index 0000000..143cdc8 --- /dev/null +++ b/hb_client/jest.config.js @@ -0,0 +1,24 @@ +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: { + '^@/(.*)$': '/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: '/tests/unit/coverage', + // 'collectCoverage': true, + 'coverageReporters': [ + 'lcov', + 'text-summary' + ], + testURL: 'http://localhost/' +} diff --git a/hb_client/jsconfig.json b/hb_client/jsconfig.json new file mode 100644 index 0000000..ed079e2 --- /dev/null +++ b/hb_client/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/hb_client/mock/index.js b/hb_client/mock/index.js new file mode 100644 index 0000000..90e2ffe --- /dev/null +++ b/hb_client/mock/index.js @@ -0,0 +1,67 @@ +import Mock from 'mockjs' +import { param2Obj } from '../src/utils' + +import user from './user' +import table from './table' + +const mocks = [ + ...user, + ...table +] + +// for front mock +// please use it cautiously, it will redefine XMLHttpRequest, +// which will cause many of your third-party libraries to be invalidated(like progress event). +export function mockXHR() { + // mock patch + // https://github.com/nuysoft/Mock/issues/300 + Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send + Mock.XHR.prototype.send = function() { + if (this.custom.xhr) { + this.custom.xhr.withCredentials = this.withCredentials || false + + if (this.responseType) { + this.custom.xhr.responseType = this.responseType + } + } + this.proxy_send(...arguments) + } + + function XHR2ExpressReqWrap(respond) { + return function(options) { + let result = null + if (respond instanceof Function) { + const { body, type, url } = options + // https://expressjs.com/en/4x/api.html#req + result = respond({ + method: type, + body: JSON.parse(body), + query: param2Obj(url) + }) + } else { + result = respond + } + return Mock.mock(result) + } + } + + for (const i of mocks) { + Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) + } +} + +// for mock server +const responseFake = (url, type, respond) => { + return { + url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`), + type: type || 'get', + response(req, res) { + console.log('request invoke:' + req.path) + res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond)) + } + } +} + +export default mocks.map(route => { + return responseFake(route.url, route.type, route.response) +}) diff --git a/hb_client/mock/mock-server.js b/hb_client/mock/mock-server.js new file mode 100644 index 0000000..4c4cb2a --- /dev/null +++ b/hb_client/mock/mock-server.js @@ -0,0 +1,68 @@ +const chokidar = require('chokidar') +const bodyParser = require('body-parser') +const chalk = require('chalk') +const path = require('path') + +const mockDir = path.join(process.cwd(), 'mock') + +function registerRoutes(app) { + let mockLastIndex + const { default: mocks } = require('./index.js') + for (const mock of mocks) { + app[mock.type](mock.url, mock.response) + mockLastIndex = app._router.stack.length + } + const mockRoutesLength = Object.keys(mocks).length + return { + mockRoutesLength: mockRoutesLength, + mockStartIndex: mockLastIndex - mockRoutesLength + } +} + +function unregisterRoutes() { + Object.keys(require.cache).forEach(i => { + if (i.includes(mockDir)) { + delete require.cache[require.resolve(i)] + } + }) +} + +module.exports = app => { + // es6 polyfill + require('@babel/register') + + // parse app.body + // https://expressjs.com/en/4x/api.html#req.body + app.use(bodyParser.json()) + app.use(bodyParser.urlencoded({ + extended: true + })) + + const mockRoutes = registerRoutes(app) + var mockRoutesLength = mockRoutes.mockRoutesLength + var mockStartIndex = mockRoutes.mockStartIndex + + // watch files, hot reload mock server + chokidar.watch(mockDir, { + ignored: /mock-server/, + ignoreInitial: true + }).on('all', (event, path) => { + if (event === 'change' || event === 'add') { + try { + // remove mock routes stack + app._router.stack.splice(mockStartIndex, mockRoutesLength) + + // clear routes cache + unregisterRoutes() + + const mockRoutes = registerRoutes(app) + mockRoutesLength = mockRoutes.mockRoutesLength + mockStartIndex = mockRoutes.mockStartIndex + + console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`)) + } catch (error) { + console.log(chalk.redBright(error)) + } + } + }) +} diff --git a/hb_client/mock/table.js b/hb_client/mock/table.js new file mode 100644 index 0000000..ba95f76 --- /dev/null +++ b/hb_client/mock/table.js @@ -0,0 +1,29 @@ +import Mock from 'mockjs' + +const data = Mock.mock({ + 'items|30': [{ + id: '@id', + title: '@sentence(10, 20)', + 'status|1': ['published', 'draft', 'deleted'], + author: 'name', + display_time: '@datetime', + pageviews: '@integer(300, 5000)' + }] +}) + +export default [ + { + url: '/vue-admin-template/table/list', + type: 'get', + response: config => { + const items = data.items + return { + code: 20000, + data: { + total: items.length, + items: items + } + } + } + } +] diff --git a/hb_client/mock/user.js b/hb_client/mock/user.js new file mode 100644 index 0000000..f007cd9 --- /dev/null +++ b/hb_client/mock/user.js @@ -0,0 +1,84 @@ + +const tokens = { + admin: { + token: 'admin-token' + }, + editor: { + token: 'editor-token' + } +} + +const users = { + 'admin-token': { + roles: ['admin'], + introduction: 'I am a super administrator', + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', + name: 'Super Admin' + }, + 'editor-token': { + roles: ['editor'], + introduction: 'I am an editor', + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', + name: 'Normal Editor' + } +} + +export default [ + // user login + { + url: '/vue-admin-template/user/login', + type: 'post', + response: config => { + const { username } = config.body + const token = tokens[username] + + // mock error + if (!token) { + return { + code: 60204, + message: 'Account and password are incorrect.' + } + } + + return { + code: 20000, + data: token + } + } + }, + + // get user info + { + url: '/vue-admin-template/user/info\.*', + type: 'get', + response: config => { + const { token } = config.query + const info = users[token] + + // mock error + if (!info) { + return { + code: 50008, + message: 'Login failed, unable to get user details.' + } + } + + return { + code: 20000, + data: info + } + } + }, + + // user logout + { + url: '/vue-admin-template/user/logout', + type: 'post', + response: _ => { + return { + code: 20000, + data: 'success' + } + } + } +] diff --git a/hb_client/package.json b/hb_client/package.json new file mode 100644 index 0000000..6e839a8 --- /dev/null +++ b/hb_client/package.json @@ -0,0 +1,68 @@ +{ + "name": "vue-admin-template", + "version": "4.2.1", + "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", + "author": "Pan ", + "license": "MIT", + "scripts": { + "dev": "vue-cli-service serve", + "dev:docker": "NODE_ENV=development PORT=80 ./node_modules/.bin/vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "preview": "node build/index.js --preview", + "lint": "eslint --ext .js,.vue src", + "test:unit": "jest --clearCache && vue-cli-service test:unit", + "test:ci": "npm run lint && npm run test:unit", + "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" + }, + "dependencies": { + "@riophae/vue-treeselect": "^0.4.0", + "axios": "0.18.1", + "element-ui": "2.13.0", + "file-saver": "^2.0.2", + "js-cookie": "2.2.0", + "normalize.css": "7.0.0", + "nprogress": "0.2.0", + "path-to-regexp": "2.4.0", + "vue": "2.6.10", + "vue-router": "3.0.6", + "vuex": "3.1.0", + "xlsx": "^0.15.5" + }, + "devDependencies": { + "@babel/core": "7.0.0", + "@babel/register": "7.0.0", + "@vue/cli-plugin-babel": "3.6.0", + "@vue/cli-plugin-eslint": "^3.9.1", + "@vue/cli-plugin-unit-jest": "3.6.3", + "@vue/cli-service": "3.6.0", + "@vue/test-utils": "1.0.0-beta.29", + "autoprefixer": "^9.5.1", + "babel-core": "7.0.0-bridge.0", + "babel-eslint": "10.0.1", + "babel-jest": "23.6.0", + "chalk": "2.4.2", + "connect": "3.6.6", + "eslint": "5.15.3", + "eslint-plugin-vue": "5.2.2", + "html-webpack-plugin": "3.2.0", + "mockjs": "1.0.1-beta3", + "node-sass": "^4.13.1", + "runjs": "^4.3.2", + "sass-loader": "^7.1.0", + "script-ext-html-webpack-plugin": "2.1.3", + "script-loader": "0.7.2", + "serve-static": "^1.13.2", + "svg-sprite-loader": "4.1.3", + "svgo": "1.2.2", + "vue-template-compiler": "2.6.10" + }, + "engines": { + "node": ">=8.9", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions" + ] +} diff --git a/hb_client/postcss.config.js b/hb_client/postcss.config.js new file mode 100644 index 0000000..10473ef --- /dev/null +++ b/hb_client/postcss.config.js @@ -0,0 +1,8 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + 'plugins': { + // to edit target browsers: use "browserslist" field in package.json + 'autoprefixer': {} + } +} diff --git a/hb_client/public/favicon.ico b/hb_client/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..34b63ac63a87ee3ea8e7a0f3f5b5406c437e2112 GIT binary patch literal 67646 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 X;@D+E7Ad=S_FbjlW&KY_+xPzm3MBT? literal 0 HcmV?d00001 diff --git a/hb_client/public/index.html b/hb_client/public/index.html new file mode 100644 index 0000000..fa2be91 --- /dev/null +++ b/hb_client/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + <%= webpackConfig.name %> + + + +
+ + + diff --git a/hb_client/src/App.vue b/hb_client/src/App.vue new file mode 100644 index 0000000..ec9032c --- /dev/null +++ b/hb_client/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/hb_client/src/api/dict.js b/hb_client/src/api/dict.js new file mode 100644 index 0000000..d46429f --- /dev/null +++ b/hb_client/src/api/dict.js @@ -0,0 +1,57 @@ +import request from '@/utils/request' + +export function getDictTypeList(query) { + return request({ + url: '/system/dicttype/', + method: 'get', + params: query + }) +} +export function createDictType(data) { + return request({ + url: '/system/dicttype/', + method: 'post', + data + }) +} +export function updateDictType(id, data) { + return request({ + url: `/system/dicttype/${id}/`, + method: 'put', + data + }) +} +export function deleteDictType(id) { + return request({ + url: `/system/dicttype/${id}/`, + method: 'delete' + }) +} + +export function getDictList(query) { + return request({ + url: '/system/dict/', + method: 'get', + params: query + }) +} +export function createDict(data) { + return request({ + url: '/system/dict/', + method: 'post', + data + }) +} +export function updateDict(id, data) { + return request({ + url: `/system/dict/${id}/`, + method: 'put', + data + }) +} +export function deleteDict(id) { + return request({ + url: `/system/dict/${id}/`, + method: 'delete' + }) +} diff --git a/hb_client/src/api/file.js b/hb_client/src/api/file.js new file mode 100644 index 0000000..01c683e --- /dev/null +++ b/hb_client/src/api/file.js @@ -0,0 +1,18 @@ +import { getToken } from "@/utils/auth" +import request from '@/utils/request' + +export function upUrl() { + return process.env.VUE_APP_BASE_API + '/file/' +} + +export function upHeaders() { + return { Authorization: "Bearer " + getToken() } +} + +export function getFileList(query) { + return request({ + url: '/file/', + method: 'get', + params: query + }) +} \ No newline at end of file diff --git a/hb_client/src/api/org.js b/hb_client/src/api/org.js new file mode 100644 index 0000000..2880ec4 --- /dev/null +++ b/hb_client/src/api/org.js @@ -0,0 +1,35 @@ +import request from '@/utils/request' + +export function getOrgAll() { + return request({ + url: '/system/organization/', + method: 'get' + }) +} +export function getOrgList(query) { + return request({ + url: '/system/organization/', + method: 'get', + params: query + }) +} +export function createOrg(data) { + return request({ + url: '/system/organization/', + method: 'post', + data + }) +} +export function updateOrg(id, data) { + return request({ + url: `/system/organization/${id}/`, + method: 'put', + data + }) +} +export function deleteOrg(id) { + return request({ + url: `/system/organization/${id}/`, + method: 'delete' + }) +} diff --git a/hb_client/src/api/perm.js b/hb_client/src/api/perm.js new file mode 100644 index 0000000..29ebf53 --- /dev/null +++ b/hb_client/src/api/perm.js @@ -0,0 +1,28 @@ +import request from '@/utils/request' + +export function getPermAll() { + return request({ + url: '/system/permission/', + method: 'get' + }) +} +export function createPerm(data) { + return request({ + url: '/system/permission/', + method: 'post', + data + }) +} +export function updatePerm(id, data) { + return request({ + url: `/system/permission/${id}/`, + method: 'put', + data + }) +} +export function deletePerm(id) { + return request({ + url: `/system/permission/${id}/`, + method: 'delete' + }) +} \ No newline at end of file diff --git a/hb_client/src/api/position.js b/hb_client/src/api/position.js new file mode 100644 index 0000000..2ea116f --- /dev/null +++ b/hb_client/src/api/position.js @@ -0,0 +1,31 @@ +import request from '@/utils/request' + +export function getPositionAll() { + return request({ + url: '/system/position/', + method: 'get' + }) +} + +export function createPosition(data) { + return request({ + url: '/system/position/', + method: 'post', + data + }) +} + +export function updatePosition(id, data) { + return request({ + url: `/system/position/${id}/`, + method: 'put', + data + }) +} + +export function deletePosition(id) { + return request({ + url: `/system/position/${id}/`, + method: 'delete' + }) +} diff --git a/hb_client/src/api/role.js b/hb_client/src/api/role.js new file mode 100644 index 0000000..e25e097 --- /dev/null +++ b/hb_client/src/api/role.js @@ -0,0 +1,38 @@ +import request from '@/utils/request' + +export function getRoutes() { + return request({ + url: '/system/permission/', + method: 'get' + }) +} + +export function getRoleAll() { + return request({ + url: '/system/role/', + method: 'get' + }) +} + +export function createRole(data) { + return request({ + url: '/system/role/', + method: 'post', + data + }) +} + +export function updateRole(id, data) { + return request({ + url: `/system/role/${id}/`, + method: 'put', + data + }) +} + +export function deleteRole(id) { + return request({ + url: `/system/role/${id}/`, + method: 'delete' + }) +} diff --git a/hb_client/src/api/table.js b/hb_client/src/api/table.js new file mode 100644 index 0000000..2752f52 --- /dev/null +++ b/hb_client/src/api/table.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getList(params) { + return request({ + url: '/vue-admin-template/table/list', + method: 'get', + params + }) +} diff --git a/hb_client/src/api/task.js b/hb_client/src/api/task.js new file mode 100644 index 0000000..1e3b397 --- /dev/null +++ b/hb_client/src/api/task.js @@ -0,0 +1,45 @@ +import request from '@/utils/request' + +export function getptaskList(query) { + return request({ + url: '/system/ptask/', + method: 'get', + params: query + }) +} + +export function getTaskAll() { + return request({ + url: '/system/task/', + method: 'get' + }) +} +export function createptask(data) { + return request({ + url: '/system/ptask/', + method: 'post', + data + }) +} + +export function updateptask(id, data) { + return request({ + url: `/system/ptask/${id}/`, + method: 'put', + data + }) +} + +export function toggletask(id) { + return request({ + url: `/system/ptask/${id}/toggle/`, + method: 'put' + }) +} + +export function deleteptask(id) { + return request({ + url: `/system/ptask/${id}/`, + method: 'delete' + }) +} \ No newline at end of file diff --git a/hb_client/src/api/user.js b/hb_client/src/api/user.js new file mode 100644 index 0000000..8edf3ab --- /dev/null +++ b/hb_client/src/api/user.js @@ -0,0 +1,70 @@ +import request from '@/utils/request' + +export function login(data) { + return request({ + url: '/token/', + method: 'post', + data + }) +} + +export function logout() { + return request({ + url: '/token/black/', + method: 'get' + }) +} + +export function getInfo() { + return request({ + url: '/system/user/info/', + method: 'get' + }) +} + +export function getUserList(query) { + return request({ + url: '/system/user/', + method: 'get', + params: query + }) +} + +export function getUser(id) { + return request({ + url: `/system/user/${id}/`, + method: 'get' + }) +} + +export function createUser(data) { + return request({ + url: '/system/user/', + method: 'post', + data + }) +} + +export function updateUser(id, data) { + return request({ + url: `/system/user/${id}/`, + method: 'put', + data + }) +} + +export function deleteUser(id, data) { + return request({ + url: `/system/user/${id}/`, + method: 'delete', + data + }) +} + +export function changePassword(data) { + return request({ + url: '/system/user/password/', + method: 'put', + data + }) +} diff --git a/hb_client/src/assets/404_images/404.png b/hb_client/src/assets/404_images/404.png new file mode 100644 index 0000000000000000000000000000000000000000..3d8e2305cc973ad2121403aee4bf08728f76c461 GIT binary patch literal 98071 zcmZsD1yoe)_qGfpFmxy&-5?DTB3;rUAxKDvbVzqeiAZ-S3L@QI(jWrT-5rArH4O2c zxq5&1-u3_1I%_Gcbl>@Z)@`}0ni zgTxS1Xz2Sp5LyN$jB+`(TK2go0$*ON+wYG~Qz71pR)(>+cvvo`d01{Xdj)u2?ZXzy zmA;x1Nzp_;m7?it6=)ebdFi9=K=7-zt#9B^kGF`IzK;CC(qMy@r8#>WqG2@cS5uox zXbf0B@c&#i)!^b0Mb!?4K=50dqjrDj)8Y7T(OQwKjh4xB0;y*hgfuAsToL#vtY-x2 zcDPC4UD@TJ&X)ylS~p2s{Vm(V1wS(C*u6kTtf;l}x2;9RDSK|B+2Q|vU# z5g|>`3ves^tw-x#pW$kM%4o{)rRUjP-bFAxh4kKaDr2nlD0Ny3>QcfT2w<51UE`{O zQGN&5UTB2YKA@#pXv;7`0|{yiD)FUE4eA?4@$j%fYDMKsqFQWUi?UOjnyuv<1_{u= zug?(m3a+6reFd6hu*h(3OM4>q*mTc~Pg?D7J-n+TvnsoY9 zWoxbD->+xD=K*Q$(+jLna6%I4kA`x*GDPIgI-Zm%UVn5!@S7kc4LW0oj3yb?d`)8c z7ej523IBV$9&o#~u-m;%@UGl)D|$=WY^|@KLU`Ac)l*@|602_{T4+M7IA6dbP#2AL)Eg1u&)lV@(b^iSAa}Wv>^6+>!0CyZsvtcv1&Qq&svN z+sZThYEIutRzAD;PdEXgWle?>lIf5kVEHlvET1a{;shO{ zn-EQLhR|g}l#-=7bY$DeCw*BaO6=ZCIRr)2d3ye8*IdkaiCqEbd9ba|DSo;7ROxl@(%P?=XHjX#v%4uLDStHz#?vp;8Jp~psBrurXiozhE0`(5iED>LBhfh5__U^oInU|$yP zEjDz&{zwWAxMdUZr8h#Q=vPr46k)9@kV_jypUZrWZ3!8{4Gc-ISvP>EqE52=OPg%cn3_A1Z+SuWO*0}uNWds4s zAhHbNeJ>FWsaCAW5waW9L4FA9Wr=FLpr*j>!WUNfY>TSb`i)Yththth%76Sc@)}q} z#=A@s1{4@Z>WAs!^^cH?WYrfik`9X{fiIcaicws{R=?W(`}oTdF7Taj4mNRDu&>;I z{4zufM6pn&*L_0n^uS2Kp2m8rj=vHajm%)0ZyNTcn@wug^UjqFs9J#iwD=khPyY|B zktqP6M89)9&wx(|%4a*P;&Jc6s(^o8=aRB(4Kgwpm-fAp_?~bxq0|4UPCxmP54Nw` zf8KveXS@t^YI)NG0{})#k;X3S`owvLhXtN)LG8zL?>f|k6Y<^+zeU_~P(n_T3cesZ z8M$)|qkPrp{Yt_1HBT1+ zO$}G`mF#sBF264SZO#=YiEgoZnB0y+E+=?at|BLr{=?)Ir}<1cztP~%gOtGG__6o( zMm~b3uxF~!@$Upjl>b=+yK-RE^|!b6=#XmBAb0Kk0yP63l$@RoTOm8=ocSwp{*zOYGx+e}se(;LO3e6?ei2{2&&Vv#NqBGgg!wJ(!R2P`LBb7c^&8 z?_}TM;6eYN3D70K&z~p#{=4r}rQ6HpW`vHNQ6cYvu$FmNk@Ifi=~0v3F+WPqS*X{> z2_Nn)^R~a;O-srktbEh9S&aNYACRic7*z#8+=w0Mna;iy>`*~9X)GjuDJ%2()!vdB zZ0%@0nm{d0Hybg!I$Csmq{VC#z5?Jn182ITfa?C@E(zU!0=cu06u$Y?}# z)Q!Vd5YFX{PI!wE)k>WaaQkvEERB9y_+J|{$ekI8#RaR>HTob-4E2h#JB02*h^Df6 z+hbAf6XDe)%Bk-yG^;-KiykYn{3G^*W_{J-^WXPidjIz05b`1L?_RQm-0y&O7;DB? znhfbMQX7`Q)xWCPdi9+!bnTwM4~5>a6{jc@y+8h6f(8CFuG-$*J2Knb^#~b_$kXV(?y&%;wLJv#A=pR$wIksq9h{$)&wK4AHHGojB6 z2(7_D+CMG$3c1i4)v3GYWLSQ5Fi4E)uPOqkT_=lR{&dUcQ=+q{7G%ZnFRo#YhBB7T zpTT4KG6XDdObk4tDsUWL!nCY;*QhBHa&fhy=Rzuuu@v+LHImBfsx)g-H;d=!^}p?a zgG^77#$I}a7(~GRLzx^(#GUa*ujinA+$hxZSd|yfo)lV_E1uj==Sh=$LkwNEasOf) zT5`b0yEWGfLaG^o+eYhw|&EXwMkEM>mX1|P;97mZ;zVY)Zsr#NQ z_wXNtrD+7xw4BGGkPG2sC178@xc9VW`wjIKq1&9CoxjJoJ{NDBp#buct7%`48WHE) zC$>LXBJREU2b$<4faQak(xe%J!T?_wMX2wIi)RGlMfr1i&r78EsVhp4-iqCvF&mHG z4kS$mO(x`l|FPc44H*0NiCw@p1ufF6T1qrfZx zWV5;6dMF$~gZGYJq({OgEp7LSuk~T2jza-BbAVZV3a>nup0jCE;N8am$F1!WO{#9F z%ZtF*))3`(x4OT{&;Ibpq5mgm{eg5pR8mNE`+AdK3E!M1R^k^_?eqFd6IT^(Ix_RdbaCSknTxXyUb|;m z&nNLmSwmlEZ7K+W|5x57X?vWEy@v0lp0n|tEjaXJUEYw9gaX7 z^uv?6E_PQbj8#SqOIQ0dtdeinTHL0b>j}|=KjZ()=~AFKB8@fg?{KMr7-*`eVN9v2 z5+(3xlWu4Te*okrAKMW0)Vu@Z-fg&P#851~z%5(K3%P>WkTRft_~S4dR%F~-z-#%4erE*iyIUDsI_aw!@R(+*>ZLLojl=EX;6?#;ZLvr}?BDkWfMk8f46 zly8wLw37nqASMlS?e0US<+1v!ZuJu)o=388_yaKFMZa(&D8r_&%q$fZ3;!1>^11Gy zH&1jY#kjMB{(5BY4VdEIM{#~yf1SA&y(8`ZDF$CA#^sPyKho>0h@rMeW|863S2=5b zZI*LJ9-puF-3MKE)x!UULqU`HK!EVidubDLM*;EsR7K7@Orc9%wX6s~WvK{qfnBqS zdPL)Yb>-qs`Os_K<6M_n3M(u4Uxf>>_qOZ-@3gObHKXsUN)R2Leg&}D3?__yiWf2{ z_V(gf^NLae+P38aZ?Jgbun=?<`Y)FtSr$1)N&!<)Ij|Hl_DA<$3TbL0u@oA_Pu=53 zPo9Vv!!I_vf6b{+B`MUR`4m&}!#^f5CPR^?F3DHuO97sVgG>x75ne&Bz@{VV{7gnk zz8pm<GC_er@IEsh z=7|sF0pe@QiuD95$$$3Lq|hqpBYVqOF`P2;GOKCPD)>t;&-s!xZ6Jz5f8M#F4bB9D zOoaNMO_xXyn1JGe19K1ta!J0G{E&HVTagC;yuR9vu(I*GVb9~LyzHxGW96Qzj^QDC zE5ak9qmHPu7iTq@REe+X$-7)cl>80e4z-=L?xp<4*t2f}Kg7z~cc!4y2C3ucni?(e z75ZH8?}@;V(BeweHxn$bx($aD63nujoxUaXE=Bh5z3nT-JrVJl8`doS#?v+%74Wa9szPtaGOjx8g5fJYN_27HkJicm~v@1-<} z=W)j=oqqC*zV(;aQ(H2V33Wf}k58JCua0sVA6TvIxx@}&yk;iI5dXaG(c#y2Ia9d* z#BG`lPxe*;<8k0(!0r7>CAY`SYLb6L48Ai6O&lTPYx&rh(3%eL+-H*_-hgW~78pr{ zot~+JNFcA#<@circTpjM-F_~Dv}@90IQpwjj_|L$2aqngFHQcV>5gVpD)#EfvCH8X zJ`uyzy7SDjemiuw<618slKkzNKqLfa2n!~@1*bm+(w)%w!*Q)P|2(#-(mL}HRv4Mg zQm8<>^G3{Aw#Z$6Xm2=s|066T!!JM%k?jWis-FoDxz7xDSlmL2rBBR`P|pqRTQo>8 zL?C~^Kw^%_`UjEioZ0#v1)6#A$I|JdN)OaT__=giTkbGnlfr;+LlYC8?ae5GTDFhc zdIc)R2o+ZybDfS7&D}Drw#-E>P%E+8Y4hqD`sI6)1gJ?#q4+3$>{87bS;qMtfBFBJ z>;4i@z9z!ze@nySP$v=-d%_-N(;>EmFErFAzEQPm{Mzwm|lFqUBuc9NI-DcEi1#S=7N~U6xl7j!oQ23A>GoOCz zu0p#A=$Xd8@q5I)xv<){ovZFNrVr)1zbKQgP9@^=CvwF8IWZ zNc?lp$>(V1gmqWooCCW!CtVxP=Ce86&vh}M{{0;zP9QWnasl7{W*~V=bYa*TaUQb? zo31v}b-tP!wp&WVNC_^Rxk&M7s4NtWosm9ztiOQqHqWNR^Z9yT#Kj8fZe6_*wqfro2X#-n{{aPZ-%v-r`uHAzt5cdI zc=SZ1D4J4B_7E{?n+3yKJT|Kl^({bi|l+Q!jcn7xl}x1MqMkULV?ct=_mz zelqcVi2J`-$wF?gN9x({!1C?NARW47f7xM!DYuxa+LGXSku;(Q((ad}-*XG=87a#* z_qLd-MV`|x3T44Il;|yPMop}pTE(n_UmtLWFy}q^h4?@l)1AXwfNl#25WC-`;+|m( znBiDcJEZwd5~TSWx1Ez7uAzS@*kHymO4-ZA(Uz@rRVjc2I3hMEt zfbZ1wmLFA-VzxpnW7{5f=A%wtsm^!hv@faA{FKODZwoqK>gEtF_xvmZ?~ZxiC^YVQ z|9?JtO31xW@F`AuqX9_s9~GDLIm(Nrc*<(;$M4O6D2;k@?+ZC}ShUd-z&I`^vbp+h znB`!{hwppFhV32vHTJvcPVZUS5}=Ue|B`&%XgifJL=I$2^<$s+pbq@-*kGp%@vem^ z@pBXV)z*$R-k|9#Xs7IF>IM+?NB&!Orq(|SWY7o_up1xdwF99sfv>K!6DwU&)>7Er zx?Gv_CR-FYp_MpWvuz-8kSV~(7BC?fm2HOV$WliWir*Z+#L}PnAGc5jbd$xzv|I|nA8yRK z5ZJiJ?7XFdoubkp&CJ55^plmn;;2l3yP4a5PG{XFQwp%L(|gmbA)GwDDJ1mERH(v^ zXsDeLyvf8MB?A&m{5e*NB^`~dRE-jj(vkxmZ5rKIpqwn10gsato-wTWfN!fW*Rn;b zp{(nR|4 zt+nh1hx~ijq4^wm)4oM5mVI1RPWVUFBE=B!>t|LN4Ldb$A$x8%ATgGU^w8lhurIzd zfy@ndCcapnr4I{ycx^b4^)lrpt(xC-rJ|Kjm#Q7``M<9iq>#j8;Po7+Q-}#ij@`-h z9rf7i_ve83GwHfM>rq`RUn2jp;%NWVJK~oIO#V|!pga~qfbeZxn^tswR-;JJfj+5si4i|3iE<2-3D8F^f<b zL{D5BKg+S}W6N8Ls2gGFnsRB5KZE&f_k@`KT+q4zUc7?#}&R{u6s_{6ZX_c3;&Z_Q?#CkO)G$u%5{DcU%B zvqJE}u-y7%w0^p;8u0Pm8s5)s8qHPErTcZ_&Qwp!C}+5=s5}RJMyi04LzC)eL6rCq z^M9&WkRmcqCEhy+csh5sgzdoGgNVC&2^mV!S$1~zJ`>+dJEWpqj3zX*cE1o`ldqJP ziDC`HxME3);a|7$ep<9`X4nuW5i`a44y(0?Cy|JAQWN{t>@sImEox4X8aMP-#$J(4 zGW*-R5KdkdH0QjC7&^z#2v~aQg@z@~pPy2!NOAbL;_-oAeIY@2`;A->U@cZ!r}Mz` zgSEUx9oCttaX(H&#$%t9a44HSVg9aJUzCxGuxMOL4u$fdYwy<7$i8`sZiP92L8<3b z(IoM`%bJ!`i&9Pmy0J5-9&G6iLQG#2qU#S4tywRc^Y<`wi1o%SK13^UN)g2k+J;4 zZ|&+AVX!!f5RmK+t|DPl~W-1C^UN3iax* z=qP`5R^~UkS*aSw=<_cDB|K{~4ZlyB;7?TM9s+7gnXpFod!U1o1|Cm(Jg{*Wm=?STJhVV&FP z&R^e|g2d|gZ9!rx@z%!rD6ZFK^yjN(`t++b0s(C_0^;wcugdn5j7HKOm)|~P_=_Y2 zy}{>(SvAs1Zz%k=K{2YjZ(vRQ^gf<#17!9UQ$ls`!@jG2to6Ik37<>ukirY|pNeuS zr&RRuf8$rPX-n6NUA3Qr*rKxb!9IWYS0f@CN2OiR$~c*#b3r(8k?Wz?NvjeE@rz8< zNb=taXf_Ne#}9ZDD9|A?@7ry*zfw2T1f!O@^kr{-1ZPjyhCi>B7`t$<88ND4rNH!a ze(Xn?Y|!@Xs`PZhFU7BG(>D29lc>ApLXZW81m%$IQXM;BTNRLdGZfpc))!X$S#@D; zUltUjVE`S7r7ZyTTB!CUS4icu^B=r7MwUZNKQJwTwEQLF&fuJOX#Y~bw7n1BgX5Cv ztF#mGT3Mp07rc=&*UtNxDVA$CxmNN^jdx+Oc`4jIMx>J)#Bb4>= z@&6(|0)PU%U+d3a6Grd`EwIVDXIp*B8tHo#)S*3p#b9vkL!78~E_+|Bt>|3r9<@=w zngkXv-w*Fa9>YNF8FXG9gCqtM#l?j;0d z#97D}K;WRP$zis!I+_8|-*9*qLKR{z%j+WlvGahZjJ%>+y zSf>u!zMdsH?>94Q>?13Q!Hh);he++PhbY%{$+M>!1aP-32oMbB+IZDIwO=8gKL7)* z`AfBY#p^-gym$51z4^IqE9-gdN4&c0@}Y>v_fW|P;s;4rr3^&u!3ZQ$Q4|ix^L{LSE;(JsBjeBRuvZmC7!jovh5X{^DSijU z2D6=qm2LhNjC&-}zL#`0k2@`lIN;mEoo)f~oCy9!4&8g-a9jmYs0WB_K&__ve%BuM ztKaZtCXIt*m!Wb_O}CT-JCw(!$X-H9!FmPPenpQhS|`yT`Coz(xfWEJ>|g*$yue~L zDxcU)K4OlDpw+zW4-sxHs5v;eyem-@FAlu71YX`pyl`fl)G*U~p3e>+K}*z-(Mh>Z zQ6uKvFXF!iYd171%kiKrHOcE2EE09s`*IXm*`%U7z)n{OpsP@5c4i_w@4+oT_ocl) z+F{GQcL}GlC*hx(0|TjD-?0`61y;fjeohOW3+J>Rs+l|Z%4u+HuO9#+tC9y9>Qwa4+X3JV~6|6 zPokd>F=p$TQM*L|Xw9rBDUdl&el_~{;LB*PgRZRG1-jB3`WD@PqE|# zzWFoi-V$+R#?QAm=Pw+|9zF{D9WvJBz+&bsS%vTktsOy4&m#<)=|c5#JH}QUA5_eT z+0IS*VBp3>UySh@UY4??vP5P>k^*$F4 z+OG!t>ZuOL4u;20=a->CB(#OB{0h;AXKN5P|>PLUl5&cbh z)dfMDHw=^Z5h4V@mYRlqIqp4n$4Qm7rb=gAs%*r%ImW5)k}A*=JYxq|q+|8AYSLHN z!fmm0+zz7{OMNzgk`o~(CpwynUI>w~OlkS9!U+0!2=O~F+Q%45^xl#UhX(APlMV}`a{w|Ah zSpoMHee2Ew5@EWE1d&xmv!Pj`4{mcXzjUj`^COp03-LT#ybpkNS3BY71MTpIqd+Kh;X5VWdJMqPE!u@-gG1X z{{HjAXQwQR-Pxjm`ofy-A47qxaIb^(Ks=SIPl(B@hf~+zCXcReee3s^D&^OcvG|Mp zJCG2wTPgmOzm$`x5OVP@FEQJ_r1-zT5_Hu8-pq1!|Uvrpmz z)slQ`wlgvV@oZm+I>}tzyYW{vgT(%baHT+=vur;7dhH?;}=^>aPu4U_w3*Z3rZNq&=M z31MVj{!ukp5ho!JF^Jw@vDIC4$ezh#?i6tv@c*Q+Q>pH#h5p83%wvWtc?^sES;>+= z|NLo9ku99OuhQuCj5zk-BmDy~z|=P%kNBGdf{Kx%<3M`Z2C0gDJ>&8kZ4;&3&BaWC zg>DJlbIB1MT7o4{l=+1<{yjG1EF9f*x9x+ zEwZs*GBGcAUUr$zAJzr!*i#+4b#01=>-*kO^uJASsl0U`lv>98V})rXfkR+x_!C+` z0;NCjea32@uAMO?c`tm82A=I6B)jARGzJ5{X1<*EEZ(kNUjt$x`zgEBsKxCImP`6{ zllLW-Ae$ke#p`JOm!wp_$))%pr}~!$%VmnU7d)X8VR1x`XbI;R5Z~+%Ie%$ES@r<; z4^1Yk=)IEw_}AuO`XB3e#2efb(WPUH~2*g$9{9=RnkFxE4y2m7!e&VgbiHy_V7 z6$QZN?a(8-ugkVVEz(Y0Rz-M0RgeqyhTPP^GV387HT;k{!s2K1LHcXBQ-pYmH&yRz zsL$c;EjoQ;$rd{40A6b4KjB-`O7R=VKX1YW0+5GO{4FPf zgp+9Wrh$^~_Si=CW<^#6ZA3D^^n49y$z$py9KL!e%28V6DF=}JsY}q zL5sSP_FT%5ACN|HR^d-~{6;BbR)D(a|G?g$3yL5ZxmZ@xdDa;*T^;UFFPn0WZE!Y` zZuE9g$3mRl1L`@M;Gt^qnfwD@7qyR+&P%FQgyh2;x72!Z?CqRe2Ta4y06|fF5 z=+{@snF46c5yaZ7$*skt!o%gKyfG)rL_%D_p&gp{I3AZStia%Wi)wV9Lw=hxTy@Lb zlaP&|Dm^17QMVa=K=c;pht$|eU3#G7V-9~3hGivM>TeqLdw`z9wEW1;xi5UR-(_AS zrx#x=r{fYo@hWHaaOXUCd&wj0isGD5%<^|j(V7YHz|f~54y*T-n zfNBSF_vgj{!RMIQzpgG%^A_yzRH5``a$S+p$@_8a2lnQ(ic*Et!_va$Sd2kCoQR`uXZI1N0L-86P2}qKuXJQ$OI4IrH>i>w zcj3DZ%Y`VW@mq;AEDzEmD*-A=HDik}c%_%=p=v}&6R_68b5AGouVo$l7d|+X?`|+F z;JwSW;<=oNiccagOP`5@@&DlBu4G`_;%RQ5D>82BoX80`yUFb2^q6)tY- zhuqf%Vr7LDK4I2dPUjp}LYoezkYc=2UE^YbYsB3zA9p^6WT-{s-0p0mV{6e`cX!;AP7Kb9Sr(ZA8g_c^S+_P8og#oCu@WWAWkfxA)dh&0uZbpHG`dD>WY@ zs{-y!U{tV^Ibt^ zBkVbQLBSy+sk#F)RX5($Xo{cfmA%JyUh$YuR$vWc?G{2%jQL6&;}tL-*0WypaS5xa z)jxoAeii>#ug`Tb6sLe1?zi^KR z3~x+EucSj1m5|!#5VP^klrJppC<^!ihskN^NgNh&hP|Q`>Tu!|{@D ze;-ypIawvtpin^+Q71T`)0A!Iu;m(K6&H%fCJp`8A&P>Br_x*iG&$UiI>p{PWEXcX zTnnq81Tc%TzR-mQfV~jEIE3y1HE2w7);A>PNhDyT-e@l}U^im}KU84=nAeJ%U@tpF z$8-MVtGL^1hQje-*-nlz42B8jHkrYx{ZMh(Co)GUji#7Bf}pSC?)rErvt#zzdRiVG zR}Q`qW>~<-@|Wgkfuagh9c@(CP}R3WTz>F?{5FT$_C%mt2#|j1K&B6yPMg}m|0Rqc z>~b%ar?Ds!M9{w1+8eV?wiO^ujg`2va|=x)_O552YVnGwJ6FH?5tWwh&~hjp`yEoi zyeu5*;te#lZHA`6zUfOHUG5jJpJ$6cW+ETn)3y2Nn;7}mi&OwESrrNMX23TA)!B2^ z2R0r&x^eu-b{u^u)M%5}O0Ws85NX2GVM^Frr92Do1~O;k z$aDcGLel|3rZ};iKlp-+I_>?`I~7Je>l%q>F=WCbl>#aXS|Ujv`P>DF-5V7PsExFW zI7et1-VePW?_$7TX>+3`tM2=Vhxqd|7djc$i{yb9!K(*8tRlfpHCQM$n>m1x$MQ2N z@T2(sl%+h#Mfz1zsqG7KVQy9^&MPv7-(q&q4!}dz3Oc5cVNCC|_2W&}lXzxMU8{^M zElP!-mbgz$=6L5`&agzc5FRaWLFpF7EIVHh62AZu2@S_~PI>y0i(T6EPp$i0)+z6X zH&&1h*B_6Q=kW$>#Qv#PT>*T}84T42{IaXOY?D|wHzLPa&8cf5Ik;IB?`GMfGqo`< zqF{}|aQztZYW1sjOGjO3G~!1k-(qVE6{W*0gUcGR8ZK_+)tXW=1$9nO64xN1lT&9F zvW@bqS+;zc1Q^=#G#qw!;p0Lqk%grwq7o{MYpQ2QBi*GZpWEV}rH>Jx0;FFS6$vGi z+kx7jInK6j;BgLtgdsXjuMqzF-LBO|4jTNB8Z9EuM$HGX<6W+$(B~0#P+Y&}7N#&n z)}Y8t)xdE=ccE#cLq#9|UJXMgGZfqFcwx%yc)x;4!aiEblNS@}c@PeOnjtVsrqr4| zQN#!o@yxu(-&UO24fwaH9HV!ZX@E8TQ;q~}5?ovm*W0-N)H7mp?sa2`p55@RElDy* zP~=Gb`t?20bSdKP#b^1Q)p*u(cZ0pTl-bUGd#Dkc3qn=x`RP64rS%_7;hpJ3lh!}DnAHJ4=u zCC=L6td2M!;`rhLI{x%0&}^nz1)oSBJ_QmooU?BW7C*#OT5b8>-aQx`oc>7jT$X-q z&&mu|-nZU6*J~1mBdIBStd!#I0w;?*G{+{?X{8&Di|D@#X!{f-8zSP`fR0B?YQIf{EiyAvE)ZP@hT=07jChp+NS0 z&9Ye-A))c@R$PP%-xw1(SWvpgq@4$cS#60=>_kdiFsv=FOl{p?zuBW%Tr6{RJT&Vn zg~_y*_a@Xtb41eHeV8Qf^_cN0KMA<^Qhv(u&7Rk6LLHhY{Ptx`e^G(0sL$(nIWnMD zh3!2nVBRRbEZO%!S1xWvK`z_dRf~!D(V)=NaC|vMB_kMOfbj%;5V^@l zBcVeXQ;kS<4iN^(a5C$CqL?JveAKU#&+HYAT0dXaU!mpMlaG#@8dZy>G^&w_s-ttl ze}y)#XTTg4%o=V}7P1YRs3wi;$MtdIRTc(G=)1OgS@Kd!h||6|9v^-IW=M?TEu;H$ z8(027qt@eb%)6Q3yGsdzOO(mJd5VfHv7-;l^6_rM1Yy3TI9}j=x{7z<7_OLtMzT!Oc zRdY*nd$dOl#qwQw-*f$x#>!W(zFYmY3wpA$+Gde=oA#-q8vZ$cGrC|( zdArb@5U*|go=uC~+=i!H?-XP9bKU)<4|~fmt9idT;sxvyR}a5j@0SydWIxc@yJ{E- zC5~`8iwDSE&XVmQvyZGp>xlG%+px#P?N$nh(A!Js-|E;122wVZOxj`y!XQ$|`!(z! zh}WLxJeITqU)xzL|ITDmC^&@mtvT&ovdr$goDh;IOMFLdSJ(rV3B9FOp{P?YC;W@7 zL4%pvc|sKjE0?MY(mHT7u8#C((WEzTkcM~o8&R(#6{T$Nsp4+61R;$-P#OjRolz>m zIbeY=!R;#g#-fjkn+?f+m64&^+KhR6b69L87QRT9pN@|prw}$~oyO?NNLB7{xAT6`3nK1g&`t&bh4kA_TM7D zPNX|U4Rmj11Ca?_Z-B(_cmaMU0t{UTb+Z_q@UWca*F1_S5v(cvz@OEhSY7`$D)DG- zC&LWFpG2_1swTnlt)zOAgb`NG^11(HUuJFfV2%4nfSr=$hhf@=*^5xlNiTm$lU8#D z7G}5eB&=+pxpep`3H&>5VyN`PmK46PE4z^A&lPzzJFQsbWcDj(N_$S%(|lSW$zFH1+xuPR&DKxs113IT z_-|7z+K0HipL|5Dic*0~yXicGvHzjP%cLvdbO!Maty}m=d|79tS&*ey9V7KD%W(%z zHnyoqz@@ITs_lWt|CSR7EC-XunFLr)7{uUC(HLTiquI#yydAajSH-Dor1d7^oeYR) zP?pj1Q0$ zFqxb=UQt!^I6C>Nl;MUl%MgP*Y~-7Zb=LX$8`t~cF#wZZ^{hTb8d?H^6ov(koOY3FmJ;F~M!Hl&;$yeZe^%_*T z&nzrf>$B!Vrxm*9rbeNwllFA|QO!X=UL4oh&89u{xGrab7xW&xm~%sYN+U8t&_k!V z@i2&>lz&E+@c{~tSl;(!fV^+N7t~TDTg&-KiNNi{b=Z*J@b~l2w+a?6oZlYuWk2C^ zX7Ok#U-yt2RkL~eIwX%>F*g6Y&O5tjuAuv!$D~EMl2iJgAexZ&14imantY3~DJYxv z_V8QbM`*jWjzQtP{zG3MbFZ!XN+Uy(0Us&KO7k1uO9p?Z&&@8)Sun{qpeMqu{GP$A zBNUkmZ>2~}n}d}bXQxT*e1rTlJmJrO68Zh8rBC2+VpK{5_SIL117)~B5}nb}Z4C9W5)ZD+M)ihZ8mNid{+_H*+AWae3IGv3sZ!m9FATHZFb@SLgZf z&0&x1Ymh6`g-d`+7@SZQ)i?x;o3pS;=2sNP_9k;O_)FBN>(byi6mbJDg;KZT6yz3# z8IP9$H6kWMw1Lcv>N#9{%0?T^mJsBV#GL!EW#4gw+9>dr349L84kZb)l(~-qxq;nd4RFS_9e!~UaqLJnDNN;S82Nt zy~9%Bo82DHpA34r>ueco^zSIh3++&Tj(H+{(b#`|9{m3Z!>sg2Y))|psRK_9X9!}J z*uuSM^U8xOWHZ_|=Xx`_E?Y|F-;d=p&rw_ow2P#HHXdSSdjNPglxo)LH%J+Tyfv6 zXW>XqV`oeTX8-wfUiiz;7;KAb_cgQ+?OD#T_*DXL*+@95b@s%jGD)31JB#RBm=?#( zvtSS5dIN`siIu{lMTv$Z1fEpQ@yp4MGZW#0;1;IX-|`N34$z$694267K*_`S0(zYh zv~loLNbaY9iEEzIv()$afmPur^nj`fP{^(RaKQ-cK83ga=l2wbRMrj*yXJcL;Y96* zbtp+V-rp-GhXtLM;>DHvp@EETJ_GS(pZ9@T%cMv<9Lq~W&;>;a7@(uZe;lq2I6UtO zj6x8Q+Kxt5=(gO_&PHNpH>)SnGoMjCk7`%LjkcbuT@z7rm^A>#fF|a)E6cEh`G@u$ zUg#|?q6=*?Pyt_ZnuwTEe+8wigkM;apMXyYEi%|^L5sV^Z`>hruvrM z`8;qd42MJlb4!t)h>Y5ZlYC|U$Hgvz)1nUgEDf)Q^mAG-tA2=llTKF`6kOTjOoc<1 zeyeygaR7+2{CLu<3_^kUk~x>9-=8f;vlZoCsfv?$gwZTacbroY59OE)E5(ZQbxe}a zp+2;mZHuwQhdAM+X4JG^?|UL%9%&6@)DA%EIa?9Oug2@Fn*BD$>zV)h8fFxk!Aj)2 z+P{G(ziD_KT!x+7o>9?%c~R!}VMac82K?p`-R)6uAGHYG@%G$Mt9N~w&fB^iF-*4( zD7V9kQ)8%Q>!hcT+I`o1k^h_TgwW$E+9S4S>9szO3MtY%w<&jjjBFTg?0)M znPVAdYb|U!?e+uCjnWO*9Tb3}20mDpr}};3cmz2KTQ{ieLcuU10ZM6~@a%Pg&A$z2 zhOzKZvozG(2Rc@-a~MpfbnrSm}fBhK>yi8FSy*>#*j zohK;Pj_}2deRhpMJ_JUpXY`BDMUId=xt+3!FSg8UiKhpYA;&${|BYT;aG2`q_erMQ zwXw9re8Cot*Dacp=e#Bkp9$ms{_~q(~E~W9fsu3F@6~HIhAG1fO1t z3}*EX<+ZkeZ-20Ryma_|%8WbqPJs4M29cr+h=UP7M67Jm8A~RgisfIhPY$}Wu+J!5mp~py zvQcQdpLV2To4(=Y^s!cV6iRKbz%jO&bSx9w9g)t*&rFh2qv%) zeaWmT1{7(?7Y#>KuckPN+;PX?b&yIp93a z&!MWZ;3E%$tm7-RJApjf^&CwhDxDP*+9G(wK9hB2Y#P~bkq>x_91~70%%x!%c>?m8 z>T8VFN!_B#@DO>BhJ6@PW&#%%8koMETzJvU3%Q43P(Pon^n6Uu@!Pd}MBSE60mN1E z!C%YB248gPtEG#OKtkUKZh4)>5j0H7jD{PRgfsgupLNC6n}?KPfm=E8fK#NY3d=u4 zDIWw>F@w5L(BM>$#USr20W0%lrfAkYm{`?TSIGWdYBT0vX;vZ(Ft!dx zR8yRUFk!p2A@woKby%dC@FQXolk9g}71GYm@b5OO;~M!GfDHI;tJbi3GUM?^m?vN* zL1zb+zmCm<0V|1N@KZ^H?4|BZUIt(-cr?7~RM;{|>q8q(^>AWfa>PB}8>;sDEHX;( zw2=QPb4h9Vfu_}>tLy5M2b$e^2EQ4mHvV#gNl!c91vCKBuC|o&Dy%5VLYB6z9RzMRFNgI-pRaB&N z0HHNCC?NbuaqXv8tQCdARxo0u&54((w|8jpXi2ONM@|Zq1jt9S4|n#~&7N9RZyrt* zJMvuDy1|Ma#XZpK!;oR{O*XKtekGj?(5>BQxdnFoz>3!;ZbB~%)mHnLJ&&d@MY7cc zJg4hxq8bTT`;k2mZ%v@f95Z=IGg=?2p$>%mqCmI%tLa1Fq+$&DRD@^M9pD6Iuz_b6v|Q zmv~^7t6lHb(JB4D+hc7*wUv*{z8sU6nncMW0l~!ijjEVxPeCXccDkm6NqebVA2nX@ zdY3)F)Gao)a(bSc|NiNdmDn;Bn@n&(cd)J3(pWeT_ z(Yq#}`x5M47B%=T$+uWHqJYzfVcEM3a$H>)CXz4|<;|HkZoo{>qqKG)RKPTZWkHKf zGcMo@K7)7IbqNyW1f)Y=)KZ-J!>NxybwKK~(C#V6`s~wCKS5nxGhtBI0o5TUFB`Kf z4^#z2_gZj&I8$_uS-sWV)fT`(XGv_wy2L55GWpZOM4m|>q8r)+{&odMJK4R?sx?9V z*KjYcjG-ppWZZ0;-LQmO3OQe(zx!Uo7GmHkDK&Y{Gu-W4m0NmV_-$~RR3e0u-l!*b7ibQWDH-!|7BoPF<^duGj=nRQyjtLL{v$6VXpMCO!Z8e&Dl#r9~4Is3d)DS01NQu6)*>1lFCgd7&2Bc%$C+zcl(b z$xi@G+DDUXM2BmD%H-h2`x5$@Au5~52JWt8id5A(R7}?#ddY^WUu1hTcwB6W-SXp4 zl5=|&>@N+>X|G7y)ZyAZ(VT!8^VT-x)HNR_hwy@oH?OEFG zS6%BUOqBd@Sy~*`>|s*rac~;&PDo`sgF+Ys|(46;9gb6C2S*Ja&o( zqF?ly2HM|6roPQgMw7?anzR~>bnLcZQKpU_DG>O4u&doa-8;0u4H?QRzshQ2*HFKR zXmo&oR6%_(!lsK5>_S*RR4q0f=+tZ%Sn) z#isMc53y8KcpmH0A9p7!25sUIeuv%Eu$vzwa7KfFho6UqtMlI3jLBrsDjY! zl)7Auq_MKRfa0ZKSFMEzTj$#9LviGKRsRorZc zXaGAzgbJF5|HIZ1)Ifp{waUh&!^K9WC5U=w#=38Pt2>E(DBPm6X=6nZ_S4qjm;To5 zab`rmzQEh<2Bd=4#S^E>2cX-9x$Nr{QdFN(?ujbT#tQuV_k}r6C^wGT`j(QVdX69B z&i_++@wddENFD8tcNwPtR%ny~iBd4Mz&a_q(tJ6+QJI9K*QZG?f1`ELUu)e_iLB2R zs3re4{U4;zoYJ8(UG3iUG_+5TXylL${&y9C;ZmTi2o|c8M~$U@`z}`O@C8-KA3e5< z;R&^>3jW$+Uc(tr^BD(*Zw93q7|YFtc^Sb|b=83jR~_W}l5Opg?q2Md2`2x0OjZCW zrOBkuy$1N+ft=;3fqdFZ)*ANr@A^AXVLx@986i1oM zgSMlCh33E`>NW}LZXpA8`A4r)``QSTkoB8Vv+uRN}>4#tEW|0qi; z@A-%OwxNVw$cJ_*0+vL<*aJ@~L*$;k<5~N{P z|0nY+urvWc2AzkA&hXBQ8amu>s7_=d*hURqGC@(EWcXz);W4b$wuM;dhyKg-;0fZnD@Z9GysG$06DTq zDMdxAJBI#VHOkk=!jIu~bErD;6u;M&3M zvyXoPD4U&#HvPo#!uaRHbc0=qZ6clvUR=WHc2BRdxeyOd4w{nvrz2@iA*>LSeXe&K*h(Wx2WmCsE3$ZaX;ld3u~|nME;o?I-b_fn(GSS=888Q1W zu`7|J%{!Y;zA=rGLVQ1Y}D3XjBI;Y02fcg!|19sOvBrC1dM+0UcB7JwajRAZc-)Bs(w2!ow8$L`g`H5 z?-bdCWEE4(xt$h%eCh1#KSBPZLB`&mtYWfU=mLTt9a67E<5gMUAGzCo^$YMngzd|l zrSUL^yF;gQ`AD!s{w=keKeQ*VVJd=v$$ns_vlJGNUk5M|Cd%5GVPl{8#~HHLYo6@` zhnG$V3i^76=9F*~DFTm#VXQT@?JotI3L>*q7ChhDm0#-?5q|F-AotpS8~)Zh;MGypLSqsU4$5oHlFgVpeU|gQ)P~-Yhg)$ zh^3MHyYbm+p?Pvd77hKw&eQ(x?Ozp{(y7$rgX8*XjM6_>^o|5kAQqq*_a@Y&hThlFXD_Mes?+N<~#8LLVdkMgo% zzwnz(+(L?T2aEqS5AH+|5`DLtg??oak}aOQ>WwnRKf7%4n_M-Vp*&c6E?o4#ISx|U z&XMigzQ|+?27fs`zs6nGka0O|P-a)~&1;?TN4CHg_aW4CYbZ9oO(;Qj$5u8f>PH(l zU0f!at1u3_uQaL)W*hQ5+DWD4$&co&23G|lB8SleyriAh)jU!7(QHMMunccqwg z(ET;31Jx>IZNvS)&@@>Ehd!*7CQpGp!>yxR z0+~*xTx66s@S_hCp#I~eE8pu^#Ga7;rCmG+DvLI_WZA92zl-<4QPuY}{ado+i4~VG zzYHyy11Iu~mbBLmUqi<}Q^d*UR>zw-&QOgFEfu<)?^yLW?qt2H+_K#{$&>%Y6^pwR zJnSB(^LIzyzQPnhS#F1}YJ?S&+s6P*QL8CxUL7ZgkGKJ4i5J|>=JRa<--Tnxe`uCT z%5Sa2tkaSE9|suc$6TTCtL)O9q^Wnk(AU>t83F+Y2*O7E``5?3E#ER$W)2McOK85p z(vHJcHDJ+io0v2zVXdBt?qnk_$Y-=-m|Dj~H{1A~!bMjCHe>YGpDse11y5mZ!OvlDO!^}qtKvO8AWbHifx^S#9iv0~ z&>#!rxp9$!n`?tD*$j5wDnH@(+K&fkxs_9KWE?zGIuIH@=!pBfO*x)JAFLbJrH`nY znq>}aA{USUcSekl*(lMG{$}g`fJc2}h*4B#|M1J`+Uz;Dzv#y~5BFKSB#P3?DWc=0 z^#&wnIZo8Aw^~}?lxz-<7RxP&2=n6E2NGSUtGh}8jDl6pn}&2vcjl|@vnG23)~0RU zhUP)iVTEyJz?0+vMF#bw|F|e#0{8ubOg>h*nT3M4Q=h$-b=f*ng&-v)NVZxkH9|lPR%8g1l4)l3X2hdDr~@^JpwyBcg9J?5hR2CvFAI^`WC z7x;)ZpgVWZS%vu{3Jrg%mU{u{3;m?s{4P|T@wm7;{ZzM1(EQ8N(x6gkhOrN{YLsjn zs9FJPjnM02ClEhYoEo*V?R+RGY7-`M-~Twn54}Fn&%9h&D>K64N!T#1z^ddE8`k3? zE4nv_HU&$ab=pS4aGqD#o*wJYy6n0pzv@K;q@z8RYwGz?a1{9M?aAp+|JIo9+<)>? zek2R%@=fqPAhL%7S(W~@kIdoHymp~-`{K^4vvY*;vPenk$)<*a)kuRh_BJhvvNHxl z)aH5vfxNH0Ay1B#bIncgg|qW?tU=FG+Qcj6wRG(!lj!EN(ZcuG;h0uq3DW1x72%H0 zdzO|sT_F8(N?7QC>;3<_gJXDpX+T{HU6U)E0!5ayjQjl7T&FksfGXe7z!T>eZI*LP z>-F{7Qj1z-^^UTszCh^xHHnmb7-wdOha05kr`Cl| zoRaK{QJAPZj`bvUQ26)A0z{lqaL&?1? zF{qTscxo>bNKk7R++`F}kSGVWq%^cG`IL~&t#Y_jK>#gz0rRk?Hz zN#kvXUkdnWihj!mKlB|+(=v!Te$e}|{KAg4>bxt=gS@tIakB~3gmH^``wRa#vin(< zEUGB)F0dh9A>Eal5%fg8{L;jFALpCY_pWrEuK_He97$tbKG9?9}8$Kb@`hh<~mU2jdee{?N>=J}NJ|70xzkKL*Ca`*V;j9-+}>Hir0 zo`Hb@LH-P`|4zFe`My7A0@PkwdU#02Z~qaepqY1+!QfFHYCwsR%3g$;ve+?3QwT?vS&IU%A?En2jV= ze;KgvAE3GsCA}C?B~^O#4356;iDYBCOh&`KN^uwNaDZMPE02ouWyHp6jbzC6m9w9P zV~nl_Kt6PuqHb^QUp0%R$c5TTsmc_gEd54Hdi^ESZ11MC_|N2X{^!vpRsAL*8{c8E zw#Sm$ffh=wSdp9m@c4UR!fB1EMwkOrUHs7y%(H13$tFL)V$gL`>n8Am1rAfu zrKp5w^~go~*yq_Gp_kyurR^&zM{m+*>hBFwc}Z=)eIwORDAcB-FR=9ee%!Y?!hkpM ze`vOakKXb2bbTx*Cr9l2CuGPgV+-Eyz)$f(W=PQS-rlX7ZlgU#@z%VeLR=~ZGQ0&+ zZDQc|ixx!u5-y~MX~QU=N6#XFe)HPj9Pjkk#{LSvh7j4pTAa#(V!bebxN7~Jc8473 zWK?&2Dlqh+#REl1%nDZqWrg*px)r9%g>gO9R)A8D`jN#sAZTg%4n=Fz+gXixU83()q%hmgX<7SvF2Tpe3RA2CoY9DKD$;)MxxZ4#--G{}7uPf@ z2Wud&$e6r;oZtT|O%v!I1tIXY_P{}it~j9)@Y!dr1IS9f&79N)L<3%!c3&oYLV4QZ z<~WFcH@SGD?B7ea5u@40u&+nl%f}vr8mrms6%^e}83l(R4~D-R$$Q zx)`uulwY~CeCFzS;JXAur_w^t`)F<1xwiAOy#k93Gt1%*rGamf0Tpe?q<+>YZw>Ix77%zKgt*;E>ewVK(@21ncMBoZ?nFuzAyc zd#S4X{w$++HW=+IZ#1%L$WH+jR;<={b%<*7-)lQ}1(NK$lZi||E%XPzd!penLs0Ew* zyXHd{6wpc7Hxm0dTZLE(1uMEwC58E{30r=;mZPv)EGk(WQ1FB*E5>uh!7VihzP zO`$*X%MN%OBQ$J^&kiIo6Cw)xyF2>Ub~9X3&b9kHgx7nEv>mM9DVAei_`1IXD8fV3 z?VK|LT8xB>3*$h`m%wf_(2}ADDvSBz9HTFEex8@QKr(z(cGalPB9?F`_3i|RqvLi1 zRCYaY4uSLuZf#5G8VGZ;XC}uIt|T4l6C{Ug(wlD9**tZ>FiH45$wF^G<;~ z%tix5Yu|9AQ-J13=q#=5Xu+u813xW5P%=@@Bt-+946>oM73#oezx?wBvs7(#tubbG zeEY^$-xeB|?hQEe!fP@!Mx@lXc?%Y(hhc+omP!mazu34UV#vn1C^mIg^6~7K5f-st zBwo5~^7?$4LI{@ISvLH9U`K26QdodjN4F(L7N>8&$j829>74MQxo*48Sp|49?1%0B zEih(zm*C!c|*@!PRCPQcPwXoZAQak}H%5u&t zdGo&&@uG)?#>LySJq)~ej(^4bZ*OlQQpHFUEHZ|J5}g-6V942pg*)Ojeh12mg8|&* zqyCjbB8g_I0DCcHOVHyL$@0YJVo&zm=vh%~MRuQXU=rSpz)XVO_o@XE9!a(_^CH*sH-|4dGeeAM6Br&VJ`4 zR!qsY)0)`2lIc&3q;=SVXND>cjS+G-zudlL4;=1Dn&MW~#@vMcWUa+!OtQUBKj#<8 z^mWznj_?1&ydX%B^tEtA4_AmgiFohYe@R)T);IUOGQM+e-QOJ7h~i&F21?PuaNw0W zjuIExaiN&Du4Rnxf`e>t=AJZN+Ej6^qBlbQAN2=AakaGVdRAKRc;XH|XYGlhn;pjw*!un56VA;9tKDQak*;frJ_Sh@ka7Th)? zs#;PCH#}afKh&+7m7VKD+ZIjo1NpGBr}BdJmf?~&0i<_PQMusMcu2MzJ%j1ZkfcC6 z8?XdwBG4X$8+_oRSR3;(J0Z6mdGt!zaDVISYfnBcr;kzbFoy0iTzX{waaF+Q4OwmK_=5Ikrcc;ZYE zTCUuusO~FLJfnjg5Hb%Y4m@GNOz8x^8Nl{86FX*%A0A*UGEVH5xrt~7zIWT@p*bL+ zpQc-q_;?8Rh5X_{aU%qHie&_;Th@`kE`o03gd3X#fvW_)6^dGmchzZNuLTqdmj2d& zQ@1Zkf48kNW&oeQy6ez?@$J_~^#hsrxSCm`=$d~FLSaMZYd9 za((3{C$j2sqc42qWb^L2;{<-S{8{rU{ir~P>%5YzIkW-4SjWBm|Ir=?tWDL z=&-APb@%Pmi5^6C7UEqpMpiTheLS6dB^ON9B;qoX)K%y4oX8)&=kWvA`arjKJzSbs zZ`3s(aU63SUM= zxEo`{c`$yKOz+0Rj2(qbV3+&rXNFmUl1PV~38Y1O z-cvI5AkFXz`@fiTFqsX3(AIH&h7(cAcLLK)cz$ChCB`66R>lqkB1h3opuYO($bs)D$-9fw6j{-hc`Pek+9!G^5OPXN zUu;bz_hm_fCsP^@L;T=MXdXCO-p6H!!@TfsDj4ILC^#uqDqRzk8$~T6!3I#od4<1Y zMWWDPN${5q_xNsi4>0<7yzE}lSB*)OytfmPRMH>MK_R7^-s7%w3ae0X_ATg{ymh`W zt)a(u;*CJm1zQ9>)td2H*=i@Jq0C(iMBR(^rZU5i$_;1En_tXe&hw^Tp^rPpPXO&> z{VXuyk}~qNah6Kbs&!6v16Xl;@URCb^F)O`DbLhah(4uksa|qfM)K(vH*OXSBq<$T z40fSA+`^NdE%$_j;nzz5kBKzQo<`Q`6i_~cW872aNEH@-TI;b&b9uu;L_jvwZa((V zH2?WQTq+u%Z=C2rx=}(aCw1_j;}!r{X&8 z*YNC2<9qyK13DIGGuViP)A@cal~flzJSW5+w%d_LDeCBIlGZN%3rZKavBJ&CdB%%n zhu5cfhLJOnw_I}rqKQHnIxN=hyZ53y_xIsFMB#pa-INW}Rih*)2-Dr?XyS zBtzo;P&2Q~xK>+tbZfsJ??p`;5hkDkJ`H-JMUlw8*)=u4d)j?{`$gw4vTa|7?j_bM zwiYgqf7I4GfaXP|U3RyOL8!If4yvY+vL{D^mAA9VLkHbD=RQ8BHvZQOtCeH(ug>{Z z_S}F-^o3OirX1m*kk~Eo-S#FcWEjr7)aZcpNbGmMVIY|B5=`o}!#M;dH>6Yiif8YU zS?&D(@pJ;it+a=GE}kb|(W?tC*Kr!g^j$Q8M91tpuT8Mt^M#;^b_S1Uccap21MY(Q zL5~md4t96*#ROR;hP5+YQ)U_yV~Qd<5a)XRv)#OKHxu~jXk}&rBQM^Ye^XR;Q`U6* z>_&md{U?#cO&BkQM7hmDIzr-Lv!0{e4fKr}!tncb`O0#AWs#6Yf!;b1IVviOzn{3c zkr^6lwINAX(iw%%WzHdU#d$#elLHwYh*osl{7Yy2Ld`xRXW^5hl~*gtBOiU6W^*zsVuy&S zuaH01^5#J@3x}kbk_RB1PoP(l$khOJoZ#-srQ)-x8hHw_HSWFq%(V~i5917*-a-&3 zw+YZR6Sb2tHMNtY+qNl>ziD4K-Zoc}R5jXV?lLOaXr!)Arn&GRF}FaPxSgaj_$}rd z>=vIr0oz_qK6~=a3_*%XP$^@MiWbZXs(kT@48wQDKcF=5^P9|w z<$|iGpf~e^vx+uHLKce+CTVZfS*dYxEjfW};!yomTQB`ATI6)bEZJM3+-nW@$mTQI zuyh6J5Jd=;BOIq0>~eJEA~pm^=W5h|ig;e`&EDPJQH4w+-+sFHB;={*y{LT1rdy|{ zr&^^m%wa2g+fHL zmm{nsV62SV)0dSq4%x_<;niUF>!X(2xW`SRJJD!@HeoGGL{e9D&6kD#Of~L4dor{G zn~KQgVI1j`;EW)`4+(UaSrlg_V=f<8(FkyhsQd#FjhAPxGGB*3&QR`qJ4nD?;O5ml zhVgoNau>*xIv3gIr{omv{n2Z95IAAnlyoV#u<8P@nF}T81%1yme+Ax z^D$4a9|YZ(mCYWhFDtyhtrULMtb4Py+#vds=)K+L0)fB{e<8l+v%n6h;D^UH-cEw9 z^aJ0_^N(sNioJhXk3-*GSPxYu6_`%KLL5FU2hQ$9GoDmiBs#cU*vkh==Kj0A|BU+R z!KjaV%;`|>e)Lg#oCefe5ij7i`IXPfnQ3qN-s9)aKDnT_SOVRQg-}5`o-2`op5#!y z-$u}u@LzYp_ z0LJ~*iW{muUf^nw6>ibMe`)3_J`q z?&D?8oFm|bo#r&Sp>XqDu;i*qwCF8Gn8Hah_N0x?p^d%1qpPV{pL$zYLJ_Y9D%e_Uox3It;I+E+lqtC z`iPman259v9u30jpV&luL;ho zu{kHD#pRLuyJHzpZLT(R>=fUqcVZ)F-$1p&P5OjS%3{AXyBVzo>BIZ54yE_+?P6Oq zu`3Z>$7AZIwffiKuTQNO-}n1g^+5O;vqX9s>)ZrleXS z;9uO)MrEV?dgwsP`=4V~aRF{s88QCrHYx*zfRb>msE?*!{jky2HH^$Mko&B?4+yyO zXQVQHKn%|3;lK+i)^07bhL%&WhDq-=zko=7-<5b?hzo-7p(bw2PW>Q7`s*u(KfLR_ z9+GBYvQo2WbN1%FA@kG6hYpVB1vSVX0gl0BY2-(>}1T>7*CXiYOiv?!1J>!>wd zvg|^TP+9Q=@{v~9$n!};qSl$e2PxNHKmm~b6QZ6OJ zqxi@qOVZ3xsGWaN`p$svhukY|H{})Ubmb5>_qa*|B8HvI2>Obw(c^nB z9c4vuDH)5pC=i+-4j@SBFMgz37RI5$>`*9IrlEvw-Rl$3THAVsi!!#-*wADAiz4^O(^qa}0P8NLGEDyldQ_VdyKvbb3e7ikAA;=q+wgLTs~v*(mumVG%{e}gH-u1MCk+o) z#?Bqi##51Pwx?&DfJmSRo>LOX2CWxJv_QmM(~I00!bj>}>+tS*P(b;03Sn+tWb(8Z zWPWp`C3P zMZF^`16hL8D8*FM7WpGY&RZZ0XQzw^wen5tir5KA$Oz~Wv^{3blUTD4>qvS>=71lz z*FG~{nfTieB9<#G`1xi?)=azd|nJS?6YNe_|J- z_#rCfw%s#-Sjt=Yb1MkuyzAKb3Z<$Yhu<#ZW+oiovhDjw(sAwESeg2D*01D6C(p7` zG2dcc6^tt^jIS?HMS_AnjEawz*rAS42|3>ebFhmwnRN{*8mx=G6q5u9-T#F;HXzK+ zNq4zsu(^Dd{AodK#DoT#;U?r}|18mm z?rzZviNOpt3wsPeSTwZ7r~bl|_ueH1eEof11Zsq)jbUU}`qxX!W@1P5RSgx5C{kJV zgseT!{?#IEQ4cOKA^w2VF}{7?7465<@9l`x=4+o`xMpobW>(8m-i}h>fM6U+-**QY zv%E3;=ir~n`)@e5!Es~<*YUVBI9FF!a2Q+Ta?MkjgTzk|Rx!s#rO2o{Km5VxOG z-Nuc!3_!KqLb^)J$@BV#bcW1OVv9>g!eDKR|#0#{Zr+BlsZ}FXwJXi(ZyElYISes!KO{6OlUJXs;31y!T+%A6>yn+;6Hg;`}VRrPOd)Cd)V217YwdgU|783 zO?MX!JO@=OC?R6aG?@%M8hq5lRKlf)ab6V!RR1 zaFg9k`2J$qw_n~JjsoCt*VV&Ze;eZOVS#f$mrvcuij~dBTehbAvqPYIC!O8wq{3VI z_F3srQ$@?3E+K=HTtonSiuL6eD+N2Yc1TZS>E@q z9*KM3|BX_FY|{r;^Rau>3^)-?|6j11_4k|A1!`26n;hu5nUdtsCTCF> zisFzW6#pGsYWjc-xR&S{%RZUqcaV+Y>WYs_!<^3=3xS(??oo$#%MoI0FiuEd;mss0 zQN_~NEAW40SL;K8z)(k?pQ*o1(Q;9|ZnDDP^mB7t5AB2UkQzI)`;|9bgK^i(RebVB z7nQI)9(*g)sw92TNa^|~bB>kfx=dR5^l5SegWmRKR*}s{$ z5|R~M#|@Fl4zi`Y740w^P(Ssh7-kOg^HU0g5b!L&o+h5(G}u2a+6Yo47##kSbAokX;PtNz(G8wdzJz5%+N9+x>?&z)eK^$XXR z4sOkoktYO|uIYPkCV_Dcjj6({u#N9>#0^(*EVcS$tWR zyDWNv(qQKeoLBn>Bj2HKpS!sx4s)weE+3mSj%)bb7m7lDY1@d+CClGy|G~*YVjo4X z`9hELX8hJSo}p2barB<$&7)VGV!Z&Kt1}gMelg3fyY+|bmUfaKT`Iq=n>qN7FClh9 z&G(rz(#*ij@cW)ve|3n&HvuUYm^}lkV2%S$puyoj*_Y{?#wEDLM+NPfh4|AYBOj{$^y_^1?5qJLv(Nv=P9G0`Z28{bEJ zQI&H4{!kwkMR^RgNRPg@H}_|*XhSxzI90ybGi4;B)Sy+gCS~%|&69nQ^NHFr4UOEW zna-mWS}?*E>O`DO+;g|ohY)kKN4AC$zQF!4(DVqs1sv%PaJw8w3IIKDb5QAY?Nen$W7}Ft-R_5$X zW>!v6M4u^6zvIa5bzX_eUEp6`-R+Cr5#!e}*GIGo$l&AspB4c0D7?J958u+q$A>xa z#lP%Cf~rYHV)nWl(t_9YKZqU8x;GUr4hdW`T*dlR0o9wXC9r}cPg|Gz}oNt1TLZ>gJcn(jG|SnSb{u%8QL+Md5(z7(vQ6 z*}CvK6AG&NJG16pJ$`iwalGK(tNiuL?WEZu^LD=54Xb@UmNrHA_6mqwy?h<$Q11DF zRbCkO1BUPyH*Cr5ApT&QH&Wq0(0={YLc@xZ>PFho8Y-*en_8ao*iJq3b;b#R<&(b< z4|pf`{sH)0Fs6O#P9EO+x&_*K|w^3ELP<2Ebmpoj;o>?cn4 z5X7GHF7D;w=xY}DAm_{`@4NKn4sFyA-YC@V&d41$HV+LTrFF7O<`s-N#0Gr0-1A(x zX(Qed&(6rU3&oCz*?ciNMQDPiMj?6?rQyTr~TPGlPx6LxECGG3)7xcCE-eXD8*8 zQas1M{F{M>wNK3Ri$p3YAwzoOw9keak@2%>b^;>f0F(@bk@yg#9cfDI$J=rLHH|~2Nvu2p4;#)XwZNIN5yV zMnZ6m@cBvXYFjIzu>FDSM@jf&)I*u<%&W`8Q5z@kVmQ_6@rW-pBW{(Ep*NYwumsxA z!4`#g@)$K7PtMk*Ex-5kaKD8yKbM9x{m!-SdS}~chKAO1@cMtHd>sx@R5*z1DhgkB z9v8$xGZ&*1T{C1Oe9cbIBAR6uXDAhQt~HH{sbPl|Ru$#^~xQp8gb4amCmY8rNb+Cn`71NVh1;Q zW2TobSTq`1)Ft1bT)c~0e^x3+`j=Ii(}K2JIS1g3 zcH+ryZd}=|zgqub*5hE5MTm3HPF1Fxz&nErePyI;AfVI&y3)|)>`AEEr^WWk02!X>@KKpZz8X;|1r4`}|gC0VoZWBr;6;MxzI@~6)Sn@)F&{gQrr~X1@{Jn09uj_J_ zq$=}Izs?!F&@xc+9YexCH(@AWJh(x(gXeRqmAL|;8%#2+L#I?~euR83&XpzEj_nH5 z>0dqE*+&f&8}Uw2EQwj=qcny`ZoM5r-%jg&hxHk!`t`SA$m>tun|={bxwsMZns?`3 z6Objdo>fH(sZY`Dw=*Nct&%fVo=@jrgOX~G6`l*7*HZu0j34)f>i_WpZ_eRPr#kTk z+VRVJ#rF!&X@YHh zoRwaCntEr9?LX{b`QZWP8`nl4_74rR)Z^x6&DRUA>e)-1GTn#h=glH#r^YjNW?P*o z@yPa%-GHW(+~jys9+%tZ{O!E*-_w)6?J1EroWU+GSUWpKH4S5i)kq+ z)>kiQc$s;SlGNF7Hx|CWRnBxfIhxk_WE=D@HkOP>=eM*b3y&;QcQ9=XcbL!CjxnCk zadrI9mX1(_&gd5FC(qf*@mPcZ!|)T$7-+_8`3>79@1}bX8Cf2QcD&yqENh30{*7i6yQnQ7&DG|3n@!rKp%AvfC z{(t@&puUeKCBfuU%%c~6Oc`_9Sc;1qJ7<5-rB=|B=>B%dsaNsL|L2Yr=kPsy@sfr4 zQWA~O>F&zUpFU|Rz41hL8Zeb*0JH6=hr&xr|2exHrb6_e4=UcVYo;H%#; zef@2Py)XZ6pF2B@eJShvZgy)X3Ld}m$zs8Zq(GX#ALHGkC1i5C>`K<_6k5IXDrb_Y*!J$iexkJb4ox z5=6gUWA^u#cH9P(VV(Z#JEuMUl&jpA!LnoDRCsk8m@%wX>hs+tQrBwirhhz%r#nh5 zPj3u`2TK{WYcfNXb83*R%+QptZG^)Hx(wK``nK$Al7Z{OiAp&TW2(uKxhRGc*}-u2 ziJ)VOlI^DFLH^b4QsZUo^JMdP%KT1nnca$&mEHG+a5wG#gKq)N=5Kwk3Zv5zPYyhV zlegZ>CvHiDKgiSdoLdDq@>X9#uf@v`zp-F&ZnZ2xG_FL+C2Q@ zG_BcL1+OM*jf4to(vHQ!kaeUI{W~%=yMBv2iam(P-bj+s(742j+jXO1{S}s_!!vCX ztGa1?AIKsCH1pnqVMyzRqy&LU(Of(XWdPBa6}*aYca_BU^g6f4eJ{qlIX6F&`^$mA z@RG2+(Lq_wu$e$m>~rF!sY9IliA?d^l&pa9n=D1qtJ~r!4`Pbw_@OYSp~$W0L9wyA zZW+F6FAE&Pucet11pnm+)*yfeQOK!LYNV65FBeh%H9y>}o?-lov*)ZWElsi~S^jXU zL=nq=4V0r+(!A(t887|Y_wehWkI8#x4YEeG~G9Yc3_4$?7nH%NEakl%Pe-+LVU5A1#Ib*(sKEj?;n zbek)hNB-u*Y2@1i?)D1O(r(ULFXP&qX0PPEBengGx6`ag(QUU#kebYn`mR_qTB zBd~4dnV!G>wY9cVF3`H`r@e?7IN2bm{<+|%R(KUj;Dt5v-t;UWekH7NLAYf-{Pa1p zQo;3Ia@B{RX#F$ok78vjQo40Bwr-i_l^$%zY=&PBZ{kVL$%NV&K6E3G>(dmdFj> z)TpeA+yOzTuHc~~v2_Jzp#5>*=#{R{oXwcmGnW z;u05^0>o>NS}gJ_9&&YP2I=0Tyi|sAkVdWNdwjgVyPYdg2iw0@Z2F0bd%I~Y9iDv< zc9fNsO;!$P1mvdpo^jpIh89jXc1OoV4M!u44Cyq0-?WVAJ23|3LELE5j~THZ+RFyB zem>U|@*f&;q*2@6htK1ROi_;Eh#gMiE$LzLNgP)h7iXOWRIW~d_>NX^XKtK@E@4m8 zUHG^bFKiDf#*-$RFPu58X6%YwZdjG}=N+PsDEh71-2L0I%sfDSpG5 z4IV+w!dp)iwvudlW|e+&HFzc+tVh=}&mm3jL~JW@o|p?y-YgMv_E+NhIJ494iE?u~ zW@Rw{Ookm_V3;@2&!7j4-~YaRHChzCJt6_pf}wgq)beV?{X>^E<$vks;$NJ=k2I~l zitPb-E;sJ3a$hT`DLYFf4!4Hp&nZc2tImBN0o)IjP?E{#&XS!KB8%d4QWz=oc6H&? z(s#X_A=as(d??Ge;^In)pzZK$r}$rvni6SM7<*X^u1%;nhq`IuK3V>UHE{>_q*h5I z>>PJxMPQxLG}n}&p^G?m+XrwKF-JP~D(|Dc^`6gmYF9lsECx=j4=dreF!_lX2+pW3 z$v5nhPW@I8vkM*z!)%mvIGPE6nW$}XfXZ3C;y(VKS{qHIY~dWx@&;#r+sz&4@p6m9 z7E`O_7c^U_2JmC%ExjK0xG(MdOgf#3{F}I*3X`HgQa3#QM@2A*=E)W^@d~u*_oX(9 zr>1YHBL{sghp|W6gC1jmt~Au3ZUkHZyj>e0U^vF?jq?QMGkYpZ`6Xe3dGzfoeLn^MDlbf}mAB^S)s_4NJt z0=PNC5WlS|0e-JY8fzPFQ~D4YB1fS4S#wpiwrOWDM~Ngse8;lJ%^&RSby7RoaP$XZpm9xGzJ^c_BC;KZP zD_Xb#7x9kxHEo{nJg!uzSq%#=<{>!bK74^&C0>AiLsd<2YZs!^UF#^gOo$rIYzC$? znDS_?KKzmo9o0h#VZ(eT*QY2>#Ne!cDBJZw3SY$4bGjfiN;4mTTl64OROmt3q=*eK2`L`@qCyZifShS6Wv zE?TheR-K99(*7IK`TRMA0XkkcYO_R_^g!rvQ>J(vTHWuv{#J@dM3A8Q%N+Mv`&D?u z1^?@T91zGj8a4^kyJsb8>t^~V%8C;pVyM#RB3CTmUXZ?^_%4`~SR%Y`*NTI^6V31yq_C#saqfn*F_qDAk1;cPl-;T^EwLAhT0vV-mYkE8s4}?D* zlD^GQbCy=jx3L3D@8YJ=uD@@8EVM$Ou+N^xX73hv8*^KY92~x!$a)9-q0R(hEod{N zp?fSolCpYDtH=Ras|f`}s0jPqRS^qy1VLxH+yMo6CBdv>I1D9t{sZ}O?yD)~K;cfd ztv#ol+RpIWRYltt%1!kZ0yL`Q3Gb-9V9q$eK)>e&B9_;{qw%MV*1NTk%%A@ymR5XN z&=4*>$nin9yX;z?eu_V9TCO#{)HhT$#8dP#y+p8m>C(!f z*{uNj90Pq8H*3Wxi?mL68k39Xzs%+wbe^=ezasRFbZLIOeJkESsX5?!?o`eTs;T?1&%T z5k_zDcUwged|jtc9FER*@_s6fjdyWr`92~^qB#a=29UnVnRjh?g?r>HSK44-3AwM0 zSSn{87YEPn+hhw=n%$@n?S7UuEFE6V5AyP7UAM3*9(7;%Y%?yuW<`QnwN>cY|4Gt? zL2Qk!>|w76^KOBqX3?wlY_*Y*fA4fH*;&&KZpa4rG&8I6*CiLdgw1q4CdeuOrurdb zzYNbfUz>aVNW+cVBkP+>lFWzRqNT~VbNeKQDqA>dt%G7Z|6^@jAGg!5~Rt3-XYq?Iw z9*5>(M?zc3=Uc=a^bCUNueJ5VUypGb@WRhrZTe7wn2q4oJlcr_!@$K1gK!|qGy_@>;btPXDjsGB9uTbhQh7{jZCtnB7hbGP02+JNy& zcE3hGJ%c#y8n06+io^s=0`j88E+ss2W0u{Xl$&gbP@h@;y|*Xqy{qaX7e1dM%rU@r zd0faiijMjv-w!@XdV9Y<+O!@NUX0XKu74%aQ3xOusjG6E<;bX@uV6%!)+8!SBl#n# zu^}e7xR+;Ng5W-olrZ3+vIFKl-|*dP*Kn zH(d6wHINX4VujOpC9#v2`6ylr#kO5@0rQMeRWeYtU$2CVmC>si$fof! z+=DM4Egl!k0R(R)o{6gx_GCR)rGf?+E!uwn>;5h2{*m%9z1aH+{U$ntBQMru6WFY3 z`@_#;1#`}CVZt8%hGEO(kNv_|jxYB0gXsn#Lk1F6B4llwxPqP0VPL$+cWS2kCMlK` zt>$_Crwd!x@+XP9fcWjQHD9=I*LrgIL;YP zs@{(dx$f~@>07FdU+b~2PBT8C8fYnO)nB^mn% z2l&uKO(*Tz{BF=cKvf8q%3tVyU)2uRMZJJv6frrhgiP@xj+m38#V=<>|Ja3~8Is4# z&tlQUt1MoBpLAWF=9KBeo%+!T_wd!~Q`x^i=^eXQYmU?iy_(L{s-=Ofx@r98PesXb ziJGYB4dMyT)ncDvn-h*vT0^}9!;{YeV)qSR-am(drvXv5p)NKKb0@OL78g^7)2d@i zBQJA!>WJ-;+P9pda>B0@pE_0z{SVmT?}HR~+?I%q0VSc*)WwuF)@DWN9FJHm~cJEUQH2!Y2=?P;zjO0Ha8rQ%34~}N$)JEv!B`;>5gYmDYGL4S0B{RWuXGcfJ zSqpSGov?M6Xw>(p4i4MREz^=JO54?1Mfg>1ciyTKR_hkmQR|%^H?{&)ffy|XQPWt9 zutlv^TqAjv(Z*G=PlroSq@%z}sc#3BUd$8mJ#IE;Vk4}E1a?Emeg^N*C%7JEDQ_Dl zB3Rt-@Wuy>3lb^q0A??|<>TvfVnwKh1{g8&9XfT@;58BxF^}ah(KWsmpuM$_mmQP2 z@Tp}4XkjHFcifZx1b=h-lSLOAVyZY(a|Ibze=m|E-PiLg!n16KMzNL^B0-?ors%Re zJ;-%)usFTOrvV4EeHyQ1#Rk(9#`S(Feikw6^yulUuK}HzVLme#CT*sdBJnan z9%UwoCd3m$^!|>a39uwnt4P>}a%5y#J?0kfQz&%>C0HP^om5yU#q@}u2_KYlv+-@G z<4d+fydVq&&40k^ZajRe-T|{u`YSzM2#)b?x5c{Ds6KNNJQR62o9dOt-ibdc$l0%x z%Ydy4Zo!Cs&VYc#clTs>c4afccWdr#`>2R?unI^e^iSI05#6+Px^BBlSngDSg+&Cw$q1`R*HEHUymT#Gdm_7wXwYEl`h>Bt`3q%++&UYx0whM+BZyQ zd~)86D268p9^?-v)=D2F;8O6*9BRgna zmRr^aApo{^T^u}f2cz)R6T+SRPV#KiBN$#5M06nrKfvMtx{hTI<$03Vij}#Hm?9hs zW=DGcaop|J_e8c3p zT?=`6T{|J69#~sh5hXx#60v@$7JO-i0Otq}B?}Y};U!_ULv&?TNwpJRVU_@gaIdqy zf`F$tYjPY@SVIZGkWS*K`wTb3zJV5D93Z3V*4~WtTyxw~dM|NoyVKO3XmeQ?>vFfv z@OG;0&ro2b@rkk4dXcJ?@MnI`nkJl@1^4YYoJ6_{m&}fS#n>(5{PsE5^KzN|QV2Au zzkeu)Nfo)*By`runhaXZd?w*o#mH~K9E{3$8*@DH*S^{w8Cl{mh>NE9 z*dn((iEMvJuTRJE9iO;YlXW)(n@vN@QVZ5nv#Z>|ZowDI+w@@1!It+JF%AnKzfDvv zGNciN4L>YpI~7XxdoV@tuSS%%eZw=_VaENWum!hJA4~<+g5L%BOD z#WrIC!p!P#d5UQO@nh}+Dk0t^!9S*2+BPc@Vqt3x;(HQ=etYvP;n-n=^!$Oph?|v- zI$h5Bdw~Eig3rr)%*qiH@L`m<)h<5)c?_4+j<+7V!izVm!4m3uF3eT^@*2#8(Agj4 z7l@a$5$61J>-IK>Psjm(LwMQ9qSysg4eD^;C3-D!GvIwv`2?40>>UfT2z9GqeTxpI zvA-(VMiCny|Ft_E@m993^z~zKe+FIKQ>weo5~nqOApcumT+wX$kmuyiryXxrHny9( z_F5Y?1I>g~&ZoO~@7|qpJM2&Nu@%;wn&#!@L6Sgs3pI`=6XZw}vRrr9Ce?8>AEt8= zL+oM9jz0 zF5c}{CdB<4jkBY`AEF1?%I!??Q|YH1?ZA8nDHTH?=LRXWnJ|B^%@;2plv+WxG<5Pg zuPYvHid5iCJ%L0%Fy2z8BmeeABYn@6vC<|gZ8&YT&>~`AEHR>(9wKe9PK}4X7E%9h zyJYCiZSK_e8`nZC#3V_!Vzje39b@94p-VRz+v$DO@S4(pcT2qkqf(^i$t+QeD@IVVRBI;HZT;k% z8#nq}-USB8EK_q68eE*|ouG%25i{IZ<^?25dB>E!bUwLNR=e`b(Rors8JjQuB^57* z5N6i*G+yeWbw5wODBW@t;$Jz`3mkJG9K}HT$w}S%Ut2#`X;WMfTuBV4VR&{#Q8f&F z|6HyN!86f}xevIH?2$9QqGg>}ESJT58t98iPVndHnH$N(&9xOi^+b<9f3&b4WX#zk zzmW__sACVBF|oKXa|gZmPR+UCKsJ2#MR7zRfmC9TFnWj+y^Fz@jJqX0@X}K8;&;{E zQFT?W_==x>&w+U!1NO#XetEJ4UIvNW4e@)QT6p^FVLyx_?fb7L^AmS%Zy!0hBza6WifAcgr$AOU@fjgm>Dt*P@t!$(67pSSJpev?R>p1Z3NttGml89 zfXoZawl&$5v1d@;)>+jPITmj{iH(sl2{iv`7!jlP8v8g~b?r)9v>s$F080G)&SAgv*bEX5Z7K z%2T};KRv)FQtAZh&JqiKt$;M;e*-G6ox9q}nYV=HzfuZO=3P;a^QW7V*0MidEuR}1 z)iTqOh!puq22EwpofTasHkIRp_%UC!tWgf(5zFj2c2Z(P_A+2Tt*rn)4z5ppCL#~o zjC}LSggjVsA2T|y#)=Dmh^c_imvs;wKGt|Y92D6I-@G{czGgMB`mPbCtQ!(Nx7oB` z^s`t<7`)s3SQRr+gY%>@hEh(FE8pqUQMZGPhdiSM&x~0teNA=Didii8@@2{c{Ffd6 z9}ff^$*OemiM`a|WDSML)m`(7yx(8=D? z5G*iTsq^V;E@h?YOz)!0vYUcU{REcyV8WU}p>;GzmyF*yZxoqV&pFm$8tDdJYld~W zN$Ih9vZYm9rATRojDyI>D;h+kYEV@O^e;i`O5tIfD&^Hd#i1Rf7SV%{BPTFL*L9-(S$(P zr3~}D5%I%H`Gbzzg}UehPjXLt-cBf>IfCdTjA=bSKGWGm(4D=ri26y^-u9G& zC-0{&uSLf_Y-_|#c)hBiXW%OtCkMWMGTniU1DpR)4sd=M+p$ly-%&VtQsg)x^i&Kc zwq>yNQ|E@Wk~@wj=5;$+Sy=&{@0SP8fOM_KqG4KX;QXh*{eAgSJ&<1~vEB21`AmE_PI3I;4E~htCAGyFEpY766JLK>*$?&_elER+03h`CnBx*ijXK2T{$Xk;MIAuqV?%&ipVyO8f`P#61I0l z^O3q;GLgK!J+zakt?ylWekk_9gUHl(>)ZG`*)cYTc&Vlm68Y)mYQEW!3`xd_^oL(w z%}ec+?d;hzRF08=qTwwhKNz^G^A3i)(-m^jBe|-B>iLSXv3 z4{HBvo+V3o7PdLeD2v=&(Vd~hxE}w-%k~>|ss;tm=f<)iE`uiZs&DhSrDtA?MKrb4 zj4o8S6~(ua<`$+D>lBSADlb~xzg}wI%xiN#ds%#bypx;7a<1#XY^BRi{ryKrGaT7> zxkf~m!Cq>yQStMM`}wExq>X=^I2^bKQbrj41%|XHP*;?mmm%h3h{IqB@vAbz^Pda# zS~=5^rC;sRFlJ!ac&FYwa9NXA4k6djte)usx*JeCEp)Ur`-6~wZ}{}=M~ORa-Y7mK{V5lRgxL-VUXoYl5zil7z)`A}Jm-CXB|TgzF%?ix{zCqc zWq==zd_m{~f^9Kx);SOQTG8Eb5@Pe+y*1iDt$gVwyDUH!uU}{lM+%-K>`a|?>g2kZ ztHyTH(|dx5cIG(D1~$~IEIbB~LL|RK(09`vt&cv@Wy_qaf%7Y*%Ux*2RG@4~0!z1! zI(|GRX3N{}TEgn%3w?bEG2JQdFMnC{d6$|WFq!;(ICt66Wt$@^+yu#qcZ7h4|M0y1 z>VBpTznZ#rL8^l}pczLo08k?KwzXtUhJbp`JmfaV_L`K&)~h`qT}CMb(Q$%Ed@v|&Tw!rpsua@C_W|jN%q$PZYrA~#7A@bDU}j<(+KdUVckS_EN%qo z{TDZ~X6rbmVCt&selMA7EA#wRXjnYa&WwfG!AE9$S_v>BzrqkI{j%y<|LNBFfOV_V z4!P>CjIDyqF8wq6gospe!>-rb%w;lUVcwsd<;$fZ5gwD^)kMQ2)mr}G?>b-XmBB&Y z(4c$4MvQy>6v|$(gE}i+&eZJ|mPV?`zY@*&Ea~-+eA;A~ zb!(txUEYNg&SoH1H&+mIjQyMIRx!L7r30v1BX`$;XIWo6zy~ zcKf}6EPN@JrvpUzrL+YWsk^E}(?Gza>v}&-1g|5Ok%TmC=YOR7bp)2H*;m4wHk)D( zU@fe*%Pv_@r9ieyfhNuvtcBI(GBvgM!pfRzlB=Uafr?}z0xx{4d?(9MYEaQATGU`e zu~apIN|b{k&|pfS5yTwD3RH=908v6?JS48&Ss}CV_Pzygqj?lz_DHH6Q5<7^Dc*JK zC9eCjOhq@Z5Hxj>c;T5ydbeXznYd)Z8=h2_Tu_iAB86@MUfEx+UK?z$-|~|x**EPt zU>Q7SWe?As*;QN2mK#(U&mYkn=}<)2h_p}i_q3>VrtoQ~3^THb$7&ko`a*-uk`gxu zo}rNVOK3t}B#G>;W$HE1tknA+36ZMzxjRqn8$G?A5+j{3bdaBczg>qDL&qMYxZB-1 zuy2x@s=S(x&XBjPTtt2>#axHdhYV{oY>6UK^)vw(kE^BNa6UA1%YYWH?>$Iek^FtS zVVD95tS8j+hbkG++}sWr+qoUe`NQ%nZMcP|2#^@n{=+MaG_Usy)zzdSG93sYdBa2_iOrU9p+{~4N6RYr^NsJ+Iwy#Dv@8E!GreXu4D-u*qk238HxG=t{lSel`s?1ysQvZ_>s&@88h8 z>YWXnKuP83o>fXD;Zg^TK+bOg44kB&%!k%j3__Tz1{32fEch$x01a;r%q`G0tIy4+ zFAf%-ckxVCe;B^9^@!YE`inT9+_oQ8*t4HOYr@>G7i-(Tw>2h%^h-dAY;9^rJn8Q= zm}qu$8eYSLyl5r1ao&UuiFeS*&-P0v5cD#tAJ)W1O^o*Lvk9B%Jd zqsAFkqKDA6zA0fLfj$b4VvX+V$aKRTni4sZrj;Rjxvh+vSm{j@L$iykvo~j9P6MIN z?A2oqR!D*8flQ}qwjF5Znfy&fuMU0ZeI_Mq?qLNr`3UA3%C3rVY*sov>ujYX&y-+m zTqHk@oY73KBRb=uhBr{;zu^-3j`~LXfh!%$>dzS59Ft=e+bt)>E__|z8x?Ai+&BsL zQ4#n=ZKK>~L&WMBH^2M(c6WmQLev0Ifk_?gti!83Jhc~c0KUh2D`1|X96pZmxVQa>~;1ESu8XqERMM%WdBvK!1OR?{JDU|KpC;F7@L3H_f*tqgQGA2xqZ* zRCRQHMDACo7FB@Oeql^+$7!}BA{Ari>#w7@O&#IWimP9nJ? z$MVOg4e&|C*%oDkW5Xu~0r>H61?yQ7Ab9yUCw?6S+|-qkWrqyMKSp=&f(*x=v<)P z$C!1^nzQ~-R)%Y2RX%^*(2J?s-P{f7Rx`;@a8fgc(tz$6S%=?hL0*~j?|_hjibepi zafu4wYh(Jdud2+JBnR=7x9cT6iKo(H*8zKseg^|5V7^D z5APUe6ybBCgH{luWd+@4MUsDSn7$&Z1;+&6-vp-JLXcUk33u)*n1)Q>Rs!W?mDFNYAZq}st( z3a#g{2NfJ6>O2V?WeD{*;WbyDnNP#XB+(6hs+jdYagwM3Ylm;~s)1BJuU<;i#2zOF*xKxe8hT&nj-!a^-t);&2qmh z9|!GTtPc6wm0C)UzNR4&)(*W-If^_tKK@YSNW|2vwCJxXpv!_Wp=w=IKTioJXkXeh zXOU`pX}=j7jharEh@OVP5SvBr)#9iIDP}G!OtF*sLDcJ8G!bk5>3X2IzkS?o*hct1 zzk8R>=EA{q0nrhM@3-S<5M8P=^h_{~KlJbg1uy*yZcE&1;Y~yi=g|Btds5x*d^?1(jf1XSVC}4yls_|q~-j5;Qdiwl_aCD#%tfRaJd;uk2Y#H zRJ;}A-)tW#Sz0LyKsnvL`74n*e6grkxgASLiDE-tO@l4;l6<-Upo|hv;e=|vmkg<) z?KQBXH1B-YNb zZ4Mt9kQ0y=R(6$=l-PY^W(C@T@$e;K{5y)~aZ8Vhm@U2HyzWTy%I+Z^t?Ij8i#~<3 zS4w%cR9VZ)mvs9*_`%(lCORqXpc``&daDP8OS|ro)c?~4l+0+w4d4$vWqdUqb&4RCC zh?9>1^~uRr#aDe$iNg_A!~XOva$iN;$2bj$WG?J}jrHK$TndR^j$$tb(#USI9u2m2 zWes*5`>>SNjYMk)Y=iEEU-jh|Z5B;L1{TnpjRdX0&b0 zvfu>IpBqBZ3K~^2yNeN;C3MaC{yz-SjivQ-!QR@IU*cG=F^JW>o`W}EUR%6l@QxBg zUw`6jmVPOd$8roYo;gV%D-!2l;U9^)l;ck+OlBQ?bB2=GY>nb25{=V#wC@6`Vh-Ld zBz_UTR5m{=qK|}Yk2NK9%2s&~W5RQlQsI34sDX0x*1 zNOj92z3loP5Q=?DlYDKeI6#x-qR;Pms9AVFkYsBU!U6?H1n~KJY71e0424;~ym>2u z?FO9~95$o`Vm=3scidSFbhHd8w>BfqF59%o}4>{>X4=E^@>y zF4E>hX@~e36Q2wCdqx>m#J#aFCfpvI-|GbElXtwITWKS%LC$md(_Ph^nEXGmgjoTK z#GWUwWoJGIs}D&-w#2QO8byR8F^8*rE`zH5c5S<;$(EnV+RuSNE#S>n3scDmM93U4@EmPGf$Cc#8_DDe)c~FD z*i7sb6*M|>VlPjch+nQY&6g|;(9~^jzm>6AB1oIXF4Z10EGAoWulFap~bR7Zdr`04na7yU$7UY`c=n4fO`-Nt+B zXWBU!G_Ycc8*u3MY!_Il*jb&i?zS?`vo1%l?!S}s2}T0wK>WsMJ<49bWiUrOk-iO+Ms(0SW?Q}6Q;7)7f=G?%mSvKMi&vwH2hGQv|opqFiR~i zoT5US&dH~#c{ox#36ig+HI54wQm->~*`SXWlX}ZU>sZ=}J+;YUAKp{Y|1ctui%o3& zGTI~PM%xVN1VnSXkCjR#_C*|SR?T$ndwt`S=Q6eoBcoVWA>mA~&lJZUcj{}RKcr%F zs{wX@#!bt}J{CPI@!J&td@9ilbb2F;yNuYNt&W>-1T9?p7?jD=F6alSo9)`#TNXT^ zj}dQ*U?1sGQk2=bMTqZX`FOeckb*4rK4!taoGm!^9`)_H2$g`Ii>}NGNpyg@Wc>Qf z69ZSp21pL+i=Aew)n`G)jR$;D9cWk9_C0iH7^SEmkZ3>YQiHCY0UC-P=uq5hI$7gQ zy?GCkb$+(e)L+*g-404F1K4yMK%f-8FexL5L*vpRpRrIvrF;iXQH}K2d z;X~Yhbfrn#i|nZ(kphRrNj$Fn(D;?#rN+!{t3INCJuyuzLIeg~eBhE}g$B5Js(Xfr zL-a}L>1K;&&V*97Gkci#HS%IeWKEFxG_hq`aC07?>~>_Po3>n}3AkS_-Z7QiRUR2D zZK7bdG$yDX5%HTo=H>(Av9?*wF*1o3E;Z zRAinm8uKl3> zO}QS9XFLqZYH^_*zKa+i8Ax`*w#VCY*_Z-*TDKA+dKY23A2}QnWa`Qj5$IGBk7N;P zj#Uvm>%fqQSFhv8>bC0tsL*8s*-40DE3mn#gX=P#g6S$L0nNd^xx`gNy+$t|1(ZB7 z)0#Qab5^lO;%|0nhRW28={ADfk(nF=%8&Y`7pzmQ?2$g2GptHP<2ZJw(u+tAai>w?G+y_ws%Q;M`D?{5aF ze}u#k&{rW-;1!sP8r8-@WHxrxW-1?OQ?jr@0u{YFDNTG5KC zlXd24MLA;&xTmG{4H?>5vI6;>+%UU=#6{~`bSidRwjO4i8(sd>DA2}Nk5A} zw)UZrDld@-?!{uSMFbCXO-=A00YNNINP89se3S~=Z8&|K1*zSg?e{~^c_!N4;|Z4H z!|-h>BVZ9L{PYWA2!{wFol<53gA1eSdp{ zTFFMUk|z=*+j)zpLp#AXjv9K@E=0A1!V`&+;2hvoc0I5O8FW@W3l@MNUn2`EP;TL8p93vMKk`2E5hZI&>{}D4{9Zb3v6un*U@S;xy zTBV<~O^i2ee^{g(6SXi$yi(!t43RlqD%)Dve{{&$5tT7!Xk{=4X5w%0cx9-z?5-^p^NBHO z=d7ciCO<5A2lScTte?(smUHjh&=sic{rcc*SVlkDnJ){}^a#)YoCFTs9g8;>Y4j>G zv=SVe)(IE-z^44f`n7K&!2i2ng(MM3kT&E}1?mB1IP4}Fw$w`UDKWUUXc8T>y5PEx zZ>D}De4X0$JwrluJoGy=VlyR}k@qv5k8JaNiU`hy)bVt5)S#n=3lxIhJLpU-$Vd}w zQDf=UCl_0fE+f0U2-Kp!BmiEoK=1Kfw=y9 zt!Y(_ZaZ#Ja+u^^+V)5HZIT=OskX?^B!9d#J(+~||53jy5Df!V303#s(fR?(-r7Fw z04=5%Fy!nnmw2qArc8yCXH|Rchu%s^CA2aH4&G|Zn|G6?_fFc>Ejv{Z?wbiLmo0{( zvMW>*?N)2ReADc#EfN0NFv?v~x%(R2u=|r*e}^YZ&NQT@wk|lcNx=9yV))}JOzDC@ ze!Nhwtk3SrgM4dxNA%cyr0XNobgqTW<0ay$NH8XRw8`u`n0zh={&~;Or7d#gWV9o zr~%9G7Qz-aWB2yY3bWbH;Oc}&JL>>3oz1mynCiEv@!4(`?ewj0(oX?UOy878tjEb< zrJh(DAu2UYqc~GZH3@NA!b3VWr0A}RwMuUy1A2yhQuwo~d0F{E;=wqb=sEWB@F-)k zMac*_<*|5>^qJaswEKEax zaa!H42~~B}*zJYBPQ7xjWv&_qG5GQCwu_|ksIk8ZaOFvII#?KCoy=BzgipT|I_%Kf zYQ^6A_6GlVD1X4>ru$TRyP>qbz)c*rUjcHK8A9%Y@CdA+KmRiTFC_g43HNcmWVefs zmVG0Q)PW2ii5IjD!s(}0@!(QtdNK@1jD8Q#b3aw5`Q0Tirz#Vg`fYGt7We*p0>-Q- zW;o{|Ao-|(=cCyr_o5JKYO!>iMZXraowae}lw#^P;XtH0s9X}`s0{db&+X7-9%Af3 zf2zPK`==weo$IHoaT`!Z#*FNZRH}g#C!`*S0xusf@^6Fol9@)|Hy8=aaCBODtf7G;~C4nIMZ0M#KJ9G^(*dx*r-}k(Xg6E zu(QDV##SZOLCEihdGa^S^mr}IwAI$^r_t+(p-?SiC@aR>*hjnml-EeGtR(ZFDg#kst#3G??FL{6DJRfxD8f z3m1)@j%^zq+qP|VY-`81JGRxaZQJVDHafZc{m#APj`I_$YRy%1KAN#Mc>C)iU{=(A z_10U9$E^U9{)FF?87Vc)?$b3?aaMVSjCzq%l|d)|kc{tO%0q+Qd7&a>eI}DvRpy*?ylVaF4GadkqLz z1Pi#aH`qbsAz~9BorMG%j?@M0Amh~08&0CZ3Zfw(J0>Z~()u&c-+9ZhUCF8mB|{=` z($~qs)NxhMyoiGeHs_ruUUkGLOLgB9<6HeP&Qn=#mJ6gK{dix84Wao0EC~Ip%99Qk zNKbKx9o(rIJ}iY-y?$+v)W&=v+n!aNni*9MZeJhI;q_3s^QC?Z%Tl0tUUyk@?Bn3+ zZ*OnENS{nraI)nVY=4-laOlSQ5WW<){hu*5nk@g$+P$_TDl^fy!fh4sOScPm5>s)o z@RExkl4XA~U9~rdae7wm*{7ma0t={lc`Cn@Wp-gNs(*M6%--Z4QyHj&v`6tzed8%@ zGB@-na>24d^FXKhN-n?h@D{X6q%7~nc4Z_G#QLar`FE#@V_Jbo_J0`ScUNNhn19VT znN%(kkBg3Q#^B@wK49qkoCtyj_JuV<7hZI}S&Hcu`xkVjn#c;Itvv;DI!I^1>QUVn z%TmnJCtmF(vA=n6tk~CEL{JRt1W{b)l@h(L{d~9DiSYohC;y%x)e%&X!B@oHz=H6y zt5tu;n`mpp4mJZRVrmr~0E?;ud0Fw_KRf!yPEFFVsYIgN(i}C_;>;&a)Ex?wNvOWM zJo#HRy<=SI$VUI;hvV500sk*Qyw#Q23DaY3m9;CZ+C8)bQxV#z#^No#WxFrbxenjB zRKCNv+G0RJ#ni5uQGJ*qOX5}|BAWWoEd0psHyBDLR0~&-;c~FcW%r#Kw`#j4YDM@F zqX($bA+AcDq3J~m&c2KFmKj3G@?^l#WZz|}jaZhY;%jY?74smo@)gqv=4 z0~5X2^F>Bq`v&d@!xXBtjDNFZ&5uW5#%}jMu7!L4+Ih*<&V7Yp7Upe7Sn1?J*<*Ur ziciolGLM}YiTgJj6;r&?3%80@YE##?+-@G}2`+F0XC%R+Vz7E_;nbP)&XI$4xRQ9*0*fjD6IBfZcAE&JNwz z99kKe%k!+T$l@s82M&Qul;ROK|b z{S|G16Su0~avvvhQSE`4rVJAnvolBa4xK!KuSmXD0cUFE3ZZi$S+Lc;`DlWftr-|g zNd*X7;j-1!$=*A>I44gTjzU_L@);8LHTEh=xj(UDmphitzs5 zy~r!b=Va`(I_$C`$;sHY=(t_@XnvRvmMtLGyKU0z zNCb3bHF1JXC%m?xl9Dz7r9#2B%bc55{;<=V8no{nlO|}mG2ZX5wD93_s-nr)w_xR9yicPDyZIAfGD1_=E|Mrd#L zr=lx}>D0|hg_7WfkDxaFdv9`V5`52s(~&yPC(U{gDW(LH8o8fD(kF60zEXCyy~wQo zNzeZcdS`RYU|1DRTO{{X1PfQuX|do!w(e=7_CuKFGqsnAlU$GXxjJ$6rD~${sA2&a zVIuSEjvZx8yk*4TYMg-`Sp#!t>F9`&8hOs=JQwa?cjs_D5Yw>RFJtS@N!oF)GdMTuAa3lyLjwOft3X=3lVy`d5KP`xm0l4Tp!lZHx2$){>l}gRyguUdE2f zX%;S#^WthDh|*$wWZ_w`kq$xN9tNBz{X>zY-sqE}aW8aL-2dXR=K5hyNepH7=_(wG zRva8Cecyu`(lf7^>zplLQxbg>|0-})+O*(i*CGuE)KRN2nZdF|1&mPSjCE%K* zH^HYFzjAkB8A%xx6w4ADc}21ukox#O+?xu!jSg-sl-)Wa*ST2 zsxnGqZRXj5P-1Mz=HFoHHa{*%VlTe(dN8PZv>m@2dEUP4dRK|-<4_~{1z9NinP2aW z(Z{Q!6@SyPGf3iDYK9+zhIR@pP<^6jql$^n;7>=#fYosLT>hM;ydyg@=EfvZH+&8O zvf#PS+w671^cm&Fnomr61=9_~5xtdxKv>bfpp3%c$9uQ#QajNp#o;s=R}VsVFcFEH zY0YVyYTt2GBvp2IXu zeC!a97UMd_V^=2G9!=kOWI7T61x+cZFI0E~q4ECc19ELhO?9FmyFKf1tQYZ$hl0%y zru0k7b=z~%Xd!$N*`ZTeP)ckxk%u3%>x-|797#esX6``JKkhJ#bCpUnB_y0!NYLb! zY~P}eXzhnDz8pcj1e+p;79xv4F#CX0QLohKPm_UT(0zG3Y}iJbM(-j-?7ic|bD5r@ zIV&(0`VN|73G^XTC(`E8X+RtELJX(kII(;IXuIRPTSj9<*RKE;Yp;@2YFI=B>Qc-K zk9ivi5$*cl&D(@la1eO>x8MpgJIr7au(&|5(76;yDL*YM!$1bUJ7kGgQjw&~2imistlOBhMsEyPZ-j+>bNVcQyV&Lh(O0_4L8)j@IZ_H3dfNTp4-y+&H5Vgmzwo|>jX zbN4;*93;B=6Mpb&NMd4ejNSsz#vb%_fUVqE0LE-^x9N#gw`Xm4r`Tf!A(tC+DqXQu z>07F3@)~ZViyPccig5;HgwJi2&a{e^3v(8cN0tAlFk@nX>1cQMRdpQ{qWJd;A#r5? zUrn5W0T><}W$cR9EUKtNsT4x#_V$N1-bM1KYYpL)xCPBH{h9jkEo%@Vjx_(#67xE8 zlGVbkC`F5O2gYRpfpKc*;QXc5(RQMb^9AA{cHALtz7)Hd#$BRA^zw|%VgejEOaX+i zEP-M3T8f|vxFCw1QoZ^eWWetdp9sA!0XqsKHf^hvql%dT$n@fh!PL#q4T0xC9!^o0 zewDlXNiJloiVzczN^`!FT{j%#E{U~)XVhv}8R>(Luj#KOKGNl6K*52qGt8A~ zS%uyQO9V~|o^hN0)Xs2a@;Ku+XU*(3G}v9Yx1}$BdMb8z{#Wn-TXo*Lp$1dyGA27i zYY>M9=IzBdPa_BvzOzMqHze!+?<|mlc|m~T9L6f9lO%iwA3K~JV4v9VAo1c)kauHD z;I%9rh-<5fLT7+xV=?}j5eO#8SfL&(UY~Q^K;&_W_XA!!2SNb=#?PyA@Td`y(-Zff z&hvt<@G2(|f9^NBN;~U%n#2Q$))Ft}Xd`411hGGja0k?3cxP(fE#i3D7?$v552MjV zEIpapJ2Q6Cj2Wl#?;m;2ZM9~Qzf`G~L80Yr172y!G*hYwIv4H%gI11|XZ7+cP&HJ|0 zA38W|Qu63+uq71IbaaI)D8rpH43%hMJgE@|uNiZaL`jUpSg`u+DW{F-M#GUhJ)V2%;bhKYk|)GBls; z7j9DYvaj$ZBwBZuFumZ=iL!nlR;8%ZRi?O&AFV2+ovf~`)H>+3IT|M?bZ1Ik`iP%n zfBGHZ?fzbM_dr@y*qF|i>(uXx2VM%KV9+EWI0z4|cX(0)Nx)S=$N5?#t2wLYs+oq% zWt#sYh3;Kc=SN#XB*$>*c3rZqb(!P65kqk#$@&{7K$Fp(LrQgKHRjPl#VRISMX^Z-WPC&tZf@`7RYu zeX4wFhhH}`K}9)wk}ssn1jLe3e848ad~@efp$8#(l!GbPHHUc$Wir)@mRoA;5n;~= zEQ)jbK@{6kONoT?L7Tdq?Ic=O*yLFIsOuB0Ra!tus%oVKXfPCGHceFDC2ZyC`k8Vq zjK_PMB~s%nnUysD+*wslacq$wwd8i+AI2`DBz>=#xW;LbP5y$A5Z3E~&BxQNFc4=etchoi z8)8_hK}ay043DCMuR$}ieq-cb*A^I~`x;J=#$u_#qx#wU_Tal4%m2}YC(+>tg>Mxu zM^@qSmMkb~Nt&)Emhuchg7%u7o!pBz*dcpZ;_h)5-RjKA z3PQqLH*0O~5mv=n=xf{$3{8gB2+i~jhR?en^>&=kI&Dt>)WqGefsKVzC}Qd2xa|3H zfuV7x9UBScpl==JJs-v%RBt6 zHTnqT5REkr?O5p_YK83F3wr6O9y6b{ZX<(^!Q{Vs`A+bNwHtV|Vw8I1XqXxGcCNL^)jZRAm{tD7z@ zTbap~URg5GCwp{4JXe!45u|pEArQ$3DINmtrbjp)i=#y~>!H?*P$CQ(dT zPnrk1Gc8Kkl53~ZCaUmwN$*Y+YYn6p5l?K`dc`T8HLQvP9d9d62Zz2w^9*!!-ruAy zq2nE5ev%@iaAAWQ6uvF5kpO+Hb&b+}NO30Besgw~y9!82@nS>;17iMSPyxOv_edZ^ z*u#tvMmtSagN{D%qWR!4gchb?wvAAUDlm|C`UeW=YZn+{GcDtB+ue|{d!e($w`iqH zfzpb4S=x(s7dj3|F+@njcI zSy7O~yJx2L1*7Cymr`k@7cs`#x(iqH5ZS0v$LogM82rF!h|(mn_*aS*?Zt{~#FJwP zj)zO)n+Qy5V@`?K!ClCiWYFayMt<-yNi5Ww`Qb?vGI^AekEI8QVGy}tHKKzpylCv$5Qtyv8#NIlEoy4wzU@$Nw+8Tqx(MI`9v}6E8tqcR&dfiPCTcXv- za461f6QahALJw^1Kq6z+2-;5ZvDr!lH-{Ok)Oj~;Pf~Kt0y-uS0-9Gd%X?RNm)5;R zn>u>(=Z6#AumiwWX#mybC5>tr2TEovpw`dV5=>}s9_+8#!)(TTf;F$~o6}oZz=3sn zM1ts42CfnlP6em0Z6;_gxfgU~0+Ezw+XxOg1OoC}0H|2j;}a_#O-K4ZYn$;7dg@jx zRzn5;UOa-MY%T@_8u_KyG`OJz)=p3|<{@FKI75`?^aT5va`ke!LQi3ohLh0lY~iLO=}Hp06IY ze32Si0C zBPzz(78}IMJU&}XLh=~9KB;uzKa4`8w9n{NJxT2DPwo}RvXxyWB#!1c!y317xm(9C z1`HEfK|tnLVZ!#RJ-rnA#)#)i23*b_q`Op@l(>$_k{7Vb$!XoFytJ*I=W%&D*}&?`cbS}CNXF(TQnk7g5nD5f{{9%p?TnxvOb z`i@rc6Qd_r-VokabB-1uNgCw`EUdJ-Q_@6eBO=E!AQi$YnuSe>A?u&?-!z>kdY#P% zl{u6^$}90*501OogNgo0{XWL`t_eE(+0r&0jfjh#+;ST- z(&qg8801IPtb@0R?^ zKyQ}x+9ld%9~aiMZfE}Pm(wDuH1%+jWC6XO3W;m+ZG=x}Tt(#mI-`nbT5C+e*5yT% zszV$iDjWp9nHcAI5s!Qz*s9S!snlo+^03lPS4TI z4||obKaKtMzZfY2W)S3&7~{emZJe|_f#K{!ltrsi^`O`V!_`Dobm%#W2%J(j!&5O^ zbU5jO*k7kWk2RRe#ey=HH*1s#R0#j}%aa|dx8LRA<}w=T$+1*?EkUAtGZ+%beHC_M zk7{z^ItM05V2h(nl5ozPz3Y5en80R`Cr5rhR|Js;!^dCQT>W3*_xM83P8k@pS5eX|aZrA1L4XxVqTlmr1KEw1VA zbiuabZx5gpUcyK&5|hY8=e(%pX0Z_4{UD1#!G80ApbjI#22_Gs(xqDG(cjrE|Ebu2 zC>>SMNYJem-eoU-BK0##{o}EEhat+YVhyp<94DkB-xA9d8r~u@S5T1P9YN=0;#y$l zx{&8`LT4(rTKs)n#tl-?iCN{=zs={fK^Z#%)8yGO+lxv@A%1dyG#P5By^$Y6wM^T& z@Dkb#BF1E@y(a=YNsSU3x)B{hN4SoQvs&^OMZkC?u*6vSQgZy&IS^+xN{6ek`}#G( zF_2wW?3(jzA;RP3T+E+W9^y{y~c%@LbotcnVF2i(nRsLk^$jbfa z)PVEVR%EAx?4q4lhP5hiq_bzQEc=E|Ei+uXbFK5b;ONc0RmSMmL@(-%=<_6-i<~=g zksBpJ95y=|N!fb73MnlB&R&qkSsxmW6Ayy`1A4r$ zy5_SWh>ox&HQ-*pMslkF0bBnwsm|~+2Fr*B07_$2%dZ!^G$o&|HFO(1Fx$N#ytcGu z7uTs7jCH)D-efSN7M*|;WOE|n7ctaO%wx844jr^(jdD*VgX7saFVl$n-E2((Ww=Y4 zIdvt0VJM*t#Da#>K|!^(Ka25QKeEoWWa}N_s2M=f{vM!J%43fAF7(5R?$e`EnL^;0 z*Z`IY*-Pi}tM9*3DLBgyZ{|gmjNlb3IsO!|Lg++NOH(y9wV#ultSf0$&oQVdo+o%q zaal+=-N`}0l=zVC+0O-Q9gcQM4d;NuI#Ac$7u{2XRK5bLc!$q4hN(c8bNMx2$LU^l z*>L5Q?kM!JGN=fp_!Bp?J?Kc*%-~db z6`3kPrF(y7x%efG$&R7OBX3oQJ)v8K*582Sj_h@39fKYFg`O=+RvJne24Mq*=`)K|i zjHt{Y+l}>0b6Gl=@mFE|MEj!w^1meVxIt2s!pp!Vu;jA{_-#JvF71`V=d;+pE(w9h zrbVa#qrUF$Cs@k7lr@QHSBGK)0rrjj0{Hg8$S!I7D}gn!!*&jHN0f`8R7i9z2|dDJ z*N00>aV4U)j%wL~Kj=W*52e@DgTOS34Gh43a!R*9EDp`?H_l@zTey&c%8K?j{9%E$ zJes|tniUO}=_*m&9s{9xO%@2etMaWPv9jW^6{Xal0^IRWW`s&M%42bi_V6XE%UZqF zCh1wLRmr+&H$1)`SX>{SM?n;>LkEhWe)vuBqEaW7SRHF%SRl6 zn6`Mnp~x=H>SJJZ#|=++_A}(Gt$mL-GJ?HaM*(k0KoUyZ}p5S=EQgeEgMRp4!$j;F!U<= zl*|8?@zO1<>XbB$2puqx*^gsX!s5bi!Q=pXeHn8zIZ-_1XkNwHzDIvdK)zB*+4Pjd zxwItS1#6yK#vIs{1boXXFG=26=Ox0R4HAc>|2M<>y38$B4w6=?r=O(^(8s?w!PjD( znb%3`4CbQ4-7epLYr@!?I3Z0^W%a`+eg`U(Z-w6;y^6X%v!K~E`iR{JIvomu_G#9% znX&CIWcrkx%XS+$Qh&3#qv@^>5XPS@!9*D=`(DB(H*<{}O}7(-MItf_P-I*AAyWL# zK58G+)Hf8WgPO21;~j)goh2Jn+Y%LBt{qLjCj(?y966(}n@nJdYcJ7y;f|z_xQ2?r ziYar&hutiX+6DbnrfI><; zBWqSTe5Av?Xksj3f##lgI)ON=)>7PSCRggxQ-ki&R=x2sP3j9V@ssx)Dod>zBqG0Z ztC%R9I{w{&lqYWKPDV*-mX@HdpHZrC*cgA@LrPvZ|W;VzP3WMNG!4F}4-Fvm&ifY3z2l90KaCNqkW$VR}_ zHX_6;9v_a?NS1nUNkQ>Id4OylTS|lYAv<`A@+2KX77A#!OYzP^5|U2Qa5c`6nGo^X zsc>|0=Or2rE#gprp+`qE_XQ@>28=ulV?}o@BHl#hJzK*2g_AJ~x9+64MJQ*{t(e>b zIk|4}R|+Z&f>2bcF+BHX0OX;5O13vM0}LmeFaSmpN_=6PqBn>9xuM0Ejy<02989N^ z5Sr$r%<3(d@g_xPxn#oq>G~M$7G5R&yJ=thCedQC>JT%prp}SNRKXT(1ZZp<>D~S!j}ESBI7ht^zB=%F{c*g zKrX-vkN~xJRP8PK)*l;5J%-E*iwS?;oqq>X0oiE9LiKC$FcKP-SIIDZOY}bR*a6Av z*3sa(%|6iVgjrt`GAUod*ISl|&X%QxcGB$}}@NvOq?|>CwZEzfAsw znaW4&*Ls_S=gDjXR2zmBz)T97vur(@s(9iZ&dDdFi4MeS!%K(t^vz!;spUbDy`Gq?Jg@oLGNj|-!sH%!VZ)%Ot|5U}@jIQ14>Ufmy5+ln zuMr|i{yCMnwD|Swa@sYd+;r|gjA;x8LPYS#!{FN`rkA}(^6j||)OFw?R_LuVC6wj! zWm0}!{Kepp_Mh=qkJH|oU*$GXjh;JwHkkoYNjWb=}K{yH(g!xNDt z&q<?eQt^qb0)7m^t;d}O@yUnnr)uQ zVx}Q^IjeDpgdHZ^G!%+z)^NfW+SQwCn^q(Kj5B&Tpvh~bicZl9(KjG4*3g5yAb+mC z%3#(5ND*1JAI+13xGDZkMMIQitN*hdG( z0UE<69T~O%Q({OILA zeYL{5Q~bswnnJlv3~NHyZMCN0Ae*|J?Of1t;H?wO&{gg&nH+3^Tu3A(Uj1r=3aCsGhpXFDq0cGN?q}Tnu7|=T1E!%f#sA7 zCtp^XTn$S;HVkIqx{?Rf8nHklCa&uWn2cMe z(atlHI(EUt@D|*J{n&jY_~G@p2DTl^`a{$w(tUCY`H}`6dSom&1VQStf@wF^H4g24 z6Zj!MvFg9xD=?uvxXL}$T~w0jq`*=KAnp3G9AA0mDU(|*Lun&*M18mm1kulAA@ z_LhK-k>bQ_BybEa46C2c6G)_B|#}Y=&7FxWE zS_);oN9+H^e2sBhKPG$xgzk-lfE#HAOx4gTdTljzeqrwl_^f z-o|I4&AmPUdYa?h?xf2PMlgO=M#Sx>xyI86gEFl}2K)ko2t(qhOtd@y!)9JbJ+Nsi z4z#a&a>Y{Ckf0y1ZN&14Ac!N%rA0BRa;TI1F<%ahJl6gY;FAlzd5JUi<%KRkCesuM)y^+5WT?Y@tct^2%BdBWB_+w0tJsZ{jIf zbU@|yM`~wIp8Q!foQo1j6QP2MlRoAIEL3Wag86^Y-Lh>r3kOoMrs+BMfD*$Eo_x7-vg{*8DuXm9LWvmJlE zv9%i(>CI-FI;vD%M*rPS5d&U8Z+!5{ztmzSF4le_-q&)}{@U#r$zd_B+b?JYrUAJ- z2%CEDvBG?Y{B#Qd>zp?=;0E>Agsf%kVy`1ptuG5|b29M?gE&NLiK8Ywg41e1*;pA% zPt+qFtM|^VW7}t-m0isYmQ{WEk6dDKfnedOJGD#W*G@p91X*M8U7OCEMJ*AFB8n31uP3R!Dg?xDn_#KOBSw7mR#!MVj6BX% zZ8*@@PLN=%AqMDQjKp3@cZvTIO9|Z`Ix3X*{02;zJ6OkP3vBxQdE2wBXV_tNJAv6XV}v&J5>WBi>z zYGKx}9v|UbgFP5X?J9isLSZdq*?y<2&D1yE)^4D0>34Nb)%2jFq59aUu1&g=lk8*A zhODEPBx~hwS^mxViAJZbdhFVR_m$5Dyjg6-4u>)TUzY5#xaeqj)?6!9knOC_5(mUt zwSRV5yj#S_f4uI68Ee#pRc_SpQnOjekp@Jb|qQ&FE>0|!T&3z7>8%)DUDn^eL}$HJ|hchkEpCGj1>0 zgx`uKcbZ>jbXSDgVu~DEAW1~G#I*o~h3RSS=M1YIG7%$6S2=QN)J^w9DkyX+IMghd z&jh-G96!-F#6%Xo{wiNPo^+A7Y%M4IA>ct@k?&9T3u;{%QlQkOi(Bo>=wyd^$p_ML zwIrZDZ%AOKp2*8>2!agE2YHQRZ&{N!;nNs3kUs+(ExK!>HBszIH)!MQx7nT4I?M(v z!l}?D_&8Cs*^5-@8kJ)whlGoL&w1i|wuGr$ZBSz;*pnPy*$y=OpWx@AtfU18g@Bn4 ztBOlOhSYy$L}E6Qv>yq+1i&>0(gF3%FgD9-PekM`arv|^O-5v&30}oD`|^kFR-^2o zZgG3iVMNIA|A#?o@g$r@jv?x>7jbt9>=zlggAuYIo3AZQNGJQpnM4O=TeU&w8=?9z zmsO*b$l`f`sy0+Uc#(Vcj2`|UfuefZGC}r37jg%(0Y9WK&D>Wy-_ifYSqOPVe9L(X5n zWI1J;FOL_dHe%D643?C1fw;{!@Q!BUd!+T0DD%%hKNUOJ`RE9WGh2NKyu5EJqq#>5$Xoiw!7OwH4u{o&#I-$0%*J1aMKW z40{wP&g?7Ke_>v4OT67q=R8ZCo&+mfiyZE&Yu=ItLN)r~{>h)ne46~aNze(p+apJZFm!gV;w#<+RPoRvb=+`tu-+(i zFiP0n;k>m}t;CP9poBqCtRN}ovqMkDi_jt6kKnsgn5;j*fgI)ug8&7xW2zsk{6jUD zS!l)E#h-^kG>i*jP5`MS=&?(E-}4PRK(`BC#mHoXZl;F6Xx8xVZzgtoJ^zEx8chRZ z=}k+ZR*g(a&EzKc8hoN|^;Pu#-h~Gs0_}1>I^at7tgIu5xs1k&g0hAcC=}F`+T=z+ z42mpN40yyX9adW5lMXoyMm(R zBe>GvyAetOn4a`igWFmv?Jh^er?y=xczse}*tq>wXg_#z{kfz$XaT8y()a02FnNT* ziFnKLuW4i#)*Z|uIWc)89>X%u2SV=|jdq7SZ*vX^{;r=0a-hI;vv#sxz(xnY8aSUw z?gA|Xb*C=b?76fYHdKt_Pd&;M(PI4?3&2)>R9!ec7XRyjcPFJk0h7aFAocFENN8;X**lt%v%w!rblS ze=~ozw;)H@&jX#Bhdw>>XH!-ApJXV3xyY93w@^t)psoi`{Nk!Gio)iCim1VCT&~%a z?fE_}i>lwTUT-O#FmCc5E$uBqw3XaSWYW(~_GMU5cb2Uje~LP;)pjpPyERL^4H-m4 zPwx|QGi8%oW%culXqlCkC6oMh$^A!d0$gEvkZoXy#kSiDqm|8M>Eqm1@qtOMH~;nq zVSaxO7AEYB-hvMxfidwx)+9h%O1F}9llFEY#(ri#ZuP#(>(p@{#WY1Y@n$lp*y2|9 zphiN^AiZ#|bM&C+>LwrJb+!oOVG%kYyS=u2*%JD%(Z*wcf`DBOA_^fV;z!Kkgf?_l zVjs#vs(hvN%=UWn(~S2{rZZsy%&me^)3ooE9a5A_W|J30xna({gj0*7OY(J*!;_3F0pboHQ|0AC*Q9--C&JLi}uMNln zqp2sx#j@el31VX8$b-yGdHo5}oaiJ>>m>jhCU9QlUee#kLa`$S6@$d%WrXlbq2aZK z`3*!Z3-E4$y+G~3lY)=Q@rIVh&Q@(%jgmM+|c;0kdbm+MuL->uv1;Wn16N6 zZDL8VCFh8hKk=kj>k>JR`qt9iK-|n1d>#%%YJmt{)Gkn$**EKCip;_80lO)_k%QZX z{9H@=?M%td`6xo+NjgPg{c5rccN`%A(fwqgj6Zhar&gNcpwaRHECHvdX`=wJq9`UZ zEVX;-3Cx|riSxYjAbWpS0vnp^&>CUbrtc&8JZo_T17=Gh9!4U}W&tx^+S?kH3SWll z^VP}N$M>AF8AE@H*viKN+DsH;X7(Y2y0~~4Ma&T&Cy2J^95EoUP8x&y(PJD7N(N>c z4g!%j+|I=ucqsY>N5AG0I{i=NepAy5Dam)t1SKX>;Jdkm3R_CRP5zB)TPciLDcQ2t z4ZAPsQ1#i0m2VTj@ts@RG|lU@$$yR?`@-begH!CW^?bbBj)3%^4R4&7CsQa_{|;eF zhrp;y=4V^vL=V-QhuAChUF}2h&a|?9lDDa?bta`PMp|49N%u>9w#VRbqMYRBK)w(H zlivFP(jST~%v6hck3A$hhvr|gEp58Aa!bJRV8P*BywHK4qw6PmSaQi&>-g{!L4x7s zU5(N;H>}HgeL6$KOF|6M5XzYbBevFDk)?f#bdPkD4AK|7oDn$m_0ExAt#!^jE7UH& zS^nEvd59kF1#Vxbfr3SCMZ#*!Le2$K@730oUY8j|W<;M93N?BC>YzOnNWzlO5kJqr zsJNRGz}7TiNI>RtwENlQixli1*&O3u=wm^HpxuHm&pW}1TQnBC;pM+U-%Ao2Q2?kw zmBlhUIIjWkA%s*;B9L%k$Eue6sewdazU7~o><-MWb{TNOh$hRFwJG6w~4 z!}wGu*3prK{ROZ+2WI||J`QyqteG88Jp`tJ_faHQI|?5zW|f=L0uGR^b)4OkZfNCj zI=pM=L2V{c)nO`#nC%*U=#N&6Yam=srCG7g>Rm+VK?)PL)Lpy-mC&^kpobnVeJS2w z0eGk+Ds1v}4GaS%L)w~@j@(iHTs=I{(NL^oSi*Jaxg8j^Zl4!}7=aI5!7}fFF3 zWsnZas=9_#-rzmJw4e~w^&vbV(A`=J+Z;%k4$)nv&L~2o9hI_Jx?f`nf_V;%0%$g9gFzc&&0`?cot9Ce^+1|llBw@@_lSQ?_zsC}=W4L>5 z)Bch?aj>*6$PU&DWQJiIX72Tb$nW@zNGOz`_Ry4TtZu%_N|HrUH(WjoUru(Fe zyTYY6LT63K*-#B=PE4zt(V$yBpA~R^Dq-V4_9Ghe0Cqyb{~b9&kL#`8n!@kOADH7= z)Q!MC6ZIpqa^m-TM4u3Het3gqCR1TZNGtV2vkxTg)P=oFkoavsl(QZL@I3*Q9i^7& z#zNr&9v@B7Lif=s;e(dpWkAq4<@j9iONj27c-v5-gNf!l3g$a%290hT3yZgk5N{OR zg>}5-$ou|1$*EbRpi_^;-K|NUer|e=Lxe^ae+Xmy>&ZaJL2oD0dLAzPCS%D!!1T3f z|HN?EzG*0cUEP<^PcSUVy9-e%`c#!4p$!5EB@5xU9&7%PTCSP+ADC=VHLiAnA%9z? z97NAhMG&!%HuQOJd;Vz`C4We4J_GG3J>E;pj*&g$G}Q_mXydb<0on z2y49c?SqFk&mVOMQC7#rNH*3Nlhh-^Bf}!b`@Fz|;jHTqR=-PI!Ep~@0=3ny2(go{PdJj9sb*RrbM zYZK(fp(0^yR~Aoi{Ce>d8$QLvNdgx(tkP<`^5jD25xI|Q)j}^#YZcG`!_-^GHTl2q z!<2%85(=n*q#!v21nH1YrF*0_6A(rZM5P-fCFFCdKzQyTv{r||Q&QaxIsYoaQavq7gYD;484HQ1VG;a{ARY#wv%tB|7uB=E&)(`7o5YR&9g}=4l58{%!B$p+ z#J-Ty8|LA_l>_SP1e19tGh{1#9<6aMdou=J9@3fwTzn{5Jn1bk zAvym2j>12zlLP-Iw(Q`|Pidu>nxpG8Uaq5yVJTw{!~^X z`eZ14a>6h-p{>~7kH7R2Xe4P!uYD_h$N61g+p8B4COoa+4p|Bxam;8dYQOY*Dc97= z^E9|sPIo&tL+iXizAL+X{_Eu3J$AE?)WD%6AxOFUcm{U1`KCsy0CYFJfzr=gzHsLq z$qc16dCyIaXo-Vvk!0~T93(!u|B#Qj8_!RLChi37bme#?0a(OMZeBNBt~hm8uk-DY zB4POu-28#dY(;xRo&v^#F~+cU!XvYupYFgaM!>WRuWLyoNGqkLZq zCbv{xqqt`Jmi+3s`1kx*CUIxEeA}61fU&H@43BCxqU+mw=ROTj9Y^jqRJgFI5s6ib z#VW?rwfGRf=pxRPUS(4)#{V>p?J=}j6}&N_{N_;e6I^H!O*lVQ?Ttxc?FFhIL=NSD zpV%s&*iO{T@YVETm3=}ULWB*LWyyTVy>%PzYNIlzUGAj6r)VRc&kxu&SP=1srEg$C zvj3IMNn?yee#x2omTNUt<({v!qeKYE4)1?XrkYe*7A-oQT;%#XK)Tyu-1&k;?)g}@ zYPta5YjDCgX~n(FgbJ~~mwOU!NXDDEBjzpOw0mLV^{hYxc94DG_#+<6uuvPFZ)1X{ zdJpAaSH1ZPiZfSWa35V9o;uu$hCu$^ z=$jo)@xqI|nDV(WS+a4Rqjy=k(scKQS(oSB{?@>UI&ps19eypZC;M9>8aOn-*&Y0H?pmp^6|T(?(1G4GmcaCSG2G-k)>fQ;&GCn3SsD`g z6)hs%u!rYVLe zLwrjoARZ)}(PMy;>|MtgyLx^;G(Wo%!Cg@( zQ8ElmiX_M61kdiun=Ez%^^mS_h6a-^%#2qQCsiPO;?a)p^w&ZEUf&22Elt(zP+X$! zZ9!D#?CIFMAc2zBba5X+15;B$icF0f)#w^K$56c%OYK(8@`W{2PH~>ytN88Q=IC#b zPkM8oSWcOkc&Ug8il4KXXG*N;Z(e4pZVpIe0dtWwNa+=%$Z}Lc1`3=$a;?FoGr#Zr zEX0K28k^%1n4SAzuK$_Lb(DcBbMoEMhW)dL5F@MG8GE%9(WOJsQPPXQ*YW%V>BF~m zwa-6$^)+dpUE$N!a`b<#OcP+AE{!Z>lI}|@Q{P>N4~XJxIe!%QN{Uz7;XWtx+&2{3 z?l#6I6o!;eQt@#I?^mJ+NHY@!CpA3``nf;Fb=uVISzyvhfnKk+h^f^6ZA2&$EpgQ> z@dY8l4B)`?6NNO5ZU(@(F-GjHpLt+^->|x=sra$h`}8o)>fZrrvRJWNcZPFcaO2}% z)`)AG&M7d*fO?PMscU+i)S38rG{+5Ylcxj^L742WsCmETi+>fb*!!Ahn^J9^IW#Nk z*KJdMu{UxulD~9`=GQw;+-1(3v;-dV>f2arW~WTtlyEvQTu+uP2R4h_BPmT?gLxGX z(Ts>z4W6*Sk{Gjr`)eYsb|yIjleMu<>?5OjcJIHfY-z{>A+J}$2PPw3$#>y-=gkdA zlpC7TOqIKiydBq!t|n@qJt=*Z=BYzLTqSAYZjlq~ge#bn;V6Cip?_A*g8UhK)vO94 zNM_$n#56;{`$w9KA`+i8ttu$*idU!fE!}jUVW~1D^c96mW=agBgidwU6B`K~-w;jW z=ofcUNKM@ruN2$aP>P6i{CkYOo>4?3__$b&fj1&4yS2~m^Km2?-Fr+S6_2MLO7JeQ6m8q zf~2Eo1^Xd92>1dM9tLB3C%^W_gWp^^J7|lyE^;+Zh{~_}-*^>_IPCR`S7)$08 zJ6U*d39xr1JB~x^;l;4z=2qhoqx+u1i}^anFZMD*sr}QgrswvGl`2YmKm_bSN1BIv z!9Wwj#w)Z$NfTr=^=4H3a_NTW5lj9?=r_JE3vYt>sFhMi zm65YY({Xi&xuda=Gq|Hhw?DSm40=x`jZBz&j1?KwNOEjdUKqtaJZd0%4spF|Q8mGSf ztKv=Sgr{GD0KuU|T(bwg3Qe7Q?OiDy6`?jTD$iOLxGtE7UQ&2M{)_;xg{9FxJaBXJ z6uew4oyYgwW%E@)K{e^~GkbLW$FI3td@+(14>my`N|stTBJ|~DAhpUbq-+;F&;e26 z$n~%(bRNx$kzA_3R;(%7ogC$}5S`K9%|N1w4%QfDX_#qII;;y$nIg^<%Y7Tv>@M7| zY-k@cc3f1szB7KXG2W9E25|XNJikcY+bqXgyhE8i2TxIK;`=DBlo~X`bV>0&s~!OJ z=aSlcNIv|B{-dWI-BNtTR!*L9_~6cZYXuCs^j-)%!kqG-1tBBnCHNr&zr@x50MDE` ztq~c~S3b3QO(fMSO!e&@y=jQVChasjM*B?PgtSo#Nw`* zVSS$lWc#sI^uYaqnB)bTp$=4OvnzBKMEH1)Ug@{J0|NrbemEBfh<-iAY3MCD(<9wy zIy>i;@5feGxV1)=cTVtNmYD=l7j)4a(psOWLOsMkV8P67f*P@bRo4Tb=NkRDA2@-5 z6h3VfUPw$))SXP99Nl;X8Lpz3&yt(jhea?2CZ#-$)PVFp^v~z5B2htZADaO}4WP(z z*L@P#_L%h~hHLCp|QjdY%o6l8yuQ` z+}TO96esD*TGIur(-l9@;iz!WuK4f4s*%+Tnp!_{)qTGSHFV8ivnLMX-w@(FTg;ga zvfBoG8G#t{WG%P+P42h_*qESKox#MybWzd^s6>5<7 zk@58=G2l0?3O42R&&?PkB8iv_7x0%Yz6*;&z7(N;)A4~U;LGS9{va(T-cX}}#~od; z-_C+UBu`L(ucQR>+jP=pzLRiAjoNv?d57cDNB!du{`P^2B!d#?oK4i`dqf+`UwaL# z-vjGn9#i^i#B#aZ#XzBr06z^;n8cc6!txy??;G`>Fw6qpaqjEv%rX@Gr_lnR`#|6u zyY_$0NXL$BXzG#HT#e7S29H@XJ^X36kL1OhvcI2};7H&FP2J9j9NZ|n`b#BG?-c3P zwp8{Xx32i1AK`xa+vGt_;WFzPaB2Hx%ZFEfMF}X*0HS8O`0R(0ma5 zmi#o4m9K>KI0u%8o9~NmN~H_Ze`@ec-`shzGf=zW2ce8Q3H-GO{oKaS!j2_j_KN@=d4N7YFe8ZPiDz<3+l7DZ{yAeUX_;|Q zcLp!RU}0j{*d?{P4tk*^ zZIOvyNf2Z&<%e90!0A*TaHDA3=d%(D84EeMpjROgJ|g-snm)6!n%q#e=K_!-y*?;P z&V5OR4-RtNNu>w`w&xz<=;D%`CauKVtLQw1Rp(X0sa{~6$C!g#@~2&MoedlHs@gr#i% z?jGnLvV0)(p`pGZQmk1RrWkQ4GQ*ixL#|EqNkEOD!^*>B<}ugu4c1jGWplqw!E@Oe zy6?J191Lq?y*lIZeTlJt_IZui(1wh$n9bahs$cx_N&QNVbU<6ILTZSV0s*4vxCGgI zb72?14c0pU4iDb>%^Wb!$HZ$+Bv2=q09x>i*>pRKCyt`TOJH=2OeV2)P|aJP&3A=p zP@H{C{_&7cG@g~s=GBMa?Q<&$dvWQLFOf*@zg)`=AKF???OLx;%gUkN;$^WOW*E4P zE9O+IPV%*N-3tN}?i{7nk8MdhF{_K~9|m*Ee;jYmPWGJUt!$E!a0|?^3_U8ut4`cKdsBL1d*@81tkXEDu|>Mv%$7K}vCfWdb7 zc2;Xb#;PW?c>B@P*5;ZUfmWl#!>`=haiXyEzW6nO!PyULh4qatuqw&<_aR7-|DOh> zBQT*5ci<1yi{f>gt!IZ=5_403`=UK5l-d_fsR_cz;yM;x~L@p=1N zVZQG7-ar2G!X-<_<$hkJajow`708S+DI8+B$t<2v@Ear`L6;|E(Em=h*jq!%Az!?i zp}-%MUqFrPRcOR?=^t(g^$!>QAlt{=l3yz4F3w~3zNN>}27Lx@yXrnOKO@J;W8>lf z1cVyq0_Eug>0=5ZMehMir@Yu|fzrE}k)7ea$-;QIoza%^xxd-LObac>8Samh6jABk zSI+E^XJq|_!jZED^`Hsg!g?6frIYQ6PSS%$5s0sRs$RwW^{PHVrXh8hC`3*S#l?R; z%z|dZ1n%~U;1=3?%VmXxyIdL~S|vk1{7NEy6T-^$L7RX9fu%)%3<@5#Y;YHRjpmNs zSaf*ti++tLSYrHztx~*~(jF-vQqTctveG|-?Mawl1C zwoW#OjO3KL!%f_E6KS_JR;jP4iPz?EwjoOeROo>VqLWUgGVS#GAItUF3xx~3*em$R$d}NdZlI;NQ``-7`wiC)w*3Z@;>F#_$Rm=KIX; zUJWVQ%?al9X*2ZVAN>#4o-6`Cu#@-r?1>J*4Y@!jN8#RW+=7F9>&9N6PB9vio@$uy zK?;vjgrlhcgK)D}^4+Ty$~QjB^S7_)Bn4LHkJCg$to4)A9U9hsY@mp(wbZ2dUsY@p zYaB^ezC3KFTO;Y77~V^wvx?u{6V?cIIX_QdkO<#4H{KK1jRDO+@5QBZwnB7niW3k{ z7n;vU3^wK3+f?mSr6P(cC%-{)pF>bGI6AXZXH6Np_yfVK`~Z3-Gwk8yFvZNS25tsd z?J^}GSLC@3X1;#O|=AX-~K-3i2Vii4JYsNG-1_rm_a4mY@MORcHs9fhwFX=^xS!g4lgDkS|$-a)p;j| zOW2WkwSL!INdRX$)}!5jd2?(4cn^0&iphv80txwpiJ&5Pu+01LVJFz=Zw8rgxH0dJFf|jI+6W2GTqY^al4~F^3@~XsdU#K&wb-+ScO+Ua?`juC66>x@>lSCt+jVC zv1r%#3c8M{RX+FfXdd25M@DY`KgR8dn_NA~Ircb)iAaxNg}__M&w3I~DQ<>vCA=Tk>pStnjxBsS-Z4_BBF zEpOb`(rhxng*e8*MWNS?=WnPkU({(6iO21?;_7MF1bHKzS9X(51$$lBMq)ukPALK<7TeX%P`;yv8+|NcB=QlDejFzvU8<-M;a@q-jTww==T|$pb1*7Q*A1P}1pAISr zl8qs1(%5=8T|B~1#=UaB@#)TaP{0Zd*4E%SjO4qYJMJ%~G35xv_sCBE{ay@FMJQg8*|I|+cnEsM?V?7f0Qf9FRFE$ zKA(5-1R&XVGJIT>`m`cycoIa zgdDW*xjO{nm*w}lrJ{sZ}t0LG3bZ;=d>o#~d?QM^wElapV3*Lfu*a~hd#gIN4(D_V8udf|#NA1>xIW|)@ypJE z+~*@rfP5&+OB=eSuXkq0BWj>#Cn4u7X-%ha5R(N?QUHqHdjvl`!(0pgrc787THBW# ztH%mt+T`_SAK&kOsYy2A_tCJCQ(eyU+64wVFXe>~}}>A$8M zw}Sj>LFt(}aq?G1XX$Y(%1uU1hv~8minohWM5-u5>S+g_S8HxMp&>;952>k0k?5Gmvb7H1r_n3qQ zHdS*erpj6MK0Yu=a3~!umNN9*OrSRs+NnSRr$hEb&d!pHeL z)AQYf8ao6e%`pwN2oO(SnTLVD{Nv*O+eLMv-Rbh!IeFKDN_GnaS4dli?`%Q)j@_^} z;x19bHr3Snu1fuZdMX`3wD|O!>q--b{Gyci_;hb-Ra#R5?PUs+l~xAGz&1&%Gv}qO z|Eu$7cEt)$y6_3E8acZ+0+?%UF3r+E1#*)^{eT~8Sc4X#lP}y_KRN|U?OnY;Oc^S{ z%^%}CGWC(1$N1 z5{6|4HR5pyN8gLpgfNsnz@i<23YDmA+|#qCgGc9;yt=G>sug}2>@UX;1@(V{N|`H6 zuG8pl!QaoRvhRcY-lnuj?J`Df9=?0Dr?S}hI4ir0_XbHuUd+nzJM!`P zgOcOy-}372@m?P@m~X2)Dv`1c9L7GSM{LK}W_MR}%jS#k4Z-wcHPsShLx*Y{I*Z#SeDV4FixIie}YxU}_vCOA_UJ5?JZH+MI+`X_09_J!$MdDS!zFjcyp zF%+XS&a9WvE4txaJ?gb?jM8QE)KL-^N^yreY_RMXh1&HQQCCHSv|U@Zlw)u6AG!?J zxwIi$MqV-&NpXCWLpTTO+$~;C`Q9qe{lU;xhr=}NfSL=ak1wJd*5>(Ud0!~w8P9*d zqAY<2^Qr_pe$3o)RQ(~3$`Mv*-3AqPjB5Sz4`QR5JLg#72~le%WT+R%A8#C4 z!^A@F{vA&x$)7ZtjUJaYP$unLbxkE<#qjEk^N#Tkl;{z*(|K*}LA_L=7*6)^ke-{u zLpb>%Rc#C+;d$rZKQ_QK??ggm{;(?%v7Y9qQGfrC>|_(VH8`67k4%!A0roSyAr;l=q=SqWxK`gs)$D zM1PCPIRfhhoDI-JMi(c99xL-V{~x$^tdV{0_J2l!=D8-DHD^3Ue%C=vt4LYjrDBKX zuq9%4o^qAI!GC!4Tc+*6lXQw!?@o5!<+GJam=Oh*lWT&Y$b@tDXx|8M-&2`Y4IZ91 z%#DBY8)}G;gOKFy4ca2te06@5Tx6JlbWu*(fGly|lBV-Bls(>PV zl$yP#x5tG_jFhZTHR7z!ooDqdgq{}o;kG$9Uf7SKqwu@XbaA8DkG?&5aeEwc-mQ8Kg5C{$hwU;B@4ouKOM{Z?2x;4%y8}G*F$5ff zyA)6%g?9`sI*8dGb%r-AZ!tZF`&LF+mS6p0vsY$7HoMkLLL0sU=3PCs>gj73J{Pw{{o^$X?OW=h!#3$yePr%8=j z;>;gRKg1$(AG{NW_nZ3|m^}B`G5PLt*HTXC*;nsgW*$oqer9%INw33@N~X^8!E(V@ zKFl%(evfh`w>~n!h2?^|ymy?Hs=hK+)K0W9TL z?#u;pehg68%uIg3xT90Sn&DpYjgBW4sSDd!XMUUY$c znSu44bY*p=%Z_bSm=r^K_eKck6kSb=-Pj+P%Kqtw-K7EK+ zYsfr#A=Q1xx{rx=v^%DRB=UapI?F8AOxP-NH7v@D;i~9}UcS1|jpXOw?BiJCS~j|4 zHk`l(y*pT*wtSiMU?-Magrb1Qj4U}`1DEwi{MXw+qL)4F6;b*Z|A8{E zD}lGytWm*?NA*sv=U}v=)7{3yiyR^~U~VHmyLpOZ(eW+Y{+FM5Khcl3cCrJzX_)Pp zRu$Ww@9~tLf!W{!*=^nR&p*x*cQys$^t&LhTI6|kAOE1?UCyhMm*DZ(Rm%M_n81eEx+w`-3 zgl=3l{Yvs9o4D4)wAatRiuKQdf9mMZu}QlL&9T7pJC7EB)=z^2$AYR`25-Eaf`Ef6Ikl-7=lCMfL>}R#ofpuZn zbISNol=BMH#Rdnhee!WY_a#^>{^_50m{OsIIh2W~G zqe_Ul9T&h}z~;9-(nss3hpMquvP!o1Le~i!L`0%i) zR)(v;^O@X9x>OSv^uA~Kp8-WIm%aQz#M^`r=ywm2*afV{R}@ zCYE*GA_MTI z!n*bL=OrafOs}Fh5biCjkwX8=a5<9g9^aD;fLA)&jD`$jLVns{evQC5sw;Ue-Xsgm zTP6BAV`0MG()dKXSwy6J(F?2XN&YxY1js(wg z_SGf!ZdJ-;Ro}fy2fQqh3PNxORH#_rIbfYMW~`G&dDodW8FDH|z)FrAzpWBSkC4mk z<|APmErMfHb04iCWFfupyyX~-py^Z4TO;}1>t8|~G)k9Yx-Bq~m)P5N{on1f_Vies zhC5U)7guOIL?V&t$|=tIc8Dgo`A$I3;Q>ni&Hae~B3@<%GkA!fC?2xDrZBJMZgxWc z%q1PFW(6r&9_>Gxv{J!qhG74zyA%$!wzBK{#)M}X5ukJ~KA@0Nx$}i1lDnhu`U?Jl zbRe4eM9rH2nSXZb;;fxt+*#S7*gE!+zCK*5^nm=rYrc2kf5|JI1Ct;d^M0+8bfn#$ zl^$w3PUBf0{hBLe>gaBC{_IAh{WD|R5{UMV@qf`JmTydoXQ=@{|E-t;fnp=@k1g_!0`OLBD z0CeLYw_=HIo}_X)T$#@E-*A|K!k#vl~rG$d4x8eN-4W1FnYTVu3>- z`u2OfmSTL1UEHnBvDd*3{-v5kR#67(4dq&Pq)*43%DI91(sMtk$n8#E~ ztjJM4>aYm@FGjDG^1mo@={?zq#5o@Z+pAx`nCK-}vmwCyvm;hM;j1-28_xK5IGO{U zzNlPFiKpWPMD9zf3}XG4T5a`e`L1#gKyjNhD_%}xz=~OEv8DzO)TIzLT&|4qK}8uX z&MKXsLNnEN?j{@kOkl5j1O8_%%E(@mRE_3x*xmX$Om7lS-(CNbFs7&45y`|JX`RR^ zgySo;Y$HeHL?c=R^K0b_r!>yW{UemvyIvXjQJ||J<#1RKk~n9Uh;G+M);_tUXm7Px|Oty zVocT%?&ystCjT93M;_A$&-UzSU~_f6Z{@>5U2pnHU2l-&$e^Z#v$FMF={fec32$v} zx@@x*EYssH6ZtjFts`ugW9FUc^R7t}q)>(9-=wp%>Va2D7XrGjq}D69>7%TXqLpmt zARkEFNTyPG7AH*yI*fPmyboE1qYgbQsHPZzgiM(Oqu`qrk>>r!W8Ok zGI0Yg=3Ckv*ckkvio|vusO*Z$g)F##_!BiTuTCT^8X|7AH!XYi$M40?_WLt~FVP4Y zqfp`#cMw3%%>bQwYdE3bR(vhKWpz6a-gV3W!TW>N@5E`~Z@6Sk!b{gd+xfw|yfaZ~ zPH=rQXM+oFo5|-3-x`DA_8ZjUzHa%7XYr2I;Y%z$9ENoNlU8P6`k+Wq>$3PI`H5X~ z5yc%}aNczxmdSx-sY-FRBf}cs-*FBiaC1WjzY+}%{pDZLrB_V90@N47qir-QqN~-t zW2DJY0l44la;~o^4W79S6`S`p&EURjFbxziqIet;>#K25D3>y7CLj5J>&G0xb}L5l z$i(OMMWv!ky~hw`)klr$bf@@*Xq{vms<`Bxc1d>0bLBW4!FW`@r1Bs&RIL;SeWj$4 zu2MZs(^8$v4d_ig1v6IPMYan!z#2_3lb6aHJ_03su&?Ogy-kifG^FJ4z@NhzIQt;o zmij+v4*T9QMUM8p+zV=`K;I?eCt@xSH!15UCdD}qMNi*Lbi`C9Za4hKp$UG5;l{#_ zbwxtjPuWZzMDE~i%<1t{aROJP%W;X|_IQ{-JfgA6qBS#@zCqz2im7CeHgv_1r1sas z?Ni0`$g$GD*z!yPe&7$P-btZigSm)ucwUsGI;*$~MAIJuu>0o9TKh4?bZ7G71Sv`BGk3%`4+#U~pGnpB>IC~jvMDo9a4eK*pcj{bR z+*n`lk9V1p{;vE(JSBk3E+!B9B<)KKs$*mWq%EK9rEe9@tmH5db#_7+x1C#k*Q$8e z^l{75aPqkzR%VHFfreJFF*oe-c^M=4qhId#7n%-_e3Jz-35BqUO1aqWTr69inF6KT zrZJ?0Pm#tSx!{pg@cBmGZLdrDW`5f>t{kR55l^bz)(d`Bt1KCZa&&5_&4>{C35ZsB z9!1|%A*?@Na(B>^zkrF9NBEhD(`~NSSE3`ZA85Mr^nEYky|APIi~l|l5HM98YlgE; zCS*+Sykij--qS%y7NC#GV?9-p%-&ah`XJpmw8+GbP3=0wtpk(7&h8aEL*IwWj|eer z;%5glJUq92n^)-+3Fl4pn|A$`O0R=3(s{oDQa*RbcE_&$*q6BUTDqJYv;*nM?_Pe2 z;y|{o>Bs^v1uZglux*d>@jUPLeD~JCERhGm353sG!*I5Zgr7Gmn?>ABn&8e+(|gEG zY2%1SY42uIX=7#xwUq05=f>0Sdgb&q88qXI&^nO}tAc#XyuQRb(!$P31 z@2v`^m%K~ve$-KvpIverq38J{#piaKBx&5+|2C3ysLfre(9xC=7Lqb_pciDx}fCYQOO@3WDhVB zlyA4x4=H?B`%6R()0}G^iV6zp1us!3y<4w1Zj*@)eQ|auMl^n+iS4qQ{nh-Sdtn^F z&KCoas60@PQ%cFK7P;*jWf^#B0kPhv9$PH3g507GmD^VOC6>VaqaYwszj+!g_Qvf0 z{IjPIu?jMT`ss1o=Ybd)wp&oQ=2%;DcO&ja_28`=zG_KfQNp%pe#L0u;H?vM@b&@u zL*04FWi6I_=h`eXuwr|LYiVnG@b)^^xmCQ(wPR$krQnvgBdiZxb$j_!K+;x^Yj>Dt zR>^i+uGO9~#;KAuvOMwUcc^{HT+$C!xWTkU>g2G zv63LSeJ9+uNKqy4JLb4C6*=H0)MckGkHrGmex<2-*L%GG$Kou8f3h_oV})G^2-Wg) zubJ?alL=^t_3qo;OuE|LvcWLF%JH9OMeh9HLkl7L?V>HvC9`@ve*xrhj>=r=uO`-< zzhgY*sh{*Qh=(XNgJUQB6x|}Bi#!c; z)kri$fVhnfcxQKG3w@9=pKb!j;}=&LJ@u11{(Et8fLtlG@kT8|VQ9?C^`E~=GA4&X z5*scek6 zy|+X1ZjVb+csHs7sbA1>#^F4Okt!t1`VAV3HMKWL8k>U5-UA=_XnN_Ni32w!@gBKo z1vRe;;m1xyZYhQi-TM4q2flWhCv{S@-F(O4q#~C%@*Y$#Jd+s33`cONhx4GH8-q$! zyj@Hsn17Gvm&{yzFS{Ppkb^YI-D~5U$?~inOHwJXX-JiXwky8pM$`d*>SI!0xvV~+ zSi~vhTk_L=OAqr62Ef&vCqNU1f~iEksvQ{rE(!hfpt4f??|Q^-b>4;j13P}%pgRId z_up5efOIM;(!B#yG5+;3>|_(l>s@OCh0wi&ZOE^y6avKn0>#xAcux_t48?a38z+iE z>)?ms&2V~3!XwmAdTR&-g-+2x&6nvgDWKM>LkR=+EZz+NMm-p?!sx!|?pFCijL4we(>u>3!lN zm#mrpWAwV9duRk~Iou2k4QxUW_4w~C{!)M#Hov@OInk}O)n017##-v+hR@7d%gh@)#^-+qI0ZjV_#i3!Oir06XLTYXDF3Z5 zYx2Q|IiWvnI6P=T7ESX_i;|^ak)-g*nX>qWU1Wx`OzTl!fNJw81}zk~^sN_dHr*6*sB9y9vcjn-uLtGtl}gwot7Kg@ zjfcGAfV%*{eJ7B4qW{;TQdAp!<4M`MvVVx2oZed&-6@CuxVIuN)yhbOytKG1Bm0wc zzOzi~|Kb7&Vr7WQpr-|4OkLJ~fXbH-!*4A(M1eO4YJq$zsrSduD+-n~ecw4BcL@jV z9Sq)ZedhnS9EQLJimcz2+@mQ+kHClSog2u9;dy7Uu4Um7DAUpfcjI28Sm@@eQW z0a81867Zn=)~aDz*wfT8M<*N@SnFJXHL<675d5+Hy1ifFMBLADm+<$!tat@t+C0|d z975u&oSufC!HTwD0nWZ?@=$uu`_4#dKhQ;)CpGX%d^<-qxzM49WP}$&@fY5KpD#>H zA0pRUdK?Pgq6_`g`4Wb1ylZ#fXh(yk+vG%>uV@abq(FQ&rsCph8Vr8RUE$HF`^UB@ zkvCANp?7=p_q_vWoN!P)){@{n;F1sM^nb&r*4SRTh`QIDKa@1lTXv3@gu&hYYspJ# zp=q`8aKX4OV95XRM+Er&(kfq7n{u&^?svcO%Y`1dy3b(c@hU3(lWQI{#|7irbc#k2 zM*aHf=d??vEDMyk+n)Glc;9S!j>YAL()tnPbyXzV7wl!(gOT#0nwSiIh*l!$ ze`<`Fr8Guxwoy4*Avv8@?r{|`Uxn-Pg!w+|oK>3frM!;fdDb8wq|Nj0sQncX^zAox zqqODCs;)14+I}fBZLpES)uDOfLk#=KuCv>EN$M5DCY|QDcz>uRLg$Mjgt2?@9_A>>_H&{zS=!N}>oQ zmh0UM>_EQVK1Z8_O`b$zI1KR+8tXhmJTEY|2!_|m&e^WW3Sdrr+|x=X#&A=Q_NMVJRg}b-0gx>go{(T;MLg7{ zcYU)se`$M!s(ER(@nBa3Ug_7NUgC@kX@1a~i@S^M(+N1Fur#nLCfBjX^N}~R3SDHglG%A()6y!t65f* z<<)~J9z9jM2KdL)xQ~MuBK;5Vviu}{lWI~~H3c~-ZOcsj3d!6JG z*w0C#;&=Ht64tuQvVmuJ8{1W#8)6pxIqLdpFjckU28GHK{@BdZ70}nMzP!z!!@B!* z^`NG`am+r~sD#w0gi7gyyCHX?SK{I7D5@ag!mF-uj^p{c|xV8C#TRO zTiRf}H@{1@r07PAjkNCxNrDAUGLiIkE7$GgZeKva^aTB~dHbb|bO?l3Ua>ycHVUBQ zB_=OX{E=Vm&Vlb+O?$dDs4{NUH=o6Gu`|c7+aeE!I+BWxUnMF*Smrw*S(|8_Gv@N~ z9l}mCoZuqtHeaII>VlWpP3`RLGRD{|EtMS0ofKpXRMBOm?)H!}P=GYJ(~B&(Xq1)C z6v3KF9jj>x2o68qBAwssQlo+ah?hEXN6EQD`Ef=)%5#tT#Mh$BWs(HuDVvAEVlDfI zL8TT~x?qRNrJe@Bwd8-6%z_UEH{YDh%HHfMf3H<@eq-A=|72ZGj=X|r4_i1&suP=! z4Xl5L09PT*y~;wlH#VFRsrhxew4Fe+IbYsX!4rlJOh)A+B<%Rj+Wqv)}DK^}`9;tQxyVYI%wndKHvhP2H8jHU4wc zaoek^VnNxLDa7vM7=NTc=|fZH=n?+IT>7Tk*WY`U(3;?~t+-SZgXp=pJDnKOH9E*+ z)bOn`n(n9b164+vRlO+&huO>`GrUi}!lih0NqxckeS0rztmOW#go;S;@>(u;}+8BYI77GtSh^C;8*ZQcnb!B5+MIN8$9 zn$Unv#TLuxa?kR^N9R;eX3Vcy1dJlMo9|~oWXKNZ`d~D#oskuHvqrnY+JQo-35vDZ zuVxuA3;oSjv%Nv&acDt0?NBEZE2U9~?{(W#j6W3x+%{UA3|Zh$NhX|8bQL5DOA;ih z$FZez1`RL-r{{Q57(P!u_CxtSP6l+7ROi^IM_GaY(X4m#xe*aw)b?Miy4x8?w#}Ie=2?uc` zu^*VR#@Kh4G$~YOauLU`Ec!h8dPg=cb8$I$kN%VKA7bN6j+m5_*yR%oW#Sn{dl=7P zY@m(6JRQfGrt&$8KDj$)Z#oo`8o|Wv_%_J1X1b5x$$>cS?3w6pYxD)^OXTNbjE;t= zMj=L|t;*+?W0}wTkLSiEiwkwTQy&Cu!tnP#;x|Lv)5hr~7~SP4uv8wS17j?Ac@RmUdLE6Kh9-e!ffpxtXu7XCwcCf9XJr-#jx{ z(t+a*8)911k|k9oq5LI^*x#hGgGAvD%~m+k3=M~*gB$T^XHN$?*35g0L-7JgH!pOh}(yP=p>t0@K}ZM3uIQJl?<<2?CE8vCkD|U zzEs;}`*m@?ZY1{tjzbiURk^=@!spPI`fKBTf9hUY9I+d*RlKxVRdwUYk%htJ+nomC z-#arDw3W}KQuX$SUTcZ^Gr4UMKrRqpxd*Al&rF}%ES`o86KUeV29!*Xn-NNt;hzcw6q4glC>>>(*hmhgUecuy;B~)Eswh2pDC*!Gj3t%ao}VI8qRn-kF{g$r?84;>73?tR|McWt2P~`I|Lr_lUqFjUkB`; zP&w1|`8hQ!==g@8c+G2-`}VHBubs^FROYU(TUYIj`I7FKb9MgqvFv1zjzuHWO-}dc zS4@7^ODxw!nr%R3;TFax@4o)y{*=_NRUlyXum;Itq@1v_^kJ8+PWzrpE{JDvptkNk zPbm4)uEOAh=YE2gP?U*v7_6L|?u)KoD{M8b>%P&37-R*QVrL z-vOEi-Z{Op;09FW>wdr&NemV?WI|sMwOjWHf?|^T#&f9@S)asdh6DhLZ+{4a3$8fdCP0oZOY`2 z3+e0f{cu`;$IYbFS3zyn0hNwV&exisjaH_@xi^t)G^Pk*V;$Pc9&s6*W^?tcDAuSZ(Mu*`)9HFYq@D_-pP=2OjEfV^v?oqL(AhO zT>JaAq!fBxPZ41tk}*rF_lqmO;6P~7R7&2vv|#z6yQB}C1h4DtI9|l+T(}t9_Kmj= z!zu#0CXtB?>dy`w8~I>*z#bR{xRn^p0Mv5|6_x;u)I!2lqE7Vz(KfSo2C2wFiDleIDE2eyXM85YyoKYK~m$q&nFp}`1RT5{>oe8pR8ZU z8Qg!REWXdPUsO=LV9)FLwr@B4+PdNV&iis`a~)c)jE8;Wd6nzqFUMv_2DtN<8JUs} z>w$-qO(p&D5D6_10P~J>D)|dip(b=~(;z?dCXk0g4*ZEcxhvB)Zac9{{TuMl;bM2H z-kgH*_LqZvjue6=joddbcT}E&vwz%EQD{8*aqrAyp!99$!UJ=%vkabKQW{fh-ylEG0dr_5K!G7s0m@YWXLX>C`D(|K5u(6JGzMi}(t$P#kgtSiKwqVG`} z&{hK#Rm?Vx8M`&0g54wfx@^C`cAEacHTBgztT2s+*G2#QDYi~LB48e;Ba?hWDy_Lg zQKdY0qAAbYk9~f>#B8g4te&xt+T_Wh3iN=^d@lrj%Q|O%AXNS`%<^dp-G(uox#_l)sZa9F-h_87G&2i|-wW%0ISv%YMX z1MzCJx8pGD^|JxA9=1sg&R1{93<`>wZe1U%5Iz!|)n?axa(-x60P1h}V)s3+rjPTa z?}sP-A8*Y6q$}TYDOI&i+mST5mubfq{uy31CDCGw*abl3=;w# zM-vY#B~kUCzds+rRRL|r0>2YB<`xmP6nZpNX&EWy&d;j-iii>qr$;l@IGQID>vMl& zg<2ZR`>O*jCQj@ZST{|e@MmzxS(ED{>!&}N;LKjQ`6qsyK%S8ZM+O^^|6q5 z{kY>mbB>UkZ7P3Y^&I}|WB)d3%B3)04KdW&9e%=HX5M==IyuLHc|fYSfA9WY_#f|0 zpnpc1ToKYa>tM&4a`_?9t*14N=I6$lC%H39D;^w8LiSG}aX@|ITE5$TH$3x=aPU{Q z`q8Gy!*zwCv0vj=mXX3ugYxG&Na??}-0E=QC>*Z(dVB2D>$Q8ccRzLPE8bDx zS1jElk~#nfw7whLb;w!JTXgkrdR{K9W_mERF*7z4Lm{d5^CX_z;wTXEE}&MJ>tA9p z@r~OScr9n`Gb;Ld`JC;^Q*OQ`6UY+y;^PO10YD|yyr_%kf#ho@^?{fbKgsUj9dXpp z;?Aiqc&2`F(T#zY3Fj=-CU3+~3=N^!B2us&E;To^oV&T`fo}DAaa!=#&DPZX5@$HT%u%>7PTF0E$oLto3a6k~)O|wB$YXyf_B%WqsGdLm|sTxx-V% z7F?o5J+ITw_CvS)5Ted)T{TlvUs5puT9$i6IDp1{?%YHy6jkA~F8OH(t|95HKwC+$ zIf}~7?(c~1I+Bxxy9-}cZ8x*s77rgL%CZdyzK!`fDN@^L;v zbw%!Alp3gQYuTlj{~Yu%IB1s|&2~z;sCqVRbwmZ}7N^-;KM^2})*68PX*<%sd|)C* z+$%QV>Sj}8YMf`+hT>I{4iI>fnhV@i!2WqskNAFA`_|__vr%lI^R9+D}K zNB!X9s+?`-Oc&GG6Win}+qNX)urFa^dGcxNAD2YY%lFf<7v`(5kLO>c?0)KYl7?G6 z^8Bm1bdLeo9wzL%dol?GMNb_|-M6%kXBZw4d4B8($1`;N5vHZE@tEorO}4YCIOI5Y z0*r+KpmeyE1U^=hFF2IpiEVJRPtfZ`buN2YKnrE!rAyLJcSvcGs>3yLM!o%aH#&BaGZ@H#OKj*E z^Y1DfS;|WTvXD#f5v;YAu9a5uwt!zYHu+u5-~tf&kD!DQ6dH)@g8t!`?JV%zudj92 zaayIr7jZWG5xYgjy(xEElEB>MFb5@e%;HboPudMJ^+}ctSHI?6;dmF{+ zgb91R-dKZgNmNK6?c#EUP@L%}E)(J3$|_%Q#kpIx8jqIM-Cc#c#i=Kd;|ZPEmPu&{ zUuM8D;!$3P>UQ-xVMg;$IbjDGW1 zXIR2Zalr$o@g*ULej2=%nnv^2GB{gg)(o$9GUE83ZF$)=qo1_Qy`{Yf1Ay{Z5k5{y zBt9hLON5%J%NnaGZB8lOFPQRM=Pgz*EiMvE{~GQK3~>R-K$?&*&==Zr^cA{P5;xQ& z=8Rr1z7|gFU|-qE%eAD-&!@MLH@_UP8&Y(}*Ven4DHFC|f&HnH^(B*P-SXsmwH@>u zXA``K8-vQYY%yXouB$QQWPM@Zgm^oL_uyH{S-AOS%YC=OSLvCF?&}kfolv-8ZSuY4oUtRz zby5e6HkMh_Z?p5xuNnVxFpLiDygYx+L*)YVCpuzO@vp*x zu7{fnDo?&E!L_{8_aQLZ^{MMsuKg~weZD_#yWtQyjB7jSP#);kgf0(is;hRRpAPpv z+s8sy`eiI;SqEpKAn_~?mA|;GcTh%G>8)alS3~jt&F5a!AEUHP@5%!m%WJ#n_t_kj`yOAoH^03XV+^h2bo*L17aLRNncp-;+z)(z8yuGK9@FQ%U|Mnk z;Ll3~)XJGDV~utAZcsh8?W?-tOA8%SSD00$P8dbD?w>%dV2&pfm4RVh%W{q?l5$L; zR!mVNm0$!O+CMO)^4>XmdU*fE1Cy~S!tk!iUT(G&you+|w`t}kCDP=gV~~mkcS4NQ znC`59jZj~?Z4a%>7Hx0;Xavi+$X}0)qMr~+14?1GF+)8J!zD>rDu7I!_N9s&&OOVh z>=6e}CPI>6$U{>x8cCg=1-BSsF6p4PD?-J%D2soH-OUWAXy+ECw>W#(1wk(wR>7E1(QtU{Vzc<`B7< zDDR{A)gVtk!eLu!63H9b>5wSy>Rtt^eLhwlrpZL&fQ4rRMd4Y-LSRWJT*nVQ+(7q2 zy7SHL)mJ?d%uyLDS%M?m;n2{4QfiZ3EAF`=&f=p~IZy?7Q_LoY5H{%EZ?EAt?% zPa0Wk{dRACy?Uh`7-x;Hfrjxdl);sbYSal)@C0t#Su96S1v;M$D4%e!QjYMK z&4{a$UX&cQYMxrwWCrDEjW@Rc$>Zmf!6sC_MR7BHzDNm_as8PY!(-MgCI{-u&d=Qy zx{rxWAQ3cx`20FB*$SP{JfJqpih`W|ge4gRJ7jVQ1R5)(+A3((7j>oJIG@#h^RSZigbN9; z^dtp&LY&|Kw7|Xo5Co#clRJZ!-z-X&cdiF%jCt|*CA)vhuIu;XR>d@tp8$(ixITN&te-Y8G6^ki-d-1VUN-rpRv zo%q!J<#-eNHM)4{NZbD3H@|fbC^|CgZnA6()Ks7f6fSMz1(Y1yw}$u}`LPgoz~ITX zSV2bG0fv?=TSq9>uCwOIyl~pnR@0m~?Uu~|!z|f#pLZkZF*9)<5}|X;Yn3oL+4{%1 z?}dPa)jfO8`z3WQ|HXJ&|4GnEG~1Bs(x%c4F3|DIm28rE=4jy}Z2M9iwW7^&?4l^G zV=}eCwF{!2A6wo~#CVc){A=%@qWw~zw2 z`j@yG@PsG~^7=)FLcCLCffN|)UM$7C@wlwhpFq*<33EhBaW~O!zq)(DEdDP5+3SOJ zvF~aXgl&O>%5_=)t^0;yy4qj;Ey8_(W#2_}2D)~mb?BM!ndUSy5&(QIqOJv`sY>jE z9i-N!FKkWde`>Zux1>Oe#kK&J_)?9IHKjFmlpbyBIf&uR;398WwUF40W$kESnzL=OeZ^*zCLvvV%->p`vP{*wsfu} z#{Nm^%>1WXS_jf4s$Z*3n;#9gLxFXiq~0jLuPpaU>2*2N$XelfP$i^M{zpOfL8e&( zv8_;};s(MkMD=wd9pi^BTB|n9l^ds6@Hn*@1bs454p>8L2<5f5V5dq5rCxknIg83Qh8@?(3#>q4{l^s5sA7fGtjBeR9HXff)75|k{Pp_$U25E!pjI@q1|(pjSoCf# zFR*7w@OeNHOV--p#%nKsUeCTjA9J$?g1NZlh>u~wO`pufwWv5nqdV&xCh7<=V zro1t%UXg#M%*CKtFDK*YHxLMy3E%QcA+bH=4dnixVy+IKJvehiUdjpsDj`><;Ps-x z$N;adCpvlI2|s!wEb!WsNJK5bF)_tqgnoEc2=IuBfE4#kOsea3f-NiX<>x z9pt<=SVCvFw)vI%_Gw&V*&Dn}m#n{Y?Y37%X%SAyrmc&2;6Vna(&Qf2=sW!OI}j^Z!3Zyo&%Ql zO*SW&MegZ3M4&g8>OX203d&|+)5^X7TobI z<_&P62+7=BSo8+6vo`!U*4k~qWPEf2XBDmOKyoYj|aLxKV?J!P@=T?O6ZgsHO5Dxp9KJe@^3fwoOYUzZ#aUXBpF&>_<-EQyGw%?)6XP4FsT8Xf}{WT>8V<0az-ZlGE z&jAc219(FruR1;pur^7uJG|_~PG`RRzBxlyTFsKf+6OL(?>Pe*`K$nf>ptEm$uKtU zk~C#kYtw61g6U$64`gJmG5_&{{pxe7?JH;gV#1~8VBBf5GGVZf9KzX>gcB8K!)kdh zfr3`f`S9CQ$Z=={5;RbqXV!r^r4>Hj*<)^5RYBeTj7Am+n*&5-IHvlSQBi`-bl&ba z$rTm42>YhLgp50}K+!a)EiI3!{9^IL*+cg>fUG^_+vg92$q>xhq*WzruV4=Clu(A<1cUl{Qqr=2|m& zy(~|qc{6KvOO^b8M%JRg>fKPKgFyXm9UTg*e=E(Eo~|5wVwLaeSYzpeRHUOT*kCu* z3YXdDPOgE2z_KpgC1u4`($qmpS;Lcu{&J1YZu@0VYUdg0F5jIm9AsYpGf6Pd&+>r2 z;8oFQxdAXHumm+%&`A>Fl0eU5WP!uxeS9TZ8hgM0-Ffw)6KM9d_W$Nx+h`mc`@c=| zF)&S~)d-JTV4C{sRv^Lkj5!t&UbUAr!k&j!9fHuA5{=qY#C4ly`6zl|m@ftl-}_^; zcq*X!^R>1%$23ldiKn7Fk>~(dqYZQVm-AsGjD^5${**vGPk}ieSDH8rLXX-eNB~KG zQe1Dpi=kGeqKlG~^qM5DjInc8P9QOtk?7CY8pmc|Z63^iF5@+l_+sEtbP5vL;$#>P3h?2)k@EZ-+I$V25xj)Dr&pv_-)IOn zP#!c=;+eB~+gkV8tQ&nzB{R=-X0*+9^b}M#YW#YRr*0_$%^LwF*gYaaKI= z-D7lm(EgS1RN`Nd3r-RQrtcme!+nm7dcnn)MuB*D-t3WRoe2Mi&(&s7u0Wg(-U&-^ z8>DW)h!miI6pl__-wBPL#$SG-=UDuX>0klq)^Zeg|E=3f9ZZ$cX7UDk?vEs>60UgE z2e(#&Y|CHJYml>wBL0x_K0X<+0RMZ@bB->uzME?wto?_)>y#e$hduea<;9=<>FD2c zd7)MXj0h{-yh48D7ls3ifkV_Y6K?A9huf-=|65e|m$UFRIE)ePqTHT)|IKx!H8bgvayg9lxinH7nDqPv2unEV(QF z9k6j(887D_B$)kBO;-d1P9kW_E6EJgUs*+PP!bm#zRY{{__O~^I~xXKSmn1I8|~j) z=tg}GdCjO#g=t+WsA`=zxZB`T@phHOWcbo;Wl3CPD!D5dmr`&g)( zQxVGhI?Wb9j2lmWUHEr2m%!1ynd~(AJuZwsMld2hF*%NLZWc73HPR&(djCk$1HXFw zCQDqRgR^(WYQ5~w<4~DBSBR60AX~6&N&QO@$ng-IjK|xN-@43aDN3)wtZ(ha%-Q_! zTtUtLK6l=PiUfmpY{1CHKzyTUK#1)lCvd6LZN@Wi5b_B-&uxx<>V`l3UwMsAb|5uJ zEkyy;V?_4%wAGYYbzmJ5yZooON+;GyfRaL)SA{h75@iPwjwymH39r|ZgfIoeZu(OopF)z_jntdSv0!H;=EAv`-=0$-*fn_sL zIow|?tlG|$Rw`Vi``%A%mFb&5?xfFC@(@y_`&X(1Yu^?CtCm8JNRfvE$#2BL?pr}^ zi?`p6qE*HB{JCO}6z}gDnU;;_4FBBnVj~>nNVMTX$LRi(f5{5)5i-Z5kV*S((>Fyf zX3EtB1oNU+c%OFT3gi>IzkrUaCHNy0;vYbrA!k3*x7MEjHj;^bGJP;4v*{$zS3^o& z?x){zK4l%ps|CC#JguDU&c9y*AjRS354fyPUO>i8HNhYG+*Ms3#0mc;B&NhfPm~Q0 zY+7t(g!!M5Py-_o4C*}hhY)j+DKPh*hNz0;I^VOvg)18{27l03w5#SS@$X-B$;E&V zjm@3EA0(M)36ewXJ+M@r^OcD(gfJu%%M)Wr)#OEqs<%1)8J7&S>43N-@bY=b{hlnv z>4hTz#hT;QyIxXC=lU*{m~)%}v-7cV3`qFr8#Lsq$b`CW@^2gNWq1%XnAD{{A=s{( z6h48>O7D7rF0E`*JYslA^R6V#Agbiw+x<`e6$COqG7JCi>-!CWT_z5Rv5fyl4;Hrk z%YZ%{BtyR=%PR11G%-$E`*%ktw9bRs_-DKfpDDa8Zw65J{Jv*-Wq dMholi;b+%;xa{z=N(%6GUF+u6VhyX1{{!?kDDwaS literal 0 HcmV?d00001 diff --git a/hb_client/src/assets/404_images/404_cloud.png b/hb_client/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..c6281d09013e0a2c5f8e699a0a6038d9480291e5 GIT binary patch literal 4766 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 literal 0 HcmV?d00001 diff --git a/hb_client/src/components/Breadcrumb/index.vue b/hb_client/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..e65a60d --- /dev/null +++ b/hb_client/src/components/Breadcrumb/index.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/hb_client/src/components/Hamburger/index.vue b/hb_client/src/components/Hamburger/index.vue new file mode 100644 index 0000000..368b002 --- /dev/null +++ b/hb_client/src/components/Hamburger/index.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/hb_client/src/components/Pagination/index.vue b/hb_client/src/components/Pagination/index.vue new file mode 100644 index 0000000..e316e20 --- /dev/null +++ b/hb_client/src/components/Pagination/index.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/hb_client/src/components/SvgIcon/index.vue b/hb_client/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..9a3318e --- /dev/null +++ b/hb_client/src/components/SvgIcon/index.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/hb_client/src/directive/el-table/adaptive.js b/hb_client/src/directive/el-table/adaptive.js new file mode 100644 index 0000000..298daea --- /dev/null +++ b/hb_client/src/directive/el-table/adaptive.js @@ -0,0 +1,42 @@ +import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event' + +/** + * How to use + * ... + * el-table height is must be set + * bottomOffset: 30(default) // The height of the table from the bottom of the page. + */ + +const doResize = (el, binding, vnode) => { + const { componentInstance: $table } = vnode + + const { value } = binding + + if (!$table.height) { + throw new Error(`el-$table must set the height. Such as height='100px'`) + } + const bottomOffset = (value && value.bottomOffset) || 30 + + if (!$table) return + + const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset + $table.$nextTick(() => { + $table.layout.setHeight(height) + }) +} + +export default { + bind(el, binding, vnode) { + el.resizeListener = () => { + doResize(el, binding, vnode) + } + // parameter 1 is must be "Element" type + addResizeListener(window.document.body, el.resizeListener) + }, + inserted(el, binding, vnode) { + doResize(el, binding, vnode) + }, + unbind(el) { + removeResizeListener(window.document.body, el.resizeListener) + } +} diff --git a/hb_client/src/directive/el-table/index.js b/hb_client/src/directive/el-table/index.js new file mode 100644 index 0000000..d3d4515 --- /dev/null +++ b/hb_client/src/directive/el-table/index.js @@ -0,0 +1,13 @@ +import adaptive from './adaptive' + +const install = function(Vue) { + Vue.directive('el-height-adaptive-table', adaptive) +} + +if (window.Vue) { + window['el-height-adaptive-table'] = adaptive + Vue.use(install); // eslint-disable-line +} + +adaptive.install = install +export default adaptive diff --git a/hb_client/src/icons/index.js b/hb_client/src/icons/index.js new file mode 100644 index 0000000..2c6b309 --- /dev/null +++ b/hb_client/src/icons/index.js @@ -0,0 +1,9 @@ +import Vue from 'vue' +import SvgIcon from '@/components/SvgIcon'// svg component + +// register globally +Vue.component('svg-icon', SvgIcon) + +const req = require.context('./svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys().map(requireContext) +requireAll(req) diff --git a/hb_client/src/icons/svg/404.svg b/hb_client/src/icons/svg/404.svg new file mode 100644 index 0000000..6df5019 --- /dev/null +++ b/hb_client/src/icons/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/bug.svg b/hb_client/src/icons/svg/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/hb_client/src/icons/svg/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/chart.svg b/hb_client/src/icons/svg/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/hb_client/src/icons/svg/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/clipboard.svg b/hb_client/src/icons/svg/clipboard.svg new file mode 100644 index 0000000..90923ff --- /dev/null +++ b/hb_client/src/icons/svg/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/component.svg b/hb_client/src/icons/svg/component.svg new file mode 100644 index 0000000..207ada3 --- /dev/null +++ b/hb_client/src/icons/svg/component.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/dashboard.svg b/hb_client/src/icons/svg/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/hb_client/src/icons/svg/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/documentation.svg b/hb_client/src/icons/svg/documentation.svg new file mode 100644 index 0000000..7043122 --- /dev/null +++ b/hb_client/src/icons/svg/documentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/drag.svg b/hb_client/src/icons/svg/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/hb_client/src/icons/svg/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/edit.svg b/hb_client/src/icons/svg/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/hb_client/src/icons/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/education.svg b/hb_client/src/icons/svg/education.svg new file mode 100644 index 0000000..7bfb01d --- /dev/null +++ b/hb_client/src/icons/svg/education.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/email.svg b/hb_client/src/icons/svg/email.svg new file mode 100644 index 0000000..74d25e2 --- /dev/null +++ b/hb_client/src/icons/svg/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/example.svg b/hb_client/src/icons/svg/example.svg new file mode 100644 index 0000000..46f42b5 --- /dev/null +++ b/hb_client/src/icons/svg/example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/excel.svg b/hb_client/src/icons/svg/excel.svg new file mode 100644 index 0000000..74d97b8 --- /dev/null +++ b/hb_client/src/icons/svg/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/exit-fullscreen.svg b/hb_client/src/icons/svg/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/hb_client/src/icons/svg/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/eye-open.svg b/hb_client/src/icons/svg/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/hb_client/src/icons/svg/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/eye.svg b/hb_client/src/icons/svg/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/hb_client/src/icons/svg/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/form.svg b/hb_client/src/icons/svg/form.svg new file mode 100644 index 0000000..dcbaa18 --- /dev/null +++ b/hb_client/src/icons/svg/form.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/fullscreen.svg b/hb_client/src/icons/svg/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/hb_client/src/icons/svg/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/guide.svg b/hb_client/src/icons/svg/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/hb_client/src/icons/svg/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/icon.svg b/hb_client/src/icons/svg/icon.svg new file mode 100644 index 0000000..82be8ee --- /dev/null +++ b/hb_client/src/icons/svg/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/international.svg b/hb_client/src/icons/svg/international.svg new file mode 100644 index 0000000..e9b56ee --- /dev/null +++ b/hb_client/src/icons/svg/international.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/language.svg b/hb_client/src/icons/svg/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/hb_client/src/icons/svg/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/link.svg b/hb_client/src/icons/svg/link.svg new file mode 100644 index 0000000..48197ba --- /dev/null +++ b/hb_client/src/icons/svg/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/list.svg b/hb_client/src/icons/svg/list.svg new file mode 100644 index 0000000..20259ed --- /dev/null +++ b/hb_client/src/icons/svg/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/lock.svg b/hb_client/src/icons/svg/lock.svg new file mode 100644 index 0000000..74fee54 --- /dev/null +++ b/hb_client/src/icons/svg/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/message.svg b/hb_client/src/icons/svg/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/hb_client/src/icons/svg/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/money.svg b/hb_client/src/icons/svg/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/hb_client/src/icons/svg/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/nested.svg b/hb_client/src/icons/svg/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/hb_client/src/icons/svg/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/password.svg b/hb_client/src/icons/svg/password.svg new file mode 100644 index 0000000..e291d85 --- /dev/null +++ b/hb_client/src/icons/svg/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/pdf.svg b/hb_client/src/icons/svg/pdf.svg new file mode 100644 index 0000000..957aa0c --- /dev/null +++ b/hb_client/src/icons/svg/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/people.svg b/hb_client/src/icons/svg/people.svg new file mode 100644 index 0000000..2bd54ae --- /dev/null +++ b/hb_client/src/icons/svg/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/peoples.svg b/hb_client/src/icons/svg/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/hb_client/src/icons/svg/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/position.svg b/hb_client/src/icons/svg/position.svg new file mode 100644 index 0000000..f89f0e0 --- /dev/null +++ b/hb_client/src/icons/svg/position.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/qq.svg b/hb_client/src/icons/svg/qq.svg new file mode 100644 index 0000000..ee13d4e --- /dev/null +++ b/hb_client/src/icons/svg/qq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/search.svg b/hb_client/src/icons/svg/search.svg new file mode 100644 index 0000000..84233dd --- /dev/null +++ b/hb_client/src/icons/svg/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/shopping.svg b/hb_client/src/icons/svg/shopping.svg new file mode 100644 index 0000000..87513e7 --- /dev/null +++ b/hb_client/src/icons/svg/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/size.svg b/hb_client/src/icons/svg/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/hb_client/src/icons/svg/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/skill.svg b/hb_client/src/icons/svg/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/hb_client/src/icons/svg/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/star.svg b/hb_client/src/icons/svg/star.svg new file mode 100644 index 0000000..6cf86e6 --- /dev/null +++ b/hb_client/src/icons/svg/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/tab.svg b/hb_client/src/icons/svg/tab.svg new file mode 100644 index 0000000..b4b48e4 --- /dev/null +++ b/hb_client/src/icons/svg/tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/table.svg b/hb_client/src/icons/svg/table.svg new file mode 100644 index 0000000..0e3dc9d --- /dev/null +++ b/hb_client/src/icons/svg/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/theme.svg b/hb_client/src/icons/svg/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/hb_client/src/icons/svg/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/tree-table.svg b/hb_client/src/icons/svg/tree-table.svg new file mode 100644 index 0000000..8aafdb8 --- /dev/null +++ b/hb_client/src/icons/svg/tree-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/tree.svg b/hb_client/src/icons/svg/tree.svg new file mode 100644 index 0000000..dd4b7dd --- /dev/null +++ b/hb_client/src/icons/svg/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/user.svg b/hb_client/src/icons/svg/user.svg new file mode 100644 index 0000000..0ba0716 --- /dev/null +++ b/hb_client/src/icons/svg/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/wechat.svg b/hb_client/src/icons/svg/wechat.svg new file mode 100644 index 0000000..c586e55 --- /dev/null +++ b/hb_client/src/icons/svg/wechat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svg/zip.svg b/hb_client/src/icons/svg/zip.svg new file mode 100644 index 0000000..f806fc4 --- /dev/null +++ b/hb_client/src/icons/svg/zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hb_client/src/icons/svgo.yml b/hb_client/src/icons/svgo.yml new file mode 100644 index 0000000..d11906a --- /dev/null +++ b/hb_client/src/icons/svgo.yml @@ -0,0 +1,22 @@ +# replace default config + +# multipass: true +# full: true + +plugins: + + # - name + # + # or: + # - name: false + # - name: true + # + # or: + # - name: + # param1: 1 + # param2: 2 + +- removeAttrs: + attrs: + - 'fill' + - 'fill-rule' diff --git a/hb_client/src/layout/components/AppMain.vue b/hb_client/src/layout/components/AppMain.vue new file mode 100644 index 0000000..f6a3286 --- /dev/null +++ b/hb_client/src/layout/components/AppMain.vue @@ -0,0 +1,40 @@ + + + + + + + diff --git a/hb_client/src/layout/components/Navbar.vue b/hb_client/src/layout/components/Navbar.vue new file mode 100644 index 0000000..d053e87 --- /dev/null +++ b/hb_client/src/layout/components/Navbar.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/hb_client/src/layout/components/Sidebar/FixiOSBug.js b/hb_client/src/layout/components/Sidebar/FixiOSBug.js new file mode 100644 index 0000000..bc14856 --- /dev/null +++ b/hb_client/src/layout/components/Sidebar/FixiOSBug.js @@ -0,0 +1,26 @@ +export default { + computed: { + device() { + return this.$store.state.app.device + } + }, + mounted() { + // In order to fix the click on menu on the ios device will trigger the mouseleave bug + // https://github.com/PanJiaChen/vue-element-admin/issues/1135 + this.fixBugIniOS() + }, + methods: { + fixBugIniOS() { + const $subMenu = this.$refs.subMenu + if ($subMenu) { + const handleMouseleave = $subMenu.handleMouseleave + $subMenu.handleMouseleave = (e) => { + if (this.device === 'mobile') { + return + } + handleMouseleave(e) + } + } + } + } +} diff --git a/hb_client/src/layout/components/Sidebar/Item.vue b/hb_client/src/layout/components/Sidebar/Item.vue new file mode 100644 index 0000000..b515f61 --- /dev/null +++ b/hb_client/src/layout/components/Sidebar/Item.vue @@ -0,0 +1,29 @@ + diff --git a/hb_client/src/layout/components/Sidebar/Link.vue b/hb_client/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..eb4dd10 --- /dev/null +++ b/hb_client/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,36 @@ + + + + diff --git a/hb_client/src/layout/components/Sidebar/Logo.vue b/hb_client/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..7121590 --- /dev/null +++ b/hb_client/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/hb_client/src/layout/components/Sidebar/SidebarItem.vue b/hb_client/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..a418c3d --- /dev/null +++ b/hb_client/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,95 @@ + + + diff --git a/hb_client/src/layout/components/Sidebar/index.vue b/hb_client/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..fb014a2 --- /dev/null +++ b/hb_client/src/layout/components/Sidebar/index.vue @@ -0,0 +1,54 @@ + + + diff --git a/hb_client/src/layout/components/index.js b/hb_client/src/layout/components/index.js new file mode 100644 index 0000000..97ee3cd --- /dev/null +++ b/hb_client/src/layout/components/index.js @@ -0,0 +1,3 @@ +export { default as Navbar } from './Navbar' +export { default as Sidebar } from './Sidebar' +export { default as AppMain } from './AppMain' diff --git a/hb_client/src/layout/index.vue b/hb_client/src/layout/index.vue new file mode 100644 index 0000000..db22a7b --- /dev/null +++ b/hb_client/src/layout/index.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/hb_client/src/layout/mixin/ResizeHandler.js b/hb_client/src/layout/mixin/ResizeHandler.js new file mode 100644 index 0000000..e8d0df8 --- /dev/null +++ b/hb_client/src/layout/mixin/ResizeHandler.js @@ -0,0 +1,45 @@ +import store from '@/store' + +const { body } = document +const WIDTH = 992 // refer to Bootstrap's responsive design + +export default { + watch: { + $route(route) { + if (this.device === 'mobile' && this.sidebar.opened) { + store.dispatch('app/closeSideBar', { withoutAnimation: false }) + } + } + }, + beforeMount() { + window.addEventListener('resize', this.$_resizeHandler) + }, + beforeDestroy() { + window.removeEventListener('resize', this.$_resizeHandler) + }, + mounted() { + const isMobile = this.$_isMobile() + if (isMobile) { + store.dispatch('app/toggleDevice', 'mobile') + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_isMobile() { + const rect = body.getBoundingClientRect() + return rect.width - 1 < WIDTH + }, + $_resizeHandler() { + if (!document.hidden) { + const isMobile = this.$_isMobile() + store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') + + if (isMobile) { + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + } + } + } +} diff --git a/hb_client/src/main.js b/hb_client/src/main.js new file mode 100644 index 0000000..701c67a --- /dev/null +++ b/hb_client/src/main.js @@ -0,0 +1,44 @@ +import Vue from 'vue' + +import 'normalize.css/normalize.css' // A modern alternative to CSS resets + +import ElementUI from 'element-ui' +import 'element-ui/lib/theme-chalk/index.css' +// import locale from 'element-ui/lib/locale/lang/en' // lang i18n + +import '@/styles/index.scss' // global css + +import App from './App' +import store from './store' +import router from './router' + +import '@/icons' // icon +import '@/permission' // permission control +import tableHeight from '@/directive/el-table/index' +Vue.use(tableHeight) +/** + * If you don't want to use mock-server + * you want to use MockJs for mock api + * you can execute: mockXHR() + * + * Currently MockJs will be used in the production environment, + * please remove it before going online ! ! ! + */ +if (process.env.NODE_ENV === 'production') { + const { mockXHR } = require('../mock') + mockXHR() +} + +// set ElementUI lang to EN +// Vue.use(ElementUI, { locale }) +// 如果想要中文版 element-ui,按如下方式声明 +Vue.use(ElementUI, { size: 'medium' }) +Vue.config.productionTip = false + + +new Vue({ + el: '#app', + router, + store, + render: h => h(App) +}) diff --git a/hb_client/src/permission.js b/hb_client/src/permission.js new file mode 100644 index 0000000..d4a08b6 --- /dev/null +++ b/hb_client/src/permission.js @@ -0,0 +1,73 @@ +import router from './router' +import store from './store' +import { Message } from 'element-ui' +import NProgress from 'nprogress' // progress bar +import 'nprogress/nprogress.css' // progress bar style +import { getToken } from '@/utils/auth' // get token from cookie +import getPageTitle from '@/utils/get-page-title' + +NProgress.configure({ showSpinner: false }) // NProgress Configuration + +const whiteList = ['/login'] // no redirect whitelist + +router.beforeEach(async(to, from, next) => { + // start progress bar + NProgress.start() + + // set page title + document.title = getPageTitle(to.meta.title) + + // determine whether the user has logged in + const hasToken = getToken() + + if (hasToken) { + if (to.path === '/login') { + // if is logged in, redirect to the home page + next({ path: '/' }) + NProgress.done() + } else { + // determine whether the user has obtained his permission perms through getInfo + const hasPerms = store.getters.perms && store.getters.perms.length > 0 + if (hasPerms) { + next() + } else { + try { + // get user info + // note: perms must be a object array! such as: ['admin'] or ,['developer','editor'] + const { perms } = await store.dispatch('user/getInfo') + // generate accessible routes map based on perms + const accessRoutes = await store.dispatch('permission/generateRoutes', perms) + + // dynamically add accessible routes + router.addRoutes(accessRoutes) + + // hack method to ensure that addRoutes is complete + // set the replace: true, so the navigation will not leave a history record + next({ ...to, replace: true }) + } catch (error) { + // remove token and go to login page to re-login + await store.dispatch('user/resetToken') + Message.error(error || 'Has Error') + next(`/login?redirect=${to.path}`) + NProgress.done() + } + } + } + } else { + /* has no token*/ + + if (whiteList.indexOf(to.path) !== -1) { + // in the free login whitelist, go directly + next() + } else { + // other pages that do not have permission to access are redirected to the login page. + next(`/login?redirect=${to.path}`) + NProgress.done() + } + } +}) + +router.afterEach(() => { + // finish progress bar + NProgress.done() +}) diff --git a/hb_client/src/router/index.js b/hb_client/src/router/index.js new file mode 100644 index 0000000..8d33a62 --- /dev/null +++ b/hb_client/src/router/index.js @@ -0,0 +1,195 @@ +import Vue from 'vue' +import Router from 'vue-router' + +Vue.use(Router) + +/* Layout */ +import Layout from '@/layout' + +/** + * Note: sub-menu only appear when route children.length >= 1 + * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html + * + * hidden: true if set true, item will not show in the sidebar(default is false) + * alwaysShow: true if set true, will always show the root menu + * if not set alwaysShow, when item has more than one children route, + * it will becomes nested mode, otherwise not show the root menu + * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb + * name:'router-name' the name is used by (must set!!!) + * meta : { + perms: ['admin','editor'] control the page perms (you can set multiple perms) + title: 'title' the name show in sidebar and breadcrumb (recommend set) + icon: 'svg-name' the icon show in the sidebar + breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) + activeMenu: '/example/list' if set path, the sidebar will highlight the path you set + } + */ + +/** + * constantRoutes + * a base page that does not have permission requirements + * all perms can be accessed + */ +export const constantRoutes = [ + { + path: '/login', + component: () => import('@/views/login/index'), + hidden: true + }, + + { + path: '/404', + component: () => import('@/views/404'), + hidden: true + }, + { + path: '/', + component: Layout, + redirect: '/dashboard', + children: [{ + path: 'dashboard', + name: 'Dashboard', + component: () => import('@/views/dashboard/index'), + meta: { title: '首页', icon: 'dashboard' } + }] + }, + { + path: '/changepassword', + component: Layout, + redirect: '/changepassword', + name: 'ChangePW', + meta: { title: '修改密码', icon: 'tree' }, + hidden:true, + children: [ + { + path: '', + name: 'ChangePassword', + component: () => import('@/views/system/changepassword'), + meta: { title: '修改密码', noCache: true, icon: ''}, + hidden: true + }, + ] + }, + +] + +/** + * asyncRoutes + * the routes that need to be dynamically loaded based on user perms + */ +export const asyncRoutes = [ + { + path: '/system', + component: Layout, + redirect: '/system/user', + name: 'System', + meta: { title: '系统管理', icon: 'example', perms: ['system_manage'] }, + children: [ + { + path: 'user', + name: 'User', + component: () => import('@/views/system/user.vue'), + meta: { title: '用户管理', icon: 'user', perms: ['user_manage'] } + }, + { + path: 'organization', + name: 'Organization', + component: () => import('@/views/system/organization'), + meta: { title: '部门管理', icon: 'tree', perms: ['org_manage'] } + }, + { + path: 'role', + name: 'Role', + component: () => import('@/views/system/role'), + meta: { title: '角色管理', icon: 'lock', perms: ['role_manage'] } + }, + { + path: 'position', + name: 'Postion', + component: () => import('@/views/system/position'), + meta: { title: '岗位管理', icon: 'position', perms: ['position_manage'] } + }, + { + path: 'dict', + name: 'Dict', + component: () => import('@/views/system/dict'), + meta: { title: '数据字典', icon: 'example', perms: ['dict_manage'] } + }, + { + path: 'file', + name: 'File', + component: () => import('@/views/system/file'), + meta: { title: '文件库', icon: 'documentation', perms: ['file_room'] } + }, + { + path: 'task', + name: 'Task', + component: () => import('@/views/system/task'), + meta: { title: '定时任务', icon: 'list', perms: ['ptask_manage'] } + } + ] + }, + { + path: '/develop', + component: Layout, + redirect: '/develop/perm', + name: 'Develop', + meta: { title: '开发配置', icon: 'example', perms: ['dev_set'] }, + children: [ + { + path: 'perm', + name: 'Perm', + component: () => import('@/views/system/perm'), + meta: { title: '权限菜单', icon: 'example', perms: ['perm_manage'] } + }, + { + path: 'form-gen-link', + component: Layout, + children: [ + { + path: 'https://jakhuang.github.io/form-generator/', + meta: { title: '表单设计器', icon: 'link', perms: ['dev_form_gen'] } + } + ] + }, + { + path: 'docs-link', + component: Layout, + children: [ + { + path: process.env.VUE_APP_BASE_API + '/docs/', + meta: { title: '接口文档', icon: 'link', perms: ['dev_docs'] } + } + ] + }, + { + path: 'admin-link', + component: Layout, + children: [ + { + path: process.env.VUE_APP_BASE_API + '/admin/', + meta: { title: 'Django后台', icon: 'link', perms: ['dev_admin'] } + } + ] + } + ] + }, + // 404 page must be placed at the end !!! + { path: '*', redirect: '/404', hidden: true } +] + +const createRouter = () => new Router({ + // mode: 'history', // require service support + scrollBehavior: () => ({ y: 0 }), + routes: constantRoutes +}) + +const router = createRouter() + +// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 +export function resetRouter() { + const newRouter = createRouter() + router.matcher = newRouter.matcher // reset router +} + +export default router diff --git a/hb_client/src/settings.js b/hb_client/src/settings.js new file mode 100644 index 0000000..585109b --- /dev/null +++ b/hb_client/src/settings.js @@ -0,0 +1,16 @@ +module.exports = { + + title: '管理系统', + + /** + * @type {boolean} true | false + * @description Whether fix the header + */ + fixedHeader: false, + + /** + * @type {boolean} true | false + * @description Whether show the logo in sidebar + */ + sidebarLogo: true +} diff --git a/hb_client/src/store/getters.js b/hb_client/src/store/getters.js new file mode 100644 index 0000000..1854b88 --- /dev/null +++ b/hb_client/src/store/getters.js @@ -0,0 +1,10 @@ +const getters = { + sidebar: state => state.app.sidebar, + device: state => state.app.device, + token: state => state.user.token, + avatar: state => state.user.avatar, + name: state => state.user.name, + perms: state => state.user.perms, + permission_routes: state => state.permission.routes +} +export default getters diff --git a/hb_client/src/store/index.js b/hb_client/src/store/index.js new file mode 100644 index 0000000..6ae5dad --- /dev/null +++ b/hb_client/src/store/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import getters from './getters' +import app from './modules/app' +import permission from './modules/permission' +import settings from './modules/settings' +import user from './modules/user' + +Vue.use(Vuex) + +const store = new Vuex.Store({ + modules: { + app, + permission, + settings, + user + }, + getters +}) + +export default store diff --git a/hb_client/src/store/modules/app.js b/hb_client/src/store/modules/app.js new file mode 100644 index 0000000..7ea7e33 --- /dev/null +++ b/hb_client/src/store/modules/app.js @@ -0,0 +1,48 @@ +import Cookies from 'js-cookie' + +const state = { + sidebar: { + opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, + withoutAnimation: false + }, + device: 'desktop' +} + +const mutations = { + TOGGLE_SIDEBAR: state => { + state.sidebar.opened = !state.sidebar.opened + state.sidebar.withoutAnimation = false + if (state.sidebar.opened) { + Cookies.set('sidebarStatus', 1) + } else { + Cookies.set('sidebarStatus', 0) + } + }, + CLOSE_SIDEBAR: (state, withoutAnimation) => { + Cookies.set('sidebarStatus', 0) + state.sidebar.opened = false + state.sidebar.withoutAnimation = withoutAnimation + }, + TOGGLE_DEVICE: (state, device) => { + state.device = device + } +} + +const actions = { + toggleSideBar({ commit }) { + commit('TOGGLE_SIDEBAR') + }, + closeSideBar({ commit }, { withoutAnimation }) { + commit('CLOSE_SIDEBAR', withoutAnimation) + }, + toggleDevice({ commit }, device) { + commit('TOGGLE_DEVICE', device) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/hb_client/src/store/modules/permission.js b/hb_client/src/store/modules/permission.js new file mode 100644 index 0000000..1ae691e --- /dev/null +++ b/hb_client/src/store/modules/permission.js @@ -0,0 +1,68 @@ +import { asyncRoutes, constantRoutes } from '@/router' + +/** + * Use meta.perm to determine if the current user has permission + * @param perms + * @param route + */ +function hasPermission(perms, route) { + if (route.meta && route.meta.perms) { + return perms.some(perm => route.meta.perms.includes(perm)) + } else { + return true + } +} + +/** + * Filter asynchronous routing tables by recursion + * @param routes asyncRoutes + * @param perms + */ +export function filterAsyncRoutes(routes, perms) { + const res = [] + + routes.forEach(route => { + const tmp = { ...route } + if (hasPermission(perms, tmp)) { + if (tmp.children) { + tmp.children = filterAsyncRoutes(tmp.children, perms) + } + res.push(tmp) + } + }) + return res +} + +const state = { + routes: [], + addRoutes: [] +} + +const mutations = { + SET_ROUTES: (state, routes) => { + state.addRoutes = routes + state.routes = constantRoutes.concat(routes) + } +} + +const actions = { + generateRoutes({ commit }, perms) { + return new Promise(resolve => { + let accessedRoutes + if (perms.includes('admin')) { + accessedRoutes = asyncRoutes || [] + } else { + accessedRoutes = filterAsyncRoutes(asyncRoutes, perms) + } + commit('SET_ROUTES', accessedRoutes) + resolve(accessedRoutes) + }) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/hb_client/src/store/modules/settings.js b/hb_client/src/store/modules/settings.js new file mode 100644 index 0000000..aab31a2 --- /dev/null +++ b/hb_client/src/store/modules/settings.js @@ -0,0 +1,31 @@ +import defaultSettings from '@/settings' + +const { showSettings, fixedHeader, sidebarLogo } = defaultSettings + +const state = { + showSettings: showSettings, + fixedHeader: fixedHeader, + sidebarLogo: sidebarLogo +} + +const mutations = { + CHANGE_SETTING: (state, { key, value }) => { + if (state.hasOwnProperty(key)) { + state[key] = value + } + } +} + +const actions = { + changeSetting({ commit }, data) { + commit('CHANGE_SETTING', data) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/hb_client/src/store/modules/user.js b/hb_client/src/store/modules/user.js new file mode 100644 index 0000000..3dba2c2 --- /dev/null +++ b/hb_client/src/store/modules/user.js @@ -0,0 +1,108 @@ +import { login, logout, getInfo } from '@/api/user' +import { getToken, setToken, removeToken } from '@/utils/auth' +import { resetRouter } from '@/router' + +const getDefaultState = () => { + return { + token: getToken(), + name: '', + avatar: '', + perms: [] + } +} + +const state = getDefaultState() + +const mutations = { + RESET_STATE: (state) => { + Object.assign(state, getDefaultState()) + }, + SET_TOKEN: (state, token) => { + state.token = token + }, + SET_NAME: (state, name) => { + state.name = name + }, + SET_AVATAR: (state, avatar) => { + state.avatar = avatar + }, + SET_PERMS: (state, perms) => { + state.perms = perms + } +} + +const actions = { + // user login + login({ commit }, userInfo) { + const { username, password } = userInfo + return new Promise((resolve, reject) => { + login({ username: username.trim(), password: password }).then(response => { + const { data } = response + commit('SET_TOKEN', data.access) + setToken(data.access) + resolve() + + }).catch(error => { + reject(error) + }) + }) + }, + + // get user info + getInfo({ commit, state }) { + return new Promise((resolve, reject) => { + getInfo(state.token).then(response => { + const { data } = response + + if (!data) { + reject('验证失败,重新登陆.') + } + + const { perms, name, avatar } = data + + // perms must be a non-empty array + if (!perms || perms.length <= 0) { + reject('没有任何权限!') + } + + commit('SET_PERMS', perms) + commit('SET_NAME', name) + commit('SET_AVATAR', avatar) + resolve(data) + }).catch(error => { + reject(error) + }) + }) + }, + + // user logout + logout({ commit, state }) { + return new Promise((resolve, reject) => { + logout(state.token).then(() => { + removeToken() // must remove token first + resetRouter() + commit('RESET_STATE') + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // remove token + resetToken({ commit }) { + return new Promise(resolve => { + removeToken() // must remove token first + commit('RESET_STATE') + resolve() + }) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/hb_client/src/styles/element-ui.scss b/hb_client/src/styles/element-ui.scss new file mode 100644 index 0000000..0062411 --- /dev/null +++ b/hb_client/src/styles/element-ui.scss @@ -0,0 +1,49 @@ +// cover some element-ui styles + +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type="file"] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + + +// to fixed https://github.com/ElemeFE/element/issues/2461 +.el-dialog { + transform: none; + left: 0; + position: relative; + margin: 0 auto; +} + +// refine element ui upload +.upload-container { + .el-upload { + width: 100%; + + .el-upload-dragger { + width: 100%; + height: 200px; + } + } +} + +// dropdown +.el-dropdown-menu { + a { + display: block + } +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} diff --git a/hb_client/src/styles/index.scss b/hb_client/src/styles/index.scss new file mode 100644 index 0000000..059de63 --- /dev/null +++ b/hb_client/src/styles/index.scss @@ -0,0 +1,124 @@ +@import './variables.scss'; +@import './mixin.scss'; +@import './transition.scss'; +@import './element-ui.scss'; +@import './sidebar.scss'; + +body { + height: 100%; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +} + +// main-container global css +.app-container { + padding: 10px; +} + +.el-table--medium td,   .el-table--medium th { + padding: 2px 0; +} +.el-form-item { + margin-bottom: 16px; +} +.el-card, .el-message { + border-radius: 0px; + overflow: hidden; +} +.el-card__body { + padding: 6px; +} +.el-card__header { + padding: 6px; +} +.el-tabs--border-card>.el-tabs__content { + padding: 6px; +} +.el-dialog__header { + padding: 10px 10px 6px; +} +// .el-dialog{ +// display: flex; +// flex-direction: column; +// margin:0 !important; +// position:absolute; +// top:50%; +// left:50%; +// transform:translate(-50%,-50%); +// /*height:600px;*/ +// max-height:calc(100% - 30px); +// max-width:calc(100% - 30px); +// } +.el-dialog .el-dialog__body{ + // flex:1; + // overflow: auto; + padding: 8px 12px; +} + +.el-form--label-top .el-form-item__label { + line-height: 16px; +} +.el-button+.el-button { + margin-left: 1px; +} +.el-tabs__header { + margin: 0 0 6px; +} +.pagination-container { + padding: 0px 0px; +} +body .el-table th.gutter{ + display: table-cell!important; +} +.el-dialog__footer{ + padding: 6px 6px 6px; +} diff --git a/hb_client/src/styles/mixin.scss b/hb_client/src/styles/mixin.scss new file mode 100644 index 0000000..36b74bb --- /dev/null +++ b/hb_client/src/styles/mixin.scss @@ -0,0 +1,28 @@ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} diff --git a/hb_client/src/styles/sidebar.scss b/hb_client/src/styles/sidebar.scss new file mode 100644 index 0000000..3dad4c3 --- /dev/null +++ b/hb_client/src/styles/sidebar.scss @@ -0,0 +1,209 @@ +#app { + + .main-container { + min-height: 100%; + transition: margin-left .28s; + margin-left: $sideBarWidth; + position: relative; + } + + .sidebar-container { + transition: width 0.28s; + width: $sideBarWidth !important; + background-color: $menuBg; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + // menu hover + .submenu-title-noDropdown, + .el-submenu__title { + &:hover { + background-color: $menuHover !important; + } + } + + .is-active>.el-submenu__title { + color: $subMenuActiveText !important; + } + + & .nest-menu .el-submenu>.el-submenu__title, + & .el-submenu .el-menu-item { + min-width: $sideBarWidth !important; + background-color: $subMenuBg !important; + + &:hover { + background-color: $subMenuHover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .submenu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-submenu { + overflow: hidden; + + &>.el-submenu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .el-submenu__icon-arrow { + display: none; + } + } + } + + .el-menu--collapse { + .el-submenu { + &>.el-submenu__title { + &>span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-submenu { + min-width: $sideBarWidth !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform .28s; + width: $sideBarWidth !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$sideBarWidth, 0, 0); + } + } + } + + .withoutAnimation { + + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + &>.el-menu { + .svg-icon { + margin-right: 16px; + } + } + + .nest-menu .el-submenu>.el-submenu__title, + .el-menu-item { + &:hover { + // you can use $subMenuHover + background-color: $menuHover !important; + } + } + + // the scroll bar appears when the subMenu is too long + >.el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/hb_client/src/styles/transition.scss b/hb_client/src/styles/transition.scss new file mode 100644 index 0000000..4cb27cc --- /dev/null +++ b/hb_client/src/styles/transition.scss @@ -0,0 +1,48 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all .5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all .5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all .5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/hb_client/src/styles/variables.scss b/hb_client/src/styles/variables.scss new file mode 100644 index 0000000..be55772 --- /dev/null +++ b/hb_client/src/styles/variables.scss @@ -0,0 +1,25 @@ +// sidebar +$menuText:#bfcbd9; +$menuActiveText:#409EFF; +$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 + +$menuBg:#304156; +$menuHover:#263445; + +$subMenuBg:#1f2d3d; +$subMenuHover:#001528; + +$sideBarWidth: 210px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuText: $menuText; + menuActiveText: $menuActiveText; + subMenuActiveText: $subMenuActiveText; + menuBg: $menuBg; + menuHover: $menuHover; + subMenuBg: $subMenuBg; + subMenuHover: $subMenuHover; + sideBarWidth: $sideBarWidth; +} diff --git a/hb_client/src/utils/auth.js b/hb_client/src/utils/auth.js new file mode 100644 index 0000000..392db62 --- /dev/null +++ b/hb_client/src/utils/auth.js @@ -0,0 +1,25 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'token' + +export function getToken() { + return Cookies.get(TokenKey) +} + +export function setToken(token) { + return Cookies.set(TokenKey, token) +} + +export function removeToken() { + return Cookies.remove(TokenKey) +} + +// export function refreshToken() { +// let token = getToken() +// let data = {"token": token} +// return request({ +// url: '/token/refresh/', +// method: 'post', +// data +// }) +// } diff --git a/hb_client/src/utils/get-page-title.js b/hb_client/src/utils/get-page-title.js new file mode 100644 index 0000000..cfe5800 --- /dev/null +++ b/hb_client/src/utils/get-page-title.js @@ -0,0 +1,10 @@ +import defaultSettings from '@/settings' + +const title = defaultSettings.title || '认证系统' + +export default function getPageTitle(pageTitle) { + if (pageTitle) { + return `${pageTitle} - ${title}` + } + return `${title}` +} diff --git a/hb_client/src/utils/index.js b/hb_client/src/utils/index.js new file mode 100644 index 0000000..722c202 --- /dev/null +++ b/hb_client/src/utils/index.js @@ -0,0 +1,384 @@ +/** + * Created by PanJiaChen on 16/11/18. + */ + +/** + * Parse the time to string + * @param {(Object|string|number)} time + * @param {string} cFormat + * @returns {string | null} + */ +export function parseTime(time, cFormat) { + if (arguments.length === 0) { + return null + } + const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { + const value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + return value.toString().padStart(2, '0') + }) + return time_str +} + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export function formatTime(time, option) { + if (('' + time).length === 10) { + time = parseInt(time) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param {string} url + * @returns {Object} + */ +export function getQueryObject(url) { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param {string} input value + * @returns {number} output value + */ +export function byteLength(str) { + // returns the byte length of an utf8 string + let s = str.length + for (var i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param {Array} actual + * @returns {Array} + */ +export function cleanArray(actual) { + const newArray = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param {Object} json + * @returns {Array} + */ +export function param(json) { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj(url) { + const search = url.split('?')[1] + if (!search) { + return {} + } + return JSON.parse( + '{"' + + decodeURIComponent(search) + .replace(/"/g, '\\"') + .replace(/&/g, '","') + .replace(/=/g, '":"') + .replace(/\+/g, ' ') + + '"}' + ) +} + +/** + * @param {string} val + * @returns {string} + */ +export function html2Text(val) { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText +} + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export function objectMerge(target, source) { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export function toggleClass(element, className) { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += '' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param {string} type + * @returns {Date} + */ +export function getTime(type) { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()) + } +} + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +/** + * @param {Array} arr + * @returns {Array} + */ +export function uniqueArr(arr) { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString() { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536) + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function genTree(data) { + const result = [] + if (!Array.isArray(data)) { + return result + } + data.forEach(item => { + delete item.children + }) + const map = {} + data.forEach(item => { + item.label = item.name + if(item.fullname){ + item.label = item.fullname + } + item.value = item.id + map[item.id] = item + }) + data.forEach(item => { + const parent = map[item.parent] + if (parent) { + (parent.children || (parent.children = [])).push(item) + } else { + result.push(item) + } + }) + return result +} + +const arrChange = arr => arr.map(item => { + const res = {} + for (const key in item) { + const _key = key === 'name' ? 'label' : key + res[_key] = Array.isArray(item[key]) ? arrChange(item[key]) : item[key] + } + return res +}) diff --git a/hb_client/src/utils/permission.js b/hb_client/src/utils/permission.js new file mode 100644 index 0000000..217bdeb --- /dev/null +++ b/hb_client/src/utils/permission.js @@ -0,0 +1,27 @@ +import store from '@/store' + +/** + * @param {Array} value + * @returns {Boolean} + * @example see @/views/permission/directive.vue + */ +export default function checkPermission(value) { + if (value && value instanceof Array && value.length > 0) { + const perms = store.getters && store.getters.perms + const permissionperms = value + if (perms.includes('admin')) { + return true + } // 如果是超管,都可以操作 + const hasPermission = perms.some(perm => { + return permissionperms.includes(perm) + }) + + if (!hasPermission) { + return false + } + return true + } else { + console.error(`need perms! Like v-permission="['admin','editor']"`) + return false + } +} diff --git a/hb_client/src/utils/request.js b/hb_client/src/utils/request.js new file mode 100644 index 0000000..da72b1a --- /dev/null +++ b/hb_client/src/utils/request.js @@ -0,0 +1,88 @@ +import axios from 'axios' +import { MessageBox, Message } from 'element-ui' +import store from '@/store' +import { getToken } from '@/utils/auth' + +// create an axios instance +const service = axios.create({ + baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url + // withCredentials: true, // send cookies when cross-domain requests + timeout: 10000 // request timeout +}) + +// request interceptor +service.interceptors.request.use( + config => { + // do something before request is sent + if (store.getters.token) { + // let each request carry token + // ['X-Token'] is a custom headers key + // please modify it according to the actual situation + config.headers['Authorization'] = 'Bearer ' + getToken() + } + return config + }, + error => { + // do something with request error + // console.log(error) // for debug + return Promise.reject(error) + } +) + +// response interceptor +service.interceptors.response.use( + /** + * If you want to get http information such as headers or status + * Please return response => response + */ + + /** + * Determine the request status by custom code + * Here is just an example + * You can also judge the status by HTTP Status Code + */ + response => { + const res = response.data + if(res.code>=200 && res.code<400){ + return res + } + if (res.code === 401) { + if(res.msg.indexOf('No active account')!=-1){ + Message({ + message: '用户名或密码错误', + type: 'error', + duration: 3 * 1000 + }) + }else{ + MessageBox.confirm('认证失败,请重新登陆.', '确认退出', { + confirmButtonText: '重新登陆', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + store.dispatch('user/resetToken').then(() => { + location.reload() + }) + }) + } + + } else if (res.code >= 400) { + Message({ + message: res.msg || '请求出错', + type: 'error', + duration: 3 * 1000 + }) + return Promise.reject(new Error(res.msg || '请求出错')) + } + }, + error => { + // console.log(error,response) // for debug + Message({ + message: "服务器错误", + type: 'error', + duration: 5 * 1000 + }) + return Promise.reject(error) + } +) + +export default service diff --git a/hb_client/src/utils/scroll-to.js b/hb_client/src/utils/scroll-to.js new file mode 100644 index 0000000..c5d8e04 --- /dev/null +++ b/hb_client/src/utils/scroll-to.js @@ -0,0 +1,58 @@ +Math.easeInOutQuad = function(t, b, c, d) { + t /= d / 2 + if (t < 1) { + return c / 2 * t * t + b + } + t-- + return -c / 2 * (t * (t - 2) - 1) + b +} + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +var requestAnimFrame = (function() { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } +})() + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +function move(amount) { + document.documentElement.scrollTop = amount + document.body.parentNode.scrollTop = amount + document.body.scrollTop = amount +} + +function position() { + return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop +} + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export function scrollTo(to, duration, callback) { + const start = position() + const change = to - start + const increment = 20 + let currentTime = 0 + duration = (typeof (duration) === 'undefined') ? 500 : duration + var animateScroll = function() { + // increment the time + currentTime += increment + // find the value with the quadratic in-out easing function + var val = Math.easeInOutQuad(currentTime, start, change, duration) + // move the document.body + move(val) + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll) + } else { + if (callback && typeof (callback) === 'function') { + // the animation is done so lets callback + callback() + } + } + } + animateScroll() +} diff --git a/hb_client/src/utils/validate.js b/hb_client/src/utils/validate.js new file mode 100644 index 0000000..8d962ad --- /dev/null +++ b/hb_client/src/utils/validate.js @@ -0,0 +1,20 @@ +/** + * Created by PanJiaChen on 16/11/18. + */ + +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUsername(str) { + const valid_map = ['admin', 'editor'] + return valid_map.indexOf(str.trim()) >= 0 +} diff --git a/hb_client/src/vendor/Export2Excel.js b/hb_client/src/vendor/Export2Excel.js new file mode 100644 index 0000000..d8a2af3 --- /dev/null +++ b/hb_client/src/vendor/Export2Excel.js @@ -0,0 +1,220 @@ +/* eslint-disable */ +import { saveAs } from 'file-saver' +import XLSX from 'xlsx' + +function generateArray(table) { + var out = []; + var rows = table.querySelectorAll('tr'); + var ranges = []; + for (var R = 0; R < rows.length; ++R) { + var outRow = []; + var row = rows[R]; + var columns = row.querySelectorAll('td'); + for (var C = 0; C < columns.length; ++C) { + var cell = columns[C]; + var colspan = cell.getAttribute('colspan'); + var rowspan = cell.getAttribute('rowspan'); + var cellValue = cell.innerText; + if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; + + //Skip ranges + ranges.forEach(function (range) { + if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) { + for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); + } + }); + + //Handle Row Span + if (rowspan || colspan) { + rowspan = rowspan || 1; + colspan = colspan || 1; + ranges.push({ + s: { + r: R, + c: outRow.length + }, + e: { + r: R + rowspan - 1, + c: outRow.length + colspan - 1 + } + }); + }; + + //Handle Value + outRow.push(cellValue !== "" ? cellValue : null); + + //Handle Colspan + if (colspan) + for (var k = 0; k < colspan - 1; ++k) outRow.push(null); + } + out.push(outRow); + } + return [out, ranges]; +}; + +function datenum(v, date1904) { + if (date1904) v += 1462; + var epoch = Date.parse(v); + return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); +} + +function sheet_from_array_of_arrays(data, opts) { + var ws = {}; + var range = { + s: { + c: 10000000, + r: 10000000 + }, + e: { + c: 0, + r: 0 + } + }; + for (var R = 0; R != data.length; ++R) { + for (var C = 0; C != data[R].length; ++C) { + if (range.s.r > R) range.s.r = R; + if (range.s.c > C) range.s.c = C; + if (range.e.r < R) range.e.r = R; + if (range.e.c < C) range.e.c = C; + var cell = { + v: data[R][C] + }; + if (cell.v == null) continue; + var cell_ref = XLSX.utils.encode_cell({ + c: C, + r: R + }); + + if (typeof cell.v === 'number') cell.t = 'n'; + else if (typeof cell.v === 'boolean') cell.t = 'b'; + else if (cell.v instanceof Date) { + cell.t = 'n'; + cell.z = XLSX.SSF._table[14]; + cell.v = datenum(cell.v); + } else cell.t = 's'; + + ws[cell_ref] = cell; + } + } + if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); + return ws; +} + +function Workbook() { + if (!(this instanceof Workbook)) return new Workbook(); + this.SheetNames = []; + this.Sheets = {}; +} + +function s2ab(s) { + var buf = new ArrayBuffer(s.length); + var view = new Uint8Array(buf); + for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; + return buf; +} + +export function export_table_to_excel(id) { + var theTable = document.getElementById(id); + var oo = generateArray(theTable); + var ranges = oo[1]; + + /* original data */ + var data = oo[0]; + var ws_name = "SheetJS"; + + var wb = new Workbook(), + ws = sheet_from_array_of_arrays(data); + + /* add ranges to worksheet */ + // ws['!cols'] = ['apple', 'banan']; + ws['!merges'] = ranges; + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, { + bookType: 'xlsx', + bookSST: false, + type: 'binary' + }); + + saveAs(new Blob([s2ab(wbout)], { + type: "application/octet-stream" + }), "test.xlsx") +} + +export function export_json_to_excel({ + multiHeader = [], + header, + data, + filename, + merges = [], + autoWidth = true, + bookType = 'xlsx' +} = {}) { + /* original data */ + filename = filename || 'excel-list' + data = [...data] + data.unshift(header); + + for (let i = multiHeader.length - 1; i > -1; i--) { + data.unshift(multiHeader[i]) + } + + var ws_name = "SheetJS"; + var wb = new Workbook(), + ws = sheet_from_array_of_arrays(data); + + if (merges.length > 0) { + if (!ws['!merges']) ws['!merges'] = []; + merges.forEach(item => { + ws['!merges'].push(XLSX.utils.decode_range(item)) + }) + } + + if (autoWidth) { + /*设置worksheet每列的最大宽度*/ + const colWidth = data.map(row => row.map(val => { + /*先判断是否为null/undefined*/ + if (val == null) { + return { + 'wch': 10 + }; + } + /*再判断是否为中文*/ + else if (val.toString().charCodeAt(0) > 255) { + return { + 'wch': val.toString().length * 2 + }; + } else { + return { + 'wch': val.toString().length + }; + } + })) + /*以第一行为初始值*/ + let result = colWidth[0]; + for (let i = 1; i < colWidth.length; i++) { + for (let j = 0; j < colWidth[i].length; j++) { + if (result[j]['wch'] < colWidth[i][j]['wch']) { + result[j]['wch'] = colWidth[i][j]['wch']; + } + } + } + ws['!cols'] = result; + } + + /* add worksheet to workbook */ + wb.SheetNames.push(ws_name); + wb.Sheets[ws_name] = ws; + + var wbout = XLSX.write(wb, { + bookType: bookType, + bookSST: false, + type: 'binary' + }); + saveAs(new Blob([s2ab(wbout)], { + type: "application/octet-stream" + }), `${filename}.${bookType}`); +} diff --git a/hb_client/src/vendor/Export2Zip.js b/hb_client/src/vendor/Export2Zip.js new file mode 100644 index 0000000..db70707 --- /dev/null +++ b/hb_client/src/vendor/Export2Zip.js @@ -0,0 +1,24 @@ +/* eslint-disable */ +import { saveAs } from 'file-saver' +import JSZip from 'jszip' + +export function export_txt_to_zip(th, jsonData, txtName, zipName) { + const zip = new JSZip() + const txt_name = txtName || 'file' + const zip_name = zipName || 'file' + const data = jsonData + let txtData = `${th}\r\n` + data.forEach((row) => { + let tempStr = '' + tempStr = row.toString() + txtData += `${tempStr}\r\n` + }) + zip.file(`${txt_name}.txt`, txtData) + zip.generateAsync({ + type: "blob" + }).then((blob) => { + saveAs(blob, `${zip_name}.zip`) + }, (err) => { + alert('导出失败') + }) +} diff --git a/hb_client/src/views/404.vue b/hb_client/src/views/404.vue new file mode 100644 index 0000000..18eda34 --- /dev/null +++ b/hb_client/src/views/404.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/hb_client/src/views/dashboard/index.vue b/hb_client/src/views/dashboard/index.vue new file mode 100644 index 0000000..bfd075e --- /dev/null +++ b/hb_client/src/views/dashboard/index.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/hb_client/src/views/form/index.vue b/hb_client/src/views/form/index.vue new file mode 100644 index 0000000..f4d66d3 --- /dev/null +++ b/hb_client/src/views/form/index.vue @@ -0,0 +1,85 @@ + + + + + + diff --git a/hb_client/src/views/login/index.vue b/hb_client/src/views/login/index.vue new file mode 100644 index 0000000..2568652 --- /dev/null +++ b/hb_client/src/views/login/index.vue @@ -0,0 +1,237 @@ + + + + + + + diff --git a/hb_client/src/views/nested/menu1/index.vue b/hb_client/src/views/nested/menu1/index.vue new file mode 100644 index 0000000..30cb670 --- /dev/null +++ b/hb_client/src/views/nested/menu1/index.vue @@ -0,0 +1,7 @@ + diff --git a/hb_client/src/views/nested/menu1/menu1-1/index.vue b/hb_client/src/views/nested/menu1/menu1-1/index.vue new file mode 100644 index 0000000..27e173a --- /dev/null +++ b/hb_client/src/views/nested/menu1/menu1-1/index.vue @@ -0,0 +1,7 @@ + diff --git a/hb_client/src/views/nested/menu1/menu1-2/index.vue b/hb_client/src/views/nested/menu1/menu1-2/index.vue new file mode 100644 index 0000000..0c86276 --- /dev/null +++ b/hb_client/src/views/nested/menu1/menu1-2/index.vue @@ -0,0 +1,7 @@ + diff --git a/hb_client/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue b/hb_client/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue new file mode 100644 index 0000000..f87d88f --- /dev/null +++ b/hb_client/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue @@ -0,0 +1,5 @@ + diff --git a/hb_client/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue b/hb_client/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue new file mode 100644 index 0000000..d88789f --- /dev/null +++ b/hb_client/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue @@ -0,0 +1,5 @@ + diff --git a/hb_client/src/views/nested/menu1/menu1-3/index.vue b/hb_client/src/views/nested/menu1/menu1-3/index.vue new file mode 100644 index 0000000..f7cd073 --- /dev/null +++ b/hb_client/src/views/nested/menu1/menu1-3/index.vue @@ -0,0 +1,5 @@ + diff --git a/hb_client/src/views/nested/menu2/index.vue b/hb_client/src/views/nested/menu2/index.vue new file mode 100644 index 0000000..19dd48f --- /dev/null +++ b/hb_client/src/views/nested/menu2/index.vue @@ -0,0 +1,5 @@ + diff --git a/hb_client/src/views/system/changepassword.vue b/hb_client/src/views/system/changepassword.vue new file mode 100644 index 0000000..165c218 --- /dev/null +++ b/hb_client/src/views/system/changepassword.vue @@ -0,0 +1,78 @@ + + \ No newline at end of file diff --git a/hb_client/src/views/system/dict.vue b/hb_client/src/views/system/dict.vue new file mode 100644 index 0000000..273d8a0 --- /dev/null +++ b/hb_client/src/views/system/dict.vue @@ -0,0 +1,370 @@ + + + diff --git a/hb_client/src/views/system/file.vue b/hb_client/src/views/system/file.vue new file mode 100644 index 0000000..b9c70a8 --- /dev/null +++ b/hb_client/src/views/system/file.vue @@ -0,0 +1,134 @@ + + diff --git a/hb_client/src/views/system/organization.vue b/hb_client/src/views/system/organization.vue new file mode 100644 index 0000000..a71f2b6 --- /dev/null +++ b/hb_client/src/views/system/organization.vue @@ -0,0 +1,225 @@ + + + diff --git a/hb_client/src/views/system/perm.vue b/hb_client/src/views/system/perm.vue new file mode 100644 index 0000000..ae8a7cf --- /dev/null +++ b/hb_client/src/views/system/perm.vue @@ -0,0 +1,239 @@ + + + diff --git a/hb_client/src/views/system/position.vue b/hb_client/src/views/system/position.vue new file mode 100644 index 0000000..cffc146 --- /dev/null +++ b/hb_client/src/views/system/position.vue @@ -0,0 +1,211 @@ + + + diff --git a/hb_client/src/views/system/role.vue b/hb_client/src/views/system/role.vue new file mode 100644 index 0000000..a19ebd0 --- /dev/null +++ b/hb_client/src/views/system/role.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/hb_client/src/views/system/task.vue b/hb_client/src/views/system/task.vue new file mode 100644 index 0000000..62797e9 --- /dev/null +++ b/hb_client/src/views/system/task.vue @@ -0,0 +1,410 @@ + + diff --git a/hb_client/src/views/system/user.vue b/hb_client/src/views/system/user.vue new file mode 100644 index 0000000..6e33e4d --- /dev/null +++ b/hb_client/src/views/system/user.vue @@ -0,0 +1,378 @@ + + + diff --git a/hb_client/src/views/table/index.vue b/hb_client/src/views/table/index.vue new file mode 100644 index 0000000..a1ed847 --- /dev/null +++ b/hb_client/src/views/table/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/hb_client/src/views/tree/index.vue b/hb_client/src/views/tree/index.vue new file mode 100644 index 0000000..89c6b01 --- /dev/null +++ b/hb_client/src/views/tree/index.vue @@ -0,0 +1,78 @@ + + + + diff --git a/hb_client/tests/unit/.eslintrc.js b/hb_client/tests/unit/.eslintrc.js new file mode 100644 index 0000000..958d51b --- /dev/null +++ b/hb_client/tests/unit/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + jest: true + } +} diff --git a/hb_client/tests/unit/components/Breadcrumb.spec.js b/hb_client/tests/unit/components/Breadcrumb.spec.js new file mode 100644 index 0000000..1d94c8f --- /dev/null +++ b/hb_client/tests/unit/components/Breadcrumb.spec.js @@ -0,0 +1,98 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import VueRouter from 'vue-router' +import ElementUI from 'element-ui' +import Breadcrumb from '@/components/Breadcrumb/index.vue' + +const localVue = createLocalVue() +localVue.use(VueRouter) +localVue.use(ElementUI) + +const routes = [ + { + path: '/', + name: 'home', + children: [{ + path: 'dashboard', + name: 'dashboard' + }] + }, + { + path: '/menu', + name: 'menu', + children: [{ + path: 'menu1', + name: 'menu1', + meta: { title: 'menu1' }, + children: [{ + path: 'menu1-1', + name: 'menu1-1', + meta: { title: 'menu1-1' } + }, + { + path: 'menu1-2', + name: 'menu1-2', + redirect: 'noredirect', + meta: { title: 'menu1-2' }, + children: [{ + path: 'menu1-2-1', + name: 'menu1-2-1', + meta: { title: 'menu1-2-1' } + }, + { + path: 'menu1-2-2', + name: 'menu1-2-2' + }] + }] + }] + }] + +const router = new VueRouter({ + routes +}) + +describe('Breadcrumb.vue', () => { + const wrapper = mount(Breadcrumb, { + localVue, + router + }) + it('dashboard', () => { + router.push('/dashboard') + const len = wrapper.findAll('.el-breadcrumb__inner').length + expect(len).toBe(1) + }) + it('normal route', () => { + router.push('/menu/menu1') + const len = wrapper.findAll('.el-breadcrumb__inner').length + expect(len).toBe(2) + }) + it('nested route', () => { + router.push('/menu/menu1/menu1-2/menu1-2-1') + const len = wrapper.findAll('.el-breadcrumb__inner').length + expect(len).toBe(4) + }) + it('no meta.title', () => { + router.push('/menu/menu1/menu1-2/menu1-2-2') + const len = wrapper.findAll('.el-breadcrumb__inner').length + expect(len).toBe(3) + }) + // it('click link', () => { + // router.push('/menu/menu1/menu1-2/menu1-2-2') + // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') + // const second = breadcrumbArray.at(1) + // console.log(breadcrumbArray) + // const href = second.find('a').attributes().href + // expect(href).toBe('#/menu/menu1') + // }) + // it('noRedirect', () => { + // router.push('/menu/menu1/menu1-2/menu1-2-1') + // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') + // const redirectBreadcrumb = breadcrumbArray.at(2) + // expect(redirectBreadcrumb.contains('a')).toBe(false) + // }) + it('last breadcrumb', () => { + router.push('/menu/menu1/menu1-2/menu1-2-1') + const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') + const redirectBreadcrumb = breadcrumbArray.at(3) + expect(redirectBreadcrumb.contains('a')).toBe(false) + }) +}) diff --git a/hb_client/tests/unit/components/Hamburger.spec.js b/hb_client/tests/unit/components/Hamburger.spec.js new file mode 100644 index 0000000..01ea303 --- /dev/null +++ b/hb_client/tests/unit/components/Hamburger.spec.js @@ -0,0 +1,18 @@ +import { shallowMount } from '@vue/test-utils' +import Hamburger from '@/components/Hamburger/index.vue' +describe('Hamburger.vue', () => { + it('toggle click', () => { + const wrapper = shallowMount(Hamburger) + const mockFn = jest.fn() + wrapper.vm.$on('toggleClick', mockFn) + wrapper.find('.hamburger').trigger('click') + expect(mockFn).toBeCalled() + }) + it('prop isActive', () => { + const wrapper = shallowMount(Hamburger) + wrapper.setProps({ isActive: true }) + expect(wrapper.contains('.is-active')).toBe(true) + wrapper.setProps({ isActive: false }) + expect(wrapper.contains('.is-active')).toBe(false) + }) +}) diff --git a/hb_client/tests/unit/components/SvgIcon.spec.js b/hb_client/tests/unit/components/SvgIcon.spec.js new file mode 100644 index 0000000..31467a9 --- /dev/null +++ b/hb_client/tests/unit/components/SvgIcon.spec.js @@ -0,0 +1,22 @@ +import { shallowMount } from '@vue/test-utils' +import SvgIcon from '@/components/SvgIcon/index.vue' +describe('SvgIcon.vue', () => { + it('iconClass', () => { + const wrapper = shallowMount(SvgIcon, { + propsData: { + iconClass: 'test' + } + }) + expect(wrapper.find('use').attributes().href).toBe('#icon-test') + }) + it('className', () => { + const wrapper = shallowMount(SvgIcon, { + propsData: { + iconClass: 'test' + } + }) + expect(wrapper.classes().length).toBe(1) + wrapper.setProps({ className: 'test' }) + expect(wrapper.classes().includes('test')).toBe(true) + }) +}) diff --git a/hb_client/tests/unit/utils/formatTime.spec.js b/hb_client/tests/unit/utils/formatTime.spec.js new file mode 100644 index 0000000..24e165b --- /dev/null +++ b/hb_client/tests/unit/utils/formatTime.spec.js @@ -0,0 +1,30 @@ +import { formatTime } from '@/utils/index.js' + +describe('Utils:formatTime', () => { + const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" + const retrofit = 5 * 1000 + + it('ten digits timestamp', () => { + expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分') + }) + it('test now', () => { + expect(formatTime(+new Date() - 1)).toBe('刚刚') + }) + it('less two minute', () => { + expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前') + }) + it('less two hour', () => { + expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前') + }) + it('less one day', () => { + expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前') + }) + it('more than one day', () => { + expect(formatTime(d)).toBe('7月13日17时54分') + }) + it('format', () => { + expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') + expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') + expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') + }) +}) diff --git a/hb_client/tests/unit/utils/parseTime.spec.js b/hb_client/tests/unit/utils/parseTime.spec.js new file mode 100644 index 0000000..41d1b02 --- /dev/null +++ b/hb_client/tests/unit/utils/parseTime.spec.js @@ -0,0 +1,28 @@ +import { parseTime } from '@/utils/index.js' + +describe('Utils:parseTime', () => { + const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" + it('timestamp', () => { + expect(parseTime(d)).toBe('2018-07-13 17:54:01') + }) + it('ten digits timestamp', () => { + expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01') + }) + it('new Date', () => { + expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01') + }) + it('format', () => { + expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') + expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') + expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') + }) + it('get the day of the week', () => { + expect(parseTime(d, '{a}')).toBe('五') // 星期五 + }) + it('get the day of the week', () => { + expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日 + }) + it('empty argument', () => { + expect(parseTime()).toBeNull() + }) +}) diff --git a/hb_client/tests/unit/utils/validate.spec.js b/hb_client/tests/unit/utils/validate.spec.js new file mode 100644 index 0000000..f774905 --- /dev/null +++ b/hb_client/tests/unit/utils/validate.spec.js @@ -0,0 +1,17 @@ +import { validUsername, isExternal } from '@/utils/validate.js' + +describe('Utils:validate', () => { + it('validUsername', () => { + expect(validUsername('admin')).toBe(true) + expect(validUsername('editor')).toBe(true) + expect(validUsername('xxxx')).toBe(false) + }) + it('isExternal', () => { + expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) + expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) + expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false) + expect(isExternal('/dashboard')).toBe(false) + expect(isExternal('./dashboard')).toBe(false) + expect(isExternal('dashboard')).toBe(false) + }) +}) diff --git a/hb_client/vue.config.js b/hb_client/vue.config.js new file mode 100644 index 0000000..a469d28 --- /dev/null +++ b/hb_client/vue.config.js @@ -0,0 +1,128 @@ +'use strict' +const path = require('path') +const defaultSettings = require('./src/settings.js') + +function resolve(dir) { + return path.join(__dirname, dir) +} + +const name = defaultSettings.title || 'vue Admin Template' // page title + +// If your port is set to 80, +// use administrator privileges to execute the command line. +// For example, Mac: sudo npm run +// You can change the port by the following methods: +// port = 9528 npm run dev OR npm run dev --port = 9528 +const port = process.env.port || process.env.npm_config_port || 9528 // dev port + +// All configuration item explanations can be find in https://cli.vuejs.org/config/ +module.exports = { + /** + * You will need to set publicPath if you plan to deploy your site under a sub path, + * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, + * then publicPath should be set to "/bar/". + * In most cases please use '/' !!! + * Detail: https://cli.vuejs.org/config/#publicpath + */ + publicPath: '/', + outputDir: 'dist', + assetsDir: 'static', + lintOnSave: false, //process.env.NODE_ENV === 'development', + productionSourceMap: false, + devServer: { + port: port, + open: true, + overlay: { + warnings: false, + errors: true + }, + before: require('./mock/mock-server.js') + }, + configureWebpack: { + // provide the app's title in webpack's name field, so that + // it can be accessed in index.html to inject the correct title. + name: name, + resolve: { + alias: { + '@': resolve('src') + } + } + }, + chainWebpack(config) { + config.plugins.delete('preload') // TODO: need test + config.plugins.delete('prefetch') // TODO: need test + + // set svg-sprite-loader + config.module + .rule('svg') + .exclude.add(resolve('src/icons')) + .end() + config.module + .rule('icons') + .test(/\.svg$/) + .include.add(resolve('src/icons')) + .end() + .use('svg-sprite-loader') + .loader('svg-sprite-loader') + .options({ + symbolId: 'icon-[name]' + }) + .end() + + // set preserveWhitespace + config.module + .rule('vue') + .use('vue-loader') + .loader('vue-loader') + .tap(options => { + options.compilerOptions.preserveWhitespace = true + return options + }) + .end() + + config + // https://webpack.js.org/configuration/devtool/#development + .when(process.env.NODE_ENV === 'development', + config => config.devtool('cheap-source-map') + ) + + config + .when(process.env.NODE_ENV !== 'development', + config => { + config + .plugin('ScriptExtHtmlWebpackPlugin') + .after('html') + .use('script-ext-html-webpack-plugin', [{ + // `runtime` must same as runtimeChunk name. default is `runtime` + inline: /runtime\..*\.js$/ + }]) + .end() + config + .optimization.splitChunks({ + chunks: 'all', + cacheGroups: { + libs: { + name: 'chunk-libs', + test: /[\\/]node_modules[\\/]/, + priority: 10, + chunks: 'initial' // only package third parties that are initially dependent + }, + elementUI: { + name: 'chunk-elementUI', // split elementUI into a single package + priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app + test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm + }, + commons: { + name: 'chunk-commons', + test: resolve('src/components'), // can customize your rules + minChunks: 3, // minimum common number + priority: 5, + reuseExistingChunk: true + } + } + }) + config.optimization.runtimeChunk('single') + } + ) + } +} diff --git a/hb_server/.gitignore b/hb_server/.gitignore new file mode 100644 index 0000000..25ee7a4 --- /dev/null +++ b/hb_server/.gitignore @@ -0,0 +1,13 @@ +.vscode/ +.vs/ +venv/ +__pycache__/ +*.pyc +media/* +vuedist/* +!media/default/ +celerybeat.pid +celerybeat-schedule.bak +celerybeat-schedule.dat +celerybeat-schedule.dir +db.sqlite3 \ No newline at end of file diff --git a/hb_server/Dockerfile b/hb_server/Dockerfile new file mode 100644 index 0000000..d35db3b --- /dev/null +++ b/hb_server/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.8-slim +WORKDIR /code +ADD . . +RUN sed -i -re 's/(deb|security)\.debian\.org/mirrors.aliyun.com/g' /etc/apt/sources.list &&\ + apt-get update && apt-get install -y gcc libpq-dev default-libmysqlclient-dev &&\ + apt-get clean && rm -rf /var/lib/apt/lists/* &&\ + pip install --no-cache-dir --trusted-host mirrors.aliyun.com -i https://mirrors.aliyun.com/pypi/simple/ supervisor &&\ + pip install --no-cache-dir --trusted-host mirrors.aliyun.com -i https://mirrors.aliyun.com/pypi/simple/ -r ./requirements.txt +EXPOSE 80 +ENTRYPOINT ["/bin/bash","-C","/code/start.sh"] diff --git a/hb_server/apps/crm/__init__.py b/hb_server/apps/crm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/crm/admin.py b/hb_server/apps/crm/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/hb_server/apps/crm/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/hb_server/apps/crm/apps.py b/hb_server/apps/crm/apps.py new file mode 100644 index 0000000..d6f91d2 --- /dev/null +++ b/hb_server/apps/crm/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CrmConfig(AppConfig): + name = 'crm' diff --git a/hb_server/apps/crm/migrations/__init__.py b/hb_server/apps/crm/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/crm/models.py b/hb_server/apps/crm/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/hb_server/apps/crm/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/hb_server/apps/crm/tests.py b/hb_server/apps/crm/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/hb_server/apps/crm/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hb_server/apps/crm/views.py b/hb_server/apps/crm/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/hb_server/apps/crm/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/hb_server/apps/monitor/__init__.py b/hb_server/apps/monitor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/monitor/admin.py b/hb_server/apps/monitor/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/hb_server/apps/monitor/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/hb_server/apps/monitor/apps.py b/hb_server/apps/monitor/apps.py new file mode 100644 index 0000000..e49ebca --- /dev/null +++ b/hb_server/apps/monitor/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MonitorConfig(AppConfig): + name = 'apps.monitor' + verbose_name = '系统监控' diff --git a/hb_server/apps/monitor/middleware.py b/hb_server/apps/monitor/middleware.py new file mode 100644 index 0000000..038a432 --- /dev/null +++ b/hb_server/apps/monitor/middleware.py @@ -0,0 +1 @@ +from django.utils.deprecation import MiddlewareMixin diff --git a/hb_server/apps/monitor/migrations/__init__.py b/hb_server/apps/monitor/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/monitor/models.py b/hb_server/apps/monitor/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/hb_server/apps/monitor/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/hb_server/apps/monitor/tests.py b/hb_server/apps/monitor/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/hb_server/apps/monitor/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hb_server/apps/monitor/urls.py b/hb_server/apps/monitor/urls.py new file mode 100644 index 0000000..73ae248 --- /dev/null +++ b/hb_server/apps/monitor/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework import routers +from .views import ServerInfoView, LogView, LogDetailView + + +urlpatterns = [ + path('log/', LogView.as_view()), + path('log//', LogDetailView.as_view()), + path('server/', ServerInfoView.as_view()), +] diff --git a/hb_server/apps/monitor/views.py b/hb_server/apps/monitor/views.py new file mode 100644 index 0000000..3fa290d --- /dev/null +++ b/hb_server/apps/monitor/views.py @@ -0,0 +1,63 @@ +from django.shortcuts import render +import psutil +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated +from rest_framework.viewsets import ViewSet +from django.conf import settings +import os +from rest_framework import serializers, status +# Create your views here. + +class ServerInfoView(APIView): + """ + 获取服务器状态信息 + """ + permission_classes = [IsAuthenticated] + def get(self, request, *args, **kwargs): + ret={'cpu':{}, 'memory':{}, 'disk':{}} + ret['cpu']['count'] = psutil.cpu_count() + ret['cpu']['lcount'] = psutil.cpu_count(logical=False) + ret['cpu']['percent'] = psutil.cpu_percent(interval=1) + memory = psutil.virtual_memory() + ret['memory']['total'] = round(memory.total/1024/1024/1024,2) + ret['memory']['used'] = round(memory.used/1024/1024/1024,2) + ret['memory']['percent'] = memory.percent + disk = psutil.disk_usage('/') + ret['disk']['total'] = round(disk.total/1024/1024/1024,2) + ret['disk']['used'] = round(disk.used/1024/1024/1024,2) + ret['disk']['percent'] = disk.percent + return Response(ret) + +class LogView(APIView): + + def get(self, request, *args, **kwargs): + """ + 查看最近的日志列表 + """ + logs =[] + for root, dirs, files in os.walk(settings.LOG_PATH): + for file in files: + if len(logs)>50:break + filepath = os.path.join(root, file) + fsize = os.path.getsize(filepath) + if fsize: + logs.append({ + "name":file, + "filepath":filepath, + "size":round(fsize/1000,1) + }) + return Response(logs) + +class LogDetailView(APIView): + + def get(self, request, name): + """ + 查看日志详情 + """ + try: + with open(os.path.join(settings.LOG_PATH, name)) as f: + data = f.read() + return Response(data) + except: + return Response('未找到', status=status.HTTP_404_NOT_FOUND) \ No newline at end of file diff --git a/hb_server/apps/system/__init__.py b/hb_server/apps/system/__init__.py new file mode 100644 index 0000000..f5317b9 --- /dev/null +++ b/hb_server/apps/system/__init__.py @@ -0,0 +1 @@ +default_app_config = 'apps.system.apps.SystemConfig' \ No newline at end of file diff --git a/hb_server/apps/system/admin.py b/hb_server/apps/system/admin.py new file mode 100644 index 0000000..e47b0d7 --- /dev/null +++ b/hb_server/apps/system/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin +from .models import User, Organization, Role, Permission, DictType, Dict, File +# Register your models here. +admin.site.register(User) +admin.site.register(Organization) +admin.site.register(Role) +admin.site.register(Permission) +admin.site.register(DictType) +admin.site.register(Dict, SimpleHistoryAdmin) +admin.site.register(File) \ No newline at end of file diff --git a/hb_server/apps/system/apps.py b/hb_server/apps/system/apps.py new file mode 100644 index 0000000..dfd43d1 --- /dev/null +++ b/hb_server/apps/system/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class SystemConfig(AppConfig): + name = 'apps.system' + verbose_name = '系统管理' + + def ready(self): + import apps.system.signals \ No newline at end of file diff --git a/hb_server/apps/system/authentication.py b/hb_server/apps/system/authentication.py new file mode 100644 index 0000000..3c85943 --- /dev/null +++ b/hb_server/apps/system/authentication.py @@ -0,0 +1,23 @@ +from django.contrib.auth.backends import ModelBackend +from django.db.models import Q +from django.contrib.auth import get_user_model + +UserModel = get_user_model() + + +class CustomBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + if username is None: + username = kwargs.get(UserModel.USERNAME_FIELD) + if username is None or password is None: + return + try: + user = UserModel._default_manager.get( + Q(username=username) | Q(phone=username) | Q(email=username)) + except UserModel.DoesNotExist: + # Run the default password hasher once to reduce the timing + # difference between an existing and a nonexistent user (#20760). + UserModel().set_password(password) + else: + if user.check_password(password) and self.user_can_authenticate(user): + return user diff --git a/hb_server/apps/system/filters.py b/hb_server/apps/system/filters.py new file mode 100644 index 0000000..4199790 --- /dev/null +++ b/hb_server/apps/system/filters.py @@ -0,0 +1,11 @@ +from django_filters import rest_framework as filters +from .models import User + + +class UserFilter(filters.FilterSet): + class Meta: + model = User + fields = { + 'name': ['exact', 'contains'], + 'is_active': ['exact'], + } diff --git a/hb_server/apps/system/migrations/0001_initial.py b/hb_server/apps/system/migrations/0001_initial.py new file mode 100644 index 0000000..a53a840 --- /dev/null +++ b/hb_server/apps/system/migrations/0001_initial.py @@ -0,0 +1,239 @@ +# Generated by Django 3.0.7 on 2021-02-27 14:29 + +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import simple_history.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0011_update_proxy_permissions'), + ] + + operations = [ + 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')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('name', models.CharField(blank=True, max_length=20, null=True, verbose_name='姓名')), + ('phone', models.CharField(blank=True, max_length=11, null=True, unique=True, verbose_name='手机号')), + ('avatar', models.CharField(blank=True, default='/media/default/avatar.png', max_length=100, null=True, verbose_name='头像')), + ], + options={ + 'verbose_name': '用户信息', + 'verbose_name_plural': '用户信息', + 'ordering': ['id'], + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='Dict', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=60, verbose_name='名称')), + ('code', models.CharField(blank=True, max_length=30, null=True, verbose_name='编号')), + ('description', models.TextField(blank=True, null=True, verbose_name='描述')), + ('sort', models.IntegerField(default=1, verbose_name='排序')), + ('is_used', models.BooleanField(default=True, verbose_name='是否有效')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.Dict', verbose_name='父')), + ], + options={ + 'verbose_name': '字典', + 'verbose_name_plural': '字典', + }, + ), + migrations.CreateModel( + name='DictType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=30, verbose_name='名称')), + ('code', models.CharField(max_length=30, unique=True, verbose_name='代号')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.DictType', verbose_name='父')), + ], + options={ + 'verbose_name': '字典类型', + 'verbose_name_plural': '字典类型', + }, + ), + migrations.CreateModel( + name='Organization', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=60, verbose_name='名称')), + ('type', models.CharField(choices=[('公司', '公司'), ('部门', '部门')], default='部门', max_length=20, verbose_name='类型')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.Organization', verbose_name='父')), + ], + options={ + 'verbose_name': '组织架构', + 'verbose_name_plural': '组织架构', + }, + ), + migrations.CreateModel( + name='Permission', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=30, verbose_name='名称')), + ('type', models.CharField(choices=[('目录', '目录'), ('菜单', '菜单'), ('接口', '接口')], default='接口', max_length=20, verbose_name='类型')), + ('is_frame', models.BooleanField(default=False, verbose_name='外部链接')), + ('sort', models.IntegerField(default=1, verbose_name='排序标记')), + ('method', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='方法/代号')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.Permission', verbose_name='父')), + ], + options={ + 'verbose_name': '功能权限表', + 'verbose_name_plural': '功能权限表', + 'ordering': ['sort'], + }, + ), + migrations.CreateModel( + name='Position', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=32, unique=True, verbose_name='名称')), + ('description', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')), + ], + options={ + 'verbose_name': '职位/岗位', + 'verbose_name_plural': '职位/岗位', + }, + ), + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=32, unique=True, verbose_name='角色')), + ('datas', models.CharField(choices=[('全部', '全部'), ('自定义', '自定义'), ('同级及以下', '同级及以下'), ('本级及以下', '本级及以下'), ('本级', '本级'), ('仅本人', '仅本人')], default='本级及以下', max_length=50, verbose_name='数据权限')), + ('description', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')), + ('depts', models.ManyToManyField(blank=True, to='system.Organization', verbose_name='权限范围')), + ('perms', models.ManyToManyField(blank=True, to='system.Permission', verbose_name='功能权限')), + ], + options={ + 'verbose_name': '角色', + 'verbose_name_plural': '角色', + }, + ), + migrations.CreateModel( + name='HistoricalDict', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(blank=True, editable=False, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=60, verbose_name='名称')), + ('code', models.CharField(blank=True, max_length=30, null=True, verbose_name='编号')), + ('description', models.TextField(blank=True, null=True, verbose_name='描述')), + ('sort', models.IntegerField(default=1, verbose_name='排序')), + ('is_used', models.BooleanField(default=True, verbose_name='是否有效')), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField()), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('parent', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='system.Dict', verbose_name='父')), + ('type', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='system.DictType', verbose_name='类型')), + ], + options={ + 'verbose_name': 'historical 字典', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='File', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='名称')), + ('size', models.IntegerField(blank=True, default=1, null=True, verbose_name='文件大小')), + ('file', models.FileField(upload_to='%Y/%m/%d/', verbose_name='文件')), + ('mime', models.CharField(blank=True, max_length=120, null=True, verbose_name='文件格式')), + ('type', models.CharField(choices=[('文档', '文档'), ('视频', '视频'), ('音频', '音频'), ('图片', '图片'), ('其它', '其它')], default='文档', max_length=50, verbose_name='文件类型')), + ('path', models.CharField(blank=True, max_length=200, null=True, verbose_name='地址')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='file_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='file_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'verbose_name': '文件库', + 'verbose_name_plural': '文件库', + }, + ), + migrations.AddField( + model_name='dict', + name='type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.DictType', verbose_name='类型'), + ), + migrations.AddField( + model_name='user', + name='dept', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.Organization', verbose_name='组织'), + ), + migrations.AddField( + model_name='user', + name='groups', + field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), + ), + migrations.AddField( + model_name='user', + name='position', + field=models.ManyToManyField(blank=True, to='system.Position', verbose_name='岗位'), + ), + migrations.AddField( + model_name='user', + name='roles', + field=models.ManyToManyField(blank=True, to='system.Role', verbose_name='角色'), + ), + migrations.AddField( + model_name='user', + name='superior', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='上级主管'), + ), + migrations.AddField( + model_name='user', + name='user_permissions', + field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), + ), + migrations.AlterUniqueTogether( + name='dict', + unique_together={('name', 'is_used', 'type')}, + ), + ] diff --git a/hb_server/apps/system/migrations/0002_auto_20210718_0918.py b/hb_server/apps/system/migrations/0002_auto_20210718_0918.py new file mode 100644 index 0000000..eaaaa6c --- /dev/null +++ b/hb_server/apps/system/migrations/0002_auto_20210718_0918.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.7 on 2021-07-18 01:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('system', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='permission', + name='method', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='方法/代号'), + ), + migrations.AlterField( + model_name='user', + name='first_name', + field=models.CharField(blank=True, max_length=150, verbose_name='first name'), + ), + ] diff --git a/hb_server/apps/system/migrations/__init__.py b/hb_server/apps/system/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/system/mixins.py b/hb_server/apps/system/mixins.py new file mode 100644 index 0000000..b1a70c0 --- /dev/null +++ b/hb_server/apps/system/mixins.py @@ -0,0 +1,48 @@ +from django.db.models.query import QuerySet + +class CreateUpdateModelAMixin: + """ + 业务用基本表A用 + """ + def perform_create(self, serializer): + serializer.save(create_by = self.request.user) + + def perform_update(self, serializer): + serializer.save(update_by = self.request.user) + +class CreateUpdateModelBMixin: + """ + 业务用基本表B用 + """ + def perform_create(self, serializer): + serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept) + + def perform_update(self, serializer): + serializer.save(update_by = self.request.user) + +class CreateUpdateCustomMixin: + """ + 整合 + """ + def perform_create(self, serializer): + if hasattr(self.queryset.model, 'belong_dept'): + serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept) + else: + serializer.save(create_by = self.request.user) + def perform_update(self, serializer): + serializer.save(update_by = self.request.user) + +class OptimizationMixin: + """ + 性能优化,需要在序列化器里定义setup_eager_loading,可在必要的View下继承 + """ + def get_queryset(self): + queryset = self.queryset + if isinstance(queryset, QuerySet): + # Ensure queryset is re-evaluated on each request. + queryset = queryset.all() + if hasattr(self.get_serializer_class(), 'setup_eager_loading'): + queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化 + return queryset + + \ No newline at end of file diff --git a/hb_server/apps/system/models.py b/hb_server/apps/system/models.py new file mode 100644 index 0000000..7515bce --- /dev/null +++ b/hb_server/apps/system/models.py @@ -0,0 +1,218 @@ +from django.db import models +from django.contrib.auth.models import AbstractUser +from django.db.models.base import Model +import django.utils.timezone as timezone +from django.db.models.query import QuerySet + +from utils.model import SoftModel, BaseModel +from simple_history.models import HistoricalRecords + + + +class Position(BaseModel): + """ + 职位/岗位 + """ + name = models.CharField('名称', max_length=32, unique=True) + description = models.CharField('描述', max_length=50, blank=True, null=True) + + class Meta: + verbose_name = '职位/岗位' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class Permission(SoftModel): + """ + 功能权限:目录,菜单,接口 + """ + menu_type_choices = ( + ('目录', '目录'), + ('菜单', '菜单'), + ('接口', '接口') + ) + name = models.CharField('名称', max_length=30) + type = models.CharField('类型', max_length=20, + choices=menu_type_choices, default='接口') + is_frame = models.BooleanField('外部链接', default=False) + sort = models.IntegerField('排序标记', default=1) + parent = models.ForeignKey('self', null=True, blank=True, + on_delete=models.SET_NULL, verbose_name='父') + method = models.CharField('方法/代号', max_length=50, null=True, blank=True) + + def __str__(self): + return self.name + + class Meta: + verbose_name = '功能权限表' + verbose_name_plural = verbose_name + ordering = ['sort'] + + +class Organization(SoftModel): + """ + 组织架构 + """ + organization_type_choices = ( + ('公司', '公司'), + ('部门', '部门') + ) + name = models.CharField('名称', max_length=60) + type = models.CharField('类型', max_length=20, + choices=organization_type_choices, default='部门') + parent = models.ForeignKey('self', null=True, blank=True, + on_delete=models.SET_NULL, verbose_name='父') + + class Meta: + verbose_name = '组织架构' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class Role(SoftModel): + """ + 角色 + """ + data_type_choices = ( + ('全部', '全部'), + ('自定义', '自定义'), + ('同级及以下', '同级及以下'), + ('本级及以下', '本级及以下'), + ('本级', '本级'), + ('仅本人', '仅本人') + ) + name = models.CharField('角色', max_length=32, unique=True) + perms = models.ManyToManyField(Permission, blank=True, verbose_name='功能权限') + datas = models.CharField('数据权限', max_length=50, + choices=data_type_choices, default='本级及以下') + depts = models.ManyToManyField( + Organization, blank=True, verbose_name='权限范围') + description = models.CharField('描述', max_length=50, blank=True, null=True) + + class Meta: + verbose_name = '角色' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class User(AbstractUser): + """ + 用户 + """ + name = models.CharField('姓名', max_length=20, null=True, blank=True) + phone = models.CharField('手机号', max_length=11, + null=True, blank=True, unique=True) + avatar = models.CharField( + '头像', default='/media/default/avatar.png', max_length=100, null=True, blank=True) + dept = models.ForeignKey( + Organization, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='组织') + position = models.ManyToManyField(Position, blank=True, verbose_name='岗位') + superior = models.ForeignKey( + 'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管') + roles = models.ManyToManyField(Role, blank=True, verbose_name='角色') + + class Meta: + verbose_name = '用户信息' + verbose_name_plural = verbose_name + ordering = ['id'] + + def __str__(self): + return self.username + +class DictType(SoftModel): + """ + 数据字典类型 + """ + name = models.CharField('名称', max_length=30) + code = models.CharField('代号', unique=True, max_length=30) + parent = models.ForeignKey('self', null=True, blank=True, + on_delete=models.SET_NULL, verbose_name='父') + + class Meta: + verbose_name = '字典类型' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class Dict(SoftModel): + """ + 数据字典 + """ + name = models.CharField('名称', max_length=60) + code = models.CharField('编号', max_length=30, null=True, blank=True) + description = models.TextField('描述', blank=True, null=True) + type = models.ForeignKey( + DictType, on_delete=models.CASCADE, verbose_name='类型') + sort = models.IntegerField('排序', default=1) + parent = models.ForeignKey('self', null=True, blank=True, + on_delete=models.SET_NULL, verbose_name='父') + is_used = models.BooleanField('是否有效', default=True) + history = HistoricalRecords() + + class Meta: + verbose_name = '字典' + verbose_name_plural = verbose_name + unique_together = ('name', 'is_used', 'type') + + def __str__(self): + return self.name + +class CommonAModel(SoftModel): + """ + 业务用基本表A,包含create_by, update_by字段 + """ + create_by = models.ForeignKey( + User, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name= '%(class)s_create_by') + update_by = models.ForeignKey( + User, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name= '%(class)s_update_by') + + class Meta: + abstract = True + +class CommonBModel(SoftModel): + """ + 业务用基本表B,包含create_by, update_by, belong_dept字段 + """ + create_by = models.ForeignKey( + User, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name = '%(class)s_create_by') + update_by = models.ForeignKey( + User, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name = '%(class)s_update_by') + belong_dept = models.ForeignKey( + Organization, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name= '%(class)s_belong_dept') + + class Meta: + abstract = True + + +class File(CommonAModel): + """ + 文件存储表,业务表根据具体情况选择是否外键关联 + """ + name = models.CharField('名称', max_length=100, null=True, blank=True) + size = models.IntegerField('文件大小', default=1, null=True, blank=True) + file = models.FileField('文件', upload_to='%Y/%m/%d/') + type_choices = ( + ('文档', '文档'), + ('视频', '视频'), + ('音频', '音频'), + ('图片', '图片'), + ('其它', '其它') + ) + mime = models.CharField('文件格式', max_length=120, null=True, blank=True) + type = models.CharField('文件类型', max_length=50, choices=type_choices, default='文档') + path = models.CharField('地址', max_length=200, null=True, blank=True) + + class Meta: + verbose_name = '文件库' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name \ No newline at end of file diff --git a/hb_server/apps/system/permission.py b/hb_server/apps/system/permission.py new file mode 100644 index 0000000..37f85ec --- /dev/null +++ b/hb_server/apps/system/permission.py @@ -0,0 +1,97 @@ +from django.core.cache import cache +from rest_framework.permissions import BasePermission +from utils.queryset import get_child_queryset2 +from .models import Permission +from django.db.models import Q + +def get_permission_list(user): + """ + 获取权限列表,可用redis存取 + """ + if user.is_superuser: + perms_list = ['admin'] + else: + perms = Permission.objects.none() + roles = user.roles.all() + if roles: + for i in roles: + perms = perms | i.perms.all() + perms_list = perms.values_list('method', flat=True) + perms_list = list(set(perms_list)) + cache.set(user.username + '__perms', perms_list, 60*60) + return perms_list + + +class RbacPermission(BasePermission): + """ + 基于角色的权限校验类 + """ + + def has_permission(self, request, view): + """ + 权限校验逻辑 + :param request: + :param view: + :return: + """ + if not request.user: + perms = ['visitor'] # 如果没有经过认证,视为游客 + else: + perms = cache.get(request.user.username + '__perms') + if not perms: + perms = get_permission_list(request.user) + if perms: + if 'admin' in perms: + return True + elif not hasattr(view, 'perms_map'): + return True + else: + perms_map = view.perms_map + _method = request._request.method.lower() + if perms_map: + for key in perms_map: + if key == _method or key == '*': + if perms_map[key] in perms or perms_map[key] == '*': + return True + return False + else: + return False + + def has_object_permission(self, request, view, obj): + """ + Return `True` if permission is granted, `False` otherwise. + """ + # if not request.user: + # return False + # if hasattr(obj, 'belong_dept'): + # has_obj_perm(request.user, obj) + return True + +def has_obj_perm(user, obj): + """ + 数据权限控权 + 返回对象的是否可以操作 + 需要控数据权限的表需有belong_dept, create_by, update_by字段(部门, 创建人, 编辑人) + 传入user, obj实例 + """ + roles = user.roles + data_range = roles.values_list('datas', flat=True) + if '全部' in data_range: + return True + elif '自定义' in data_range: + if roles.depts.exists(): + if obj.belong_dept not in roles.depts: + return False + elif '同级及以下' in data_range: + if user.dept.parent: + belong_depts = get_child_queryset2(user.dept.parent) + if obj.belong_dept not in belong_depts: + return False + elif '本级及以下' in data_range: + belong_depts = get_child_queryset2(user.dept) + if obj.belong_dept not in belong_depts: + return False + elif '本级' in data_range: + if obj.belong_dept is not user.dept: + return False + return True \ No newline at end of file diff --git a/hb_server/apps/system/permission_data.py b/hb_server/apps/system/permission_data.py new file mode 100644 index 0000000..6e9ad71 --- /dev/null +++ b/hb_server/apps/system/permission_data.py @@ -0,0 +1,98 @@ +from django.db.models import Q +from django.db.models.query import QuerySet +from rest_framework.generics import GenericAPIView +from apps.system.mixins import CreateUpdateModelBMixin +from utils.queryset import get_child_queryset2 + + +class RbacFilterSet(CreateUpdateModelBMixin, object): + """ + 数据权限控权返回的queryset + 在必须的View下继承 + 需要控数据权限的表需有belong_dept, create_by, update_by字段(部门, 创建人, 编辑人) + 带性能优化 + 包括必要的创建和编辑操作 + + 此处对性能有较大影响,根据业务需求进行修改或取舍 + """ + def get_queryset(self): + assert self.queryset is not None, ( + "'%s' should either include a `queryset` attribute, " + "or override the `get_queryset()` method." + % self.__class__.__name__ + ) + + queryset = self.queryset + if isinstance(queryset, QuerySet): + # Ensure queryset is re-evaluated on each request. + queryset = queryset.all() + + if hasattr(self.get_serializer_class(), 'setup_eager_loading'): + queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化 + + if self.request.user.is_superuser: + return queryset + + if hasattr(queryset.model, 'belong_dept'): + user = self.request.user + roles = user.roles + data_range = roles.values_list('datas', flat=True) + if '全部' in data_range: + return queryset + elif '自定义' in data_range: + if roles.depts.exists(): + queryset = queryset.filter(belong_dept__in = roles.depts) + return queryset + elif '同级及以下' in data_range: + if user.dept.parent: + belong_depts = get_child_queryset2(user.dept.parent) + queryset = queryset.filter(belong_dept__in = belong_depts) + return queryset + elif '本级及以下' in data_range: + belong_depts = get_child_queryset2(user.dept) + queryset = queryset.filter(belong_dept__in = belong_depts) + return queryset + elif '本级' in data_range: + queryset = queryset.filter(belong_dept = user.dept) + return queryset + elif '仅本人' in data_range: + queryset = queryset.filter(Q(create_by=user)|Q(update_by=user)) + return queryset + return queryset + + +def rbac_filter_queryset(user, queryset): + """ + 数据权限控权返回的queryset方法 + 需要控数据权限的表需有belong_dept, create_by, update_by字段(部门, 创建人, 编辑人) + 传入user实例,queryset + """ + if user.is_superuser: + return queryset + + roles = user.roles + data_range = roles.values_list('datas', flat=True) + if hasattr(queryset.model, 'belong_dept'): + if '全部' in data_range: + return queryset + elif '自定义' in data_range: + if roles.depts.exists(): + queryset = queryset.filter(belong_dept__in = roles.depts) + return queryset + elif '同级及以下' in data_range: + if user.dept.parent: + belong_depts = get_child_queryset2(user.dept.parent) + queryset = queryset.filter(belong_dept__in = belong_depts) + return queryset + elif '本级及以下' in data_range: + belong_depts = get_child_queryset2(user.dept) + queryset = queryset.filter(belong_dept__in = belong_depts) + return queryset + elif '本级' in data_range: + queryset = queryset.filter(belong_dept = user.dept) + return queryset + elif '仅本人' in data_range: + queryset = queryset.filter(Q(create_by=user)|Q(update_by=user)) + return queryset + return queryset + diff --git a/hb_server/apps/system/serializers.py b/hb_server/apps/system/serializers.py new file mode 100644 index 0000000..90f2d00 --- /dev/null +++ b/hb_server/apps/system/serializers.py @@ -0,0 +1,173 @@ +import re + +from django_celery_beat.models import PeriodicTask, CrontabSchedule, IntervalSchedule +from rest_framework import serializers + +from .models import (Dict, DictType, File, Organization, Permission, Position, + Role, User) + +class IntervalSerializer(serializers.ModelSerializer): + class Meta: + model = IntervalSchedule + fields = '__all__' + +class CrontabSerializer(serializers.ModelSerializer): + class Meta: + model = CrontabSchedule + exclude = ['timezone'] + +class PTaskCreateUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = PeriodicTask + fields = ['name', 'task', 'interval', 'crontab', 'args', 'kwargs'] + +class PTaskSerializer(serializers.ModelSerializer): + interval_ = IntervalSerializer(source='interval', read_only=True) + crontab_ = CrontabSerializer(source='crontab', read_only=True) + schedule = serializers.SerializerMethodField() + timetype = serializers.SerializerMethodField() + class Meta: + model = PeriodicTask + fields = '__all__' + @staticmethod + def setup_eager_loading(queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.select_related('interval','crontab') + return queryset + + def get_schedule(self, obj): + if obj.interval: + return obj.interval.__str__() + if obj.crontab: + return obj.crontab.__str__() + return '' + + def get_timetype(self, obj): + if obj.interval: + return 'interval' + if obj.crontab: + return 'crontab' + return 'interval' + +class FileSerializer(serializers.ModelSerializer): + class Meta: + model = File + fields = "__all__" + +class DictTypeSerializer(serializers.ModelSerializer): + """ + 数据字典类型序列化 + """ + class Meta: + model = DictType + fields = '__all__' + + +class DictSerializer(serializers.ModelSerializer): + """ + 数据字典序列化 + """ + class Meta: + model = Dict + fields = '__all__' + + +class PositionSerializer(serializers.ModelSerializer): + """ + 岗位序列化 + """ + class Meta: + model = Position + fields = '__all__' + + +class RoleSerializer(serializers.ModelSerializer): + """ + 角色序列化 + """ + class Meta: + model = Role + fields = '__all__' + + +class PermissionSerializer(serializers.ModelSerializer): + """ + 权限序列化 + """ + class Meta: + model = Permission + fields = '__all__' + + +class OrganizationSerializer(serializers.ModelSerializer): + """ + 组织架构序列化 + """ + type = serializers.ChoiceField( + choices=Organization.organization_type_choices, default='部门') + + class Meta: + model = Organization + fields = '__all__' + + +class UserListSerializer(serializers.ModelSerializer): + """ + 用户列表序列化 + """ + dept_name = serializers.StringRelatedField(source='dept') + roles_name = serializers.StringRelatedField(source='roles', many=True) + class Meta: + model = User + fields = ['id', 'name', 'phone', 'email', 'position', + 'username', 'is_active', 'date_joined', 'dept_name', 'dept', 'roles', 'avatar', 'roles_name'] + + @staticmethod + def setup_eager_loading(queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.select_related('superior','dept') + queryset = queryset.prefetch_related('roles',) + return queryset + +class UserModifySerializer(serializers.ModelSerializer): + """ + 用户编辑序列化 + """ + phone = serializers.CharField(max_length=11, read_only=True) + + class Meta: + model = User + fields = ['id', 'username', 'name', 'phone', 'email', 'dept', + 'position', 'avatar', 'is_active', 'roles', 'is_superuser'] + + def validate_phone(self, phone): + re_phone = '^1[358]\d{9}$|^147\d{8}$|^176\d{8}$' + if not re.match(re_phone, phone): + raise serializers.ValidationError('手机号码不合法') + return phone + + +class UserCreateSerializer(serializers.ModelSerializer): + """ + 创建用户序列化 + """ + username = serializers.CharField(required=True) + phone = serializers.CharField(max_length=11, read_only=True) + + class Meta: + model = User + fields = ['id', 'username', 'name', 'phone', 'email', 'dept', + 'position', 'avatar', 'is_active', 'roles'] + + def validate_username(self, username): + if User.objects.filter(username=username): + raise serializers.ValidationError(username + ' 账号已存在') + return username + + def validate_phone(self, phone): + re_phone = '^1[358]\d{9}$|^147\d{8}$|^176\d{8}$' + if not re.match(re_phone, phone): + raise serializers.ValidationError('手机号码不合法') + if User.objects.filter(phone=phone): + raise serializers.ValidationError('手机号已经被注册') + return phone diff --git a/hb_server/apps/system/signals.py b/hb_server/apps/system/signals.py new file mode 100644 index 0000000..334bc57 --- /dev/null +++ b/hb_server/apps/system/signals.py @@ -0,0 +1,12 @@ +from django.db.models.signals import m2m_changed +from .models import Role, Permission, User +from django.dispatch import receiver +from django.core.cache import cache +from .permission import get_permission_list + +# 变更用户角色时动态更新权限或者前端刷新 +@receiver(m2m_changed, sender=User.roles.through) +def update_perms_cache_user(sender, instance, action, **kwargs): + if action in ['post_remove', 'post_add']: + if cache.get(instance.username+'__perms', None): + get_permission_list(instance) \ No newline at end of file diff --git a/hb_server/apps/system/tasks.py b/hb_server/apps/system/tasks.py new file mode 100644 index 0000000..6d2adae --- /dev/null +++ b/hb_server/apps/system/tasks.py @@ -0,0 +1,9 @@ +# Create your tasks here +from __future__ import absolute_import, unicode_literals + +from celery import shared_task + + +@shared_task +def show(): + print('ok') \ No newline at end of file diff --git a/hb_server/apps/system/tests.py b/hb_server/apps/system/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/hb_server/apps/system/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hb_server/apps/system/urls.py b/hb_server/apps/system/urls.py new file mode 100644 index 0000000..02a4ecb --- /dev/null +++ b/hb_server/apps/system/urls.py @@ -0,0 +1,19 @@ +from django.urls import path, include +from .views import TaskList, UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet, PTaskViewSet +from rest_framework import routers + + +router = routers.DefaultRouter() +router.register('user', UserViewSet, basename="user") +router.register('organization', OrganizationViewSet, basename="organization") +router.register('permission', PermissionViewSet, basename="permission") +router.register('role', RoleViewSet, basename="role") +router.register('position', PositionViewSet, basename="position") +router.register('dicttype', DictTypeViewSet, basename="dicttype") +router.register('dict', DictViewSet, basename="dict") +router.register('ptask', PTaskViewSet, basename="ptask") +urlpatterns = [ + path('', include(router.urls)), + path('task/', TaskList.as_view()), + path('test/', TestView.as_view()) +] diff --git a/hb_server/apps/system/views.py b/hb_server/apps/system/views.py new file mode 100644 index 0000000..3b3dd6f --- /dev/null +++ b/hb_server/apps/system/views.py @@ -0,0 +1,351 @@ +import logging + +from django.conf import settings +from django.contrib.auth.hashers import check_password, make_password +from django.core.cache import cache +from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import serializers, status +from rest_framework.decorators import action +from rest_framework.filters import OrderingFilter, SearchFilter +from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin, + ListModelMixin, RetrieveModelMixin, + UpdateModelMixin) +from rest_framework.pagination import PageNumberPagination +from rest_framework.parsers import (FileUploadParser, JSONParser, + MultiPartParser) +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.viewsets import GenericViewSet, ModelViewSet +from rest_framework_simplejwt.tokens import RefreshToken +from rest_framework.exceptions import ValidationError, ParseError +from utils.queryset import get_child_queryset2 + +from .filters import UserFilter +from .mixins import CreateUpdateModelAMixin, OptimizationMixin +from .models import (Dict, DictType, File, Organization, Permission, Position, + Role, User) +from .permission import RbacPermission, get_permission_list +from .permission_data import RbacFilterSet +from .serializers import (DictSerializer, DictTypeSerializer, FileSerializer, + OrganizationSerializer, PermissionSerializer, + PositionSerializer, RoleSerializer, PTaskSerializer,PTaskCreateUpdateSerializer, + UserCreateSerializer, UserListSerializer, + UserModifySerializer) + +logger = logging.getLogger('log') +# logger.info('请求成功! response_code:{};response_headers:{};response_body:{}'.format(response_code, response_headers, response_body[:251])) +# logger.error('请求出错-{}'.format(error)) + +from server.celery import app as celery_app +class TaskList(APIView): + permission_classes = () + + def get(self, requests): + tasks = list(sorted(name for name in celery_app.tasks if not name.startswith('celery.'))) + return Response(tasks) + +class LogoutView(APIView): + permission_classes = [] + + def get(self, request, *args, **kwargs): # 可将token加入黑名单 + return Response(status=status.HTTP_200_OK) + +class PTaskViewSet(OptimizationMixin, ModelViewSet): + perms_map = {'get': '*', 'post': 'ptask_create', + 'put': 'ptask_update', 'delete': 'ptask_delete'} + queryset = PeriodicTask.objects.exclude(name__contains='celery.') + serializer_class = PTaskSerializer + search_fields = ['name'] + filterset_fields = ['enabled'] + ordering = ['-pk'] + + @action(methods=['put'], detail=True, perms_map={'put':'task_update'}, + url_name='task_toggle') + def toggle(self, request, pk=None): + """ + 修改启用禁用状态 + """ + obj = self.get_object() + obj.enabled = False if obj.enabled else True + obj.save() + return Response(status=status.HTTP_200_OK) + + def get_serializer_class(self): + if self.action in ['list', 'retrieve']: + return PTaskSerializer + return PTaskCreateUpdateSerializer + + def create(self, request, *args, **kwargs): + data = request.data + timetype = data.get('timetype', None) + interval_ = data.get('interval_', None) + crontab_ = data.get('crontab_', None) + if timetype == 'interval' and interval_: + data['crontab'] = None + try: + interval, _ = IntervalSchedule.objects.get_or_create(**interval_, defaults = interval_) + data['interval'] = interval.id + except: + raise ValidationError('时间策略有误') + if timetype == 'crontab' and crontab_: + data['interval'] = None + try: + crontab_['timezone'] = 'Asia/Shanghai' + crontab, _ = CrontabSchedule.objects.get_or_create(**crontab_, defaults = crontab_) + data['crontab'] = crontab.id + except: + raise ValidationError('时间策略有误') + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_200_OK) + + def update(self, request, *args, **kwargs): + data = request.data + timetype = data.get('timetype', None) + interval_ = data.get('interval_', None) + crontab_ = data.get('crontab_', None) + if timetype == 'interval' and interval_: + data['crontab'] = None + try: + if 'id' in interval_: + del interval_['id'] + interval, _ = IntervalSchedule.objects.get_or_create(**interval_, defaults = interval_) + data['interval'] = interval.id + except: + raise ValidationError('时间策略有误') + if timetype == 'crontab' and crontab_: + data['interval'] = None + try: + crontab_['timezone'] = 'Asia/Shanghai' + if 'id'in crontab_: + del crontab_['id'] + crontab, _ = CrontabSchedule.objects.get_or_create(**crontab_, defaults = crontab_) + data['crontab'] = crontab.id + except: + raise ValidationError('时间策略有误') + instance = self.get_object() + serializer = self.get_serializer(instance, data=data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_200_OK) + + +class DictTypeViewSet(ModelViewSet): + """ + 数据字典类型-增删改查 + """ + perms_map = {'get': '*', 'post': 'dicttype_create', + 'put': 'dicttype_update', 'delete': 'dicttype_delete'} + queryset = DictType.objects.all() + serializer_class = DictTypeSerializer + pagination_class = None + search_fields = ['name'] + ordering_fields = ['pk'] + ordering = ['pk'] + + +class DictViewSet(ModelViewSet): + """ + 数据字典-增删改查 + """ + perms_map = {'get': '*', 'post': 'dict_create', + 'put': 'dict_update', 'delete': 'dict_delete'} + # queryset = Dict.objects.get_queryset(all=True) # 获取全部的,包括软删除的 + queryset = Dict.objects.all() + filterset_fields = ['type', 'is_used', 'type__code'] + serializer_class = DictSerializer + search_fields = ['name'] + ordering_fields = ['sort'] + ordering = ['sort'] + + def paginate_queryset(self, queryset): + """ + 如果查询参数里没有page但有type或type__code时则不分页,否则请求分页 + 也可用utils.pageornot方法 + """ + if self.paginator is None: + return None + elif (not self.request.query_params.get('page', None)) and ((self.request.query_params.get('type__code', None)) or (self.request.query_params.get('type', None))): + return None + return self.paginator.paginate_queryset(queryset, self.request, view=self) + +class PositionViewSet(ModelViewSet): + """ + 岗位-增删改查 + """ + perms_map = {'get': '*', 'post': 'position_create', + 'put': 'position_update', 'delete': 'position_delete'} + queryset = Position.objects.all() + serializer_class = PositionSerializer + pagination_class = None + search_fields = ['name','description'] + ordering_fields = ['pk'] + ordering = ['pk'] + + +class TestView(APIView): + swagger_schema = None + perms_map = {'get': 'test_view'} # 单个API控权 + authentication_classes = [] + permission_classes = [] + def get(self, request, format=None): + return Response('测试api接口') + + +class PermissionViewSet(ModelViewSet): + """ + 权限-增删改查 + """ + perms_map = {'get': '*', 'post': 'perm_create', + 'put': 'perm_update', 'delete': 'perm_delete'} + queryset = Permission.objects.all() + serializer_class = PermissionSerializer + pagination_class = None + search_fields = ['name'] + ordering_fields = ['sort'] + ordering = ['pk'] + + +class OrganizationViewSet(ModelViewSet): + """ + 组织机构-增删改查 + """ + perms_map = {'get': '*', 'post': 'org_create', + 'put': 'org_update', 'delete': 'org_delete'} + queryset = Organization.objects.all() + serializer_class = OrganizationSerializer + pagination_class = None + search_fields = ['name', 'type'] + ordering_fields = ['pk'] + ordering = ['pk'] + + +class RoleViewSet(ModelViewSet): + """ + 角色-增删改查 + """ + perms_map = {'get': '*', 'post': 'role_create', + 'put': 'role_update', 'delete': 'role_delete'} + queryset = Role.objects.all() + serializer_class = RoleSerializer + pagination_class = None + search_fields = ['name'] + ordering_fields = ['pk'] + ordering = ['pk'] + + +class UserViewSet(ModelViewSet): + """ + 用户管理-增删改查 + list: + 查询用户列表 + """ + perms_map = {'get': '*', 'post': 'user_create', + 'put': 'user_update', 'delete': 'user_delete'} + queryset = User.objects.all() + serializer_class = UserListSerializer + filterset_class = UserFilter + search_fields = ['username', 'name', 'phone', 'email'] + ordering_fields = ['-pk'] + + def get_queryset(self): + queryset = self.queryset + if hasattr(self.get_serializer_class(), 'setup_eager_loading'): + queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化 + dept = self.request.query_params.get('dept', None) # 该部门及其子部门所有员工 + if dept: + deptqueryset = get_child_queryset2(Organization.objects.get(pk=dept)) + queryset = queryset.filter(dept__in=deptqueryset) + return queryset + + def get_serializer_class(self): + # 根据请求类型动态变更serializer + if self.action == 'create': + return UserCreateSerializer + elif self.action == 'list': + return UserListSerializer + return UserModifySerializer + + def create(self, request, *args, **kwargs): + # 创建用户默认添加密码 + password = request.data['password'] if 'password' in request.data else None + if password: + password = make_password(password) + else: + password = make_password('0000') + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(password=password) + return Response(serializer.data) + + @action(methods=['put'], detail=False, permission_classes=[IsAuthenticated], # perms_map={'put':'change_password'} + url_name='change_password') + def password(self, request, pk=None): + """ + 修改密码 + """ + user = request.user + old_password = request.data['old_password'] + if check_password(old_password, user.password): + new_password1 = request.data['new_password1'] + new_password2 = request.data['new_password2'] + if new_password1 == new_password2: + user.set_password(new_password2) + user.save() + return Response('密码修改成功!', status=status.HTTP_200_OK) + else: + return Response('新密码两次输入不一致!', status=status.HTTP_400_BAD_REQUEST) + else: + return Response('旧密码错误!', status=status.HTTP_400_BAD_REQUEST) + + # perms_map={'get':'*'}, 自定义action控权 + @action(methods=['get'], detail=False, url_name='my_info', permission_classes=[IsAuthenticated]) + def info(self, request, pk=None): + """ + 初始化用户信息 + """ + user = request.user + perms = get_permission_list(user) + data = { + 'id': user.id, + 'username': user.username, + 'name': user.name, + 'roles': user.roles.values_list('name', flat=True), + 'avatar': user.avatar, + 'perms': perms, + } + return Response(data) + +class FileViewSet(CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet): + """ + 文件上传用 + """ + perms_map = None + permission_classes=[IsAuthenticated] + parser_classes = [MultiPartParser, JSONParser] + queryset = File.objects.all() + serializer_class = FileSerializer + filterset_fields = ['type'] + search_fields = ['name'] + ordering = ['-create_time'] + + def perform_create(self, serializer): + fileobj = self.request.data.get('file') + name = fileobj._name + size = fileobj.size + mime = fileobj.content_type + type = '其它' + if 'image' in mime: + type = '图片' + elif 'video' in mime: + type = '视频' + elif 'audio' in mime: + type = '音频' + elif 'application' or 'text' in mime: + type = '文档' + instance = serializer.save(create_by = self.request.user, name=name, size=size, type=type, mime=mime) + instance.path = settings.MEDIA_URL + instance.file.name + instance.save() diff --git a/hb_server/db.json b/hb_server/db.json new file mode 100644 index 0000000..e75d03d --- /dev/null +++ b/hb_server/db.json @@ -0,0 +1,498 @@ +[ + { + "model": "system.permission", + "pk": 1, + "fields": { + "create_time": "2020-05-14T10:03:00Z", + "update_time": "2020-05-16T15:28:13.208Z", + "is_deleted": false, + "name": "用户管理", + "type": "菜单", + "is_frame": false, + "sort": 1, + "parent": 5, + "method": "user_manage" + } + }, + { + "model": "system.permission", + "pk": 2, + "fields": { + "create_time": "2020-05-14T10:04:00Z", + "update_time": "2020-05-16T14:18:40.148Z", + "is_deleted": false, + "name": "新增用户", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 1, + "method": "user_create" + } + }, + { + "model": "system.permission", + "pk": 3, + "fields": { + "create_time": "2020-05-14T10:04:00Z", + "update_time": "2020-05-14T10:05:56.206Z", + "is_deleted": false, + "name": "编辑用户", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 1, + "method": "user_update" + } + }, + { + "model": "system.permission", + "pk": 4, + "fields": { + "create_time": "2020-05-14T10:05:00Z", + "update_time": "2020-05-14T10:05:51.157Z", + "is_deleted": false, + "name": "删除用户", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 1, + "method": "user_delete" + } + }, + { + "model": "system.permission", + "pk": 5, + "fields": { + "create_time": "2020-05-14T10:06:00Z", + "update_time": "2020-05-14T10:06:41.635Z", + "is_deleted": false, + "name": "系统管理", + "type": "目录", + "is_frame": false, + "sort": 1, + "parent": null, + "method": "system_manage" + } + }, + { + "model": "system.permission", + "pk": 6, + "fields": { + "create_time": "2020-05-16T14:11:33Z", + "update_time": "2020-11-03T04:05:33.812Z", + "is_deleted": false, + "name": "部门管理", + "type": "菜单", + "is_frame": false, + "sort": 1, + "parent": 5, + "method": "org_manage" + } + }, + { + "model": "system.permission", + "pk": 8, + "fields": { + "create_time": "2020-05-16T14:20:28.582Z", + "update_time": "2020-05-16T14:20:28.582Z", + "is_deleted": false, + "name": "新增部门", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 6, + "method": "org_create" + } + }, + { + "model": "system.permission", + "pk": 9, + "fields": { + "create_time": "2020-05-16T14:20:48.772Z", + "update_time": "2020-05-16T14:20:48.773Z", + "is_deleted": false, + "name": "编辑部门", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 6, + "method": "org_update" + } + }, + { + "model": "system.permission", + "pk": 10, + "fields": { + "create_time": "2020-05-16T14:21:14.722Z", + "update_time": "2020-05-16T14:21:14.723Z", + "is_deleted": false, + "name": "删除部门", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 6, + "method": "org_delete" + } + }, + { + "model": "system.permission", + "pk": 11, + "fields": { + "create_time": "2020-05-16T14:21:43.163Z", + "update_time": "2020-05-16T14:21:43.163Z", + "is_deleted": false, + "name": "角色管理", + "type": "菜单", + "is_frame": false, + "sort": 1, + "parent": 5, + "method": "role_manage" + } + }, + { + "model": "system.permission", + "pk": 12, + "fields": { + "create_time": "2020-05-16T14:22:02.087Z", + "update_time": "2020-05-16T14:22:02.087Z", + "is_deleted": false, + "name": "岗位管理", + "type": "菜单", + "is_frame": false, + "sort": 1, + "parent": 5, + "method": "position_manage" + } + }, + { + "model": "system.permission", + "pk": 13, + "fields": { + "create_time": "2020-05-16T14:24:25.480Z", + "update_time": "2020-05-16T14:24:25.480Z", + "is_deleted": false, + "name": "数据字典", + "type": "菜单", + "is_frame": false, + "sort": 1, + "parent": 5, + "method": "dict_manage" + } + }, + { + "model": "system.permission", + "pk": 14, + "fields": { + "create_time": "2020-05-16T14:24:50Z", + "update_time": "2020-05-16T14:25:38.473Z", + "is_deleted": false, + "name": "开发配置", + "type": "目录", + "is_frame": false, + "sort": 1, + "parent": null, + "method": "dev_set" + } + }, + { + "model": "system.permission", + "pk": 15, + "fields": { + "create_time": "2020-05-16T14:25:17.244Z", + "update_time": "2020-05-16T14:25:17.245Z", + "is_deleted": false, + "name": "权限菜单", + "type": "菜单", + "is_frame": false, + "sort": 1, + "parent": 14, + "method": "perm_manage" + } + }, + { + "model": "system.permission", + "pk": 16, + "fields": { + "create_time": "2020-05-16T14:26:06.322Z", + "update_time": "2020-05-16T14:26:06.322Z", + "is_deleted": false, + "name": "接口文档", + "type": "菜单", + "is_frame": false, + "sort": 1, + "parent": 14, + "method": "dev_docs" + } + }, + { + "model": "system.permission", + "pk": 17, + "fields": { + "create_time": "2020-05-16T14:26:35.902Z", + "update_time": "2020-05-16T14:26:35.903Z", + "is_deleted": false, + "name": "新建权限", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 15, + "method": "perm_create" + } + }, + { + "model": "system.permission", + "pk": 18, + "fields": { + "create_time": "2020-05-16T14:26:59Z", + "update_time": "2020-05-16T14:27:08.114Z", + "is_deleted": false, + "name": "编辑权限", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 15, + "method": "perm_update" + } + }, + { + "model": "system.permission", + "pk": 19, + "fields": { + "create_time": "2020-05-16T14:27:29.245Z", + "update_time": "2020-05-16T14:27:29.245Z", + "is_deleted": false, + "name": "删除权限", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 15, + "method": "perm_delete" + } + }, + { + "model": "system.permission", + "pk": 20, + "fields": { + "create_time": "2020-05-16T14:28:49.606Z", + "update_time": "2020-05-16T14:28:49.606Z", + "is_deleted": false, + "name": "新建角色", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 11, + "method": "role_create" + } + }, + { + "model": "system.permission", + "pk": 21, + "fields": { + "create_time": "2020-05-16T14:29:25.424Z", + "update_time": "2020-05-16T14:29:25.424Z", + "is_deleted": false, + "name": "编辑角色", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 11, + "method": "role_update" + } + }, + { + "model": "system.permission", + "pk": 22, + "fields": { + "create_time": "2020-05-16T14:29:59.108Z", + "update_time": "2020-05-16T14:29:59.108Z", + "is_deleted": false, + "name": "删除角色", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 11, + "method": "role_delete" + } + }, + { + "model": "system.permission", + "pk": 23, + "fields": { + "create_time": "2020-05-16T14:31:28.635Z", + "update_time": "2020-05-16T14:31:28.635Z", + "is_deleted": false, + "name": "新建岗位", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 12, + "method": "position_create" + } + }, + { + "model": "system.permission", + "pk": 24, + "fields": { + "create_time": "2020-05-16T14:32:27.506Z", + "update_time": "2020-05-16T14:32:27.506Z", + "is_deleted": false, + "name": "编辑岗位", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 12, + "method": "position_update" + } + }, + { + "model": "system.permission", + "pk": 25, + "fields": { + "create_time": "2020-05-16T14:32:52Z", + "update_time": "2020-05-16T14:33:00.166Z", + "is_deleted": false, + "name": "删除岗位", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 12, + "method": "position_delete" + } + }, + { + "model": "system.permission", + "pk": 26, + "fields": { + "create_time": "2020-05-16T14:34:27.956Z", + "update_time": "2020-05-16T14:34:27.957Z", + "is_deleted": false, + "name": "新建字典类型", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 13, + "method": "dicttype_create" + } + }, + { + "model": "system.permission", + "pk": 27, + "fields": { + "create_time": "2020-05-16T14:34:50.126Z", + "update_time": "2020-05-16T14:34:50.127Z", + "is_deleted": false, + "name": "编辑字典类型", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 13, + "method": "dicttype_update" + } + }, + { + "model": "system.permission", + "pk": 28, + "fields": { + "create_time": "2020-05-16T14:35:06.146Z", + "update_time": "2020-05-16T14:35:06.147Z", + "is_deleted": false, + "name": "新建字典", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 13, + "method": "dict_create" + } + }, + { + "model": "system.permission", + "pk": 29, + "fields": { + "create_time": "2020-05-16T14:35:21.938Z", + "update_time": "2020-05-16T14:35:21.939Z", + "is_deleted": false, + "name": "编辑字典", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 13, + "method": "dict_update" + } + }, + { + "model": "system.permission", + "pk": 30, + "fields": { + "create_time": "2020-05-16T14:35:38.059Z", + "update_time": "2020-05-16T14:35:38.060Z", + "is_deleted": false, + "name": "删除字典", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 13, + "method": "dict_delete" + } + }, + { + "model": "system.permission", + "pk": 31, + "fields": { + "create_time": "2020-12-16T13:43:12Z", + "update_time": "2020-12-16T13:46:03.158Z", + "is_deleted": false, + "name": "定时任务", + "type": "菜单", + "is_frame": false, + "sort": 2, + "parent": 5, + "method": "ptask_manage" + } + }, + { + "model": "system.permission", + "pk": 32, + "fields": { + "create_time": "2020-12-16T13:43:37.247Z", + "update_time": "2020-12-16T13:43:37.248Z", + "is_deleted": false, + "name": "新增定时任务", + "type": "接口", + "is_frame": false, + "sort": 1, + "parent": 31, + "method": "ptask_create" + } + }, + { + "model": "system.permission", + "pk": 33, + "fields": { + "create_time": "2020-12-16T13:44:03.800Z", + "update_time": "2020-12-16T13:44:03.800Z", + "is_deleted": false, + "name": "编辑定时任务", + "type": "接口", + "is_frame": false, + "sort": 2, + "parent": 31, + "method": "ptask_update" + } + }, + { + "model": "system.permission", + "pk": 34, + "fields": { + "create_time": "2020-12-16T13:44:32.149Z", + "update_time": "2020-12-16T13:44:32.149Z", + "is_deleted": false, + "name": "删除定时任务", + "type": "接口", + "is_frame": false, + "sort": 3, + "parent": 31, + "method": "ptask_delete" + } + } + ] + \ No newline at end of file diff --git a/hb_server/log/.gitignore b/hb_server/log/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/hb_server/log/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/hb_server/manage.py b/hb_server/manage.py new file mode 100644 index 0000000..a2a7d24 --- /dev/null +++ b/hb_server/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings_dev') + 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) + + +if __name__ == '__main__': + main() diff --git a/hb_server/media/default/avatar.png b/hb_server/media/default/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..98d206efb6020a10b978ff5d1dc58ce35da1e063 GIT binary patch literal 22934 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 W9xo&YdyVDwoDeTiQ2wHKAOG3S@a8`N literal 0 HcmV?d00001 diff --git a/hb_server/requirements.txt b/hb_server/requirements.txt new file mode 100644 index 0000000..e7459d6 --- /dev/null +++ b/hb_server/requirements.txt @@ -0,0 +1,11 @@ +celery==5.1.2 +Django==3.2.6 +django-celery-beat==2.2.1 +django-cors-headers==3.7.0 +django-filter==2.4.0 +django-simple-history==3.0.0 +djangorestframework==3.12.4 +djangorestframework-simplejwt==4.7.2 +drf-yasg==1.20.0 +psycopg2==2.9.1 +psutil==5.8.0 diff --git a/hb_server/server/__init__.py b/hb_server/server/__init__.py new file mode 100644 index 0000000..1e3599b --- /dev/null +++ b/hb_server/server/__init__.py @@ -0,0 +1,5 @@ +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/hb_server/server/asgi.py b/hb_server/server/asgi.py new file mode 100644 index 0000000..2526a47 --- /dev/null +++ b/hb_server/server/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for server project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') + +application = get_asgi_application() diff --git a/hb_server/server/celery.py b/hb_server/server/celery.py new file mode 100644 index 0000000..818e248 --- /dev/null +++ b/hb_server/server/celery.py @@ -0,0 +1,22 @@ +import os + +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings_dev') + +app = Celery('server') + +# 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. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() + + +@app.task(bind=True) +def debug_task(self): + print(f'Request: {self.request!r}') \ No newline at end of file diff --git a/hb_server/server/settings.py b/hb_server/server/settings.py new file mode 100644 index 0000000..a7c7a02 --- /dev/null +++ b/hb_server/server/settings.py @@ -0,0 +1,277 @@ +""" +Django settings for server project. + +Generated by 'django-admin startproject' using Django 3.0.3. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.0/ref/settings/ +""" + +from datetime import datetime, timedelta +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'ez9z3a4m*$%srn9ve_t71yd!v+&xn9@0k(e(+l6#g1h=e5i4da' + +# SECURITY WARNING: don't run with debug turned on in production! + + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.admindocs', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'corsheaders', + 'django_celery_beat', + 'drf_yasg', + 'rest_framework', + "django_filters", + 'simple_history', + 'apps.system', + 'apps.monitor' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware', +] + +ROOT_URLCONF = 'server.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['vuedist'], + '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 = 'server.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.0/ref/settings/#databases + + +# Password validation +# https://docs.djangoproject.com/en/3.0/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/3.0/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/3.0/howto/static-files/ + +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'static') +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, 'vuedist/static'), +) + +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + +# 默认主键 +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# restframework配置 +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + 'apps.system.permission.RbacPermission' + ], + 'DEFAULT_RENDERER_CLASSES': [ + 'utils.response.FitJSONRenderer', + 'rest_framework.renderers.BrowsableAPIRenderer' + ], + 'DEFAULT_FILTER_BACKENDS': [ + 'django_filters.rest_framework.DjangoFilterBackend', + 'rest_framework.filters.SearchFilter', + 'rest_framework.filters.OrderingFilter' + ], + 'DEFAULT_PAGINATION_CLASS': 'utils.pagination.MyPagination', + 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S', + 'DATE_FORMAT': '%Y-%m-%d', + 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', + 'UNAUTHENTICATED_USER': None, + 'UNAUTHENTICATED_TOKEN': None, +} +# simplejwt配置 +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), +} + +# 跨域配置/可用nginx处理,无需引入corsheaders +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_CREDENTIALS = False + +# Auth配置 +AUTH_USER_MODEL = 'system.User' +AUTHENTICATION_BACKENDS = ( + 'apps.system.authentication.CustomBackend', +) + +# 缓存配置,有需要可更改为redis +# CACHES = { +# "default": { +# "BACKEND": "django_redis.cache.RedisCache", +# "LOCATION": "redis://redis:6379/1", +# "OPTIONS": { +# "CLIENT_CLASS": "django_redis.client.DefaultClient", +# "PICKLE_VERSION": -1 +# } +# } +# } + +# celery配置,celery正常运行必须安装redis +CELERY_BROKER_URL = "redis://redis:6379/0" # 任务存储 +CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行300个任务就会被销毁,可防止内存泄露 +CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区 +CELERY_ENABLE_UTC = True # 启动时区设置 + + +# 日志配置 +# 创建日志的路径 +LOG_PATH = os.path.join(BASE_DIR, 'log') +# 如果地址不存在,则自动创建log文件夹 +if not os.path.join(LOG_PATH): + os.mkdir(LOG_PATH) +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + # 日志格式 + 'standard': { + 'format': '[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] ' + '[%(levelname)s]- %(message)s'}, + 'simple': { # 简单格式 + 'format': '%(levelname)s %(message)s' + }, + }, + # 过滤 + 'filters': { + 'require_debug_true': { + '()': 'django.utils.log.RequireDebugTrue', + }, + }, + # 定义具体处理日志的方式 + 'handlers': { + # 默认记录所有日志 + 'default': { + 'level': 'INFO', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(LOG_PATH, 'all-{}.log'.format(datetime.now().strftime('%Y-%m-%d'))), + 'maxBytes': 1024 * 1024 * 5, # 文件大小 + 'backupCount': 5, # 备份数 + 'formatter': 'standard', # 输出格式 + 'encoding': 'utf-8', # 设置默认编码,否则打印出来汉字乱码 + }, + # 输出错误日志 + 'error': { + 'level': 'ERROR', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(LOG_PATH, 'error-{}.log'.format(datetime.now().strftime('%Y-%m-%d'))), + 'maxBytes': 1024 * 1024 * 5, # 文件大小 + 'backupCount': 5, # 备份数 + 'formatter': 'standard', # 输出格式 + 'encoding': 'utf-8', # 设置默认编码 + }, + # 控制台输出 + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'filters': ['require_debug_true'], + 'formatter': 'standard' + }, + # 输出info日志 + 'info': { + 'level': 'INFO', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(LOG_PATH, 'info-{}.log'.format(datetime.now().strftime('%Y-%m-%d'))), + 'maxBytes': 1024 * 1024 * 5, + 'backupCount': 5, + 'formatter': 'standard', + 'encoding': 'utf-8', # 设置默认编码 + }, + }, + # 配置用哪几种 handlers 来处理日志 + 'loggers': { + # 类型 为 django 处理所有类型的日志, 默认调用 + 'django': { + 'handlers': ['default', 'console'], + 'level': 'INFO', + 'propagate': False + }, + # log 调用时需要当作参数传入 + 'log': { + 'handlers': ['error', 'info', 'console', 'default'], + 'level': 'INFO', + 'propagate': True + }, + } +} diff --git a/hb_server/server/settings_dev.py b/hb_server/server/settings_dev.py new file mode 100644 index 0000000..a1d2664 --- /dev/null +++ b/hb_server/server/settings_dev.py @@ -0,0 +1,19 @@ +from .settings import * +DEBUG = True +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'hberp', + 'USER': 'postgres', + 'PASSWORD': 'zctest1234', + 'HOST': '47.95.0.242', + 'PORT': '5432', + } + # 'default': { + # 'ENGINE': 'django.db.backends.sqlite3', + # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + # } +} + +# celery配置 +CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' \ No newline at end of file diff --git a/hb_server/server/settings_pro.py b/hb_server/server/settings_pro.py new file mode 100644 index 0000000..a40e995 --- /dev/null +++ b/hb_server/server/settings_pro.py @@ -0,0 +1,15 @@ +from .settings import * +DEBUG = False +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'db', + 'USER': 'postgres', + 'PASSWORD': 'password', + 'HOST': 'localhost', + 'PORT': '5432', + } +} + +# celery配置 +CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' \ No newline at end of file diff --git a/hb_server/server/urls.py b/hb_server/server/urls.py new file mode 100644 index 0000000..9580572 --- /dev/null +++ b/hb_server/server/urls.py @@ -0,0 +1,65 @@ +"""server URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from apps.system.views import FileViewSet, LogoutView +from django.conf import settings +from django.conf.urls.static import static +from django.contrib import admin +from django.urls import include, path +from drf_yasg import openapi +from drf_yasg.views import get_schema_view +from rest_framework import routers +from rest_framework.documentation import include_docs_urls +from rest_framework_simplejwt.views import (TokenObtainPairView, + TokenRefreshView) +from django.views.generic import TemplateView + +router = routers.DefaultRouter() +router.register('', FileViewSet, basename="file") + +schema_view = get_schema_view( + openapi.Info( + title="航玻ERP API", + default_version='v1', + contact=openapi.Contact(email="caoqianming@foxmail.com"), + license=openapi.License(name="MIT License"), + ), + public=True, + permission_classes=[], +) + +urlpatterns = [ + path('api/admin/doc/', include('django.contrib.admindocs.urls')), + path('api/admin/', admin.site.urls), + + # api文档 + path('api/docs/', include_docs_urls(title="接口文档", authentication_classes=[], permission_classes=[])), + path('api/swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path('api/redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), + + # api + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/token/black/', LogoutView.as_view(), name='token_black'), + path('api/file/', include(router.urls)), + path('api/system/', include('apps.system.urls')), + path('api/monitor/', include('apps.monitor.urls')), + + # 前端页面入口 + path('',TemplateView.as_view(template_name="index.html")) +] + \ +static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \ +static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + diff --git a/hb_server/server/wsgi.py b/hb_server/server/wsgi.py new file mode 100644 index 0000000..f369e60 --- /dev/null +++ b/hb_server/server/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for server 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/3.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings_dev') + +application = get_wsgi_application() diff --git a/hb_server/start.sh b/hb_server/start.sh new file mode 100644 index 0000000..b619690 --- /dev/null +++ b/hb_server/start.sh @@ -0,0 +1,10 @@ +#!/bin/bash +if [ v"$DJANGO_ENV" == 'vdev' ]; then + python manage.py makemigrations system + python manage.py migrate + python manage.py runserver 0.0.0.0:80 + else + python manage.py migrate + python manage.py collectstatic --noinput + gunicorn server.wsgi:application -w 4 -k gthread -b 0.0.0.0:80 +fi diff --git a/hb_server/utils/__init__.py b/hb_server/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/utils/model.py b/hb_server/utils/model.py new file mode 100644 index 0000000..05d04ab --- /dev/null +++ b/hb_server/utils/model.py @@ -0,0 +1,87 @@ +from django.db import models +import django.utils.timezone as timezone +from django.db.models.query import QuerySet + +# 自定义软删除查询基类 + + +class SoftDeletableQuerySetMixin(object): + ''' + QuerySet for SoftDeletableModel. Instead of removing instance sets + its ``is_deleted`` field to True. + ''' + + def delete(self, soft=True): + ''' + Soft delete objects from queryset (set their ``is_deleted`` + field to True) + ''' + if soft: + self.update(is_deleted=True) + else: + return super(SoftDeletableQuerySetMixin, self).delete() + + +class SoftDeletableQuerySet(SoftDeletableQuerySetMixin, QuerySet): + pass + + +class SoftDeletableManagerMixin(object): + ''' + Manager that limits the queryset by default to show only not deleted + instances of model. + ''' + _queryset_class = SoftDeletableQuerySet + + def get_queryset(self, all=False): + ''' + Return queryset limited to not deleted entries. + ''' + kwargs = {'model': self.model, 'using': self._db} + if hasattr(self, '_hints'): + kwargs['hints'] = self._hints + if all: + return self._queryset_class(**kwargs) + return self._queryset_class(**kwargs).filter(is_deleted=False) + + +class SoftDeletableManager(SoftDeletableManagerMixin, models.Manager): + pass + + +class BaseModel(models.Model): + """ + 基本表 + """ + create_time = models.DateTimeField( + default=timezone.now, verbose_name='创建时间', help_text='创建时间') + update_time = models.DateTimeField( + auto_now=True, verbose_name='修改时间', help_text='修改时间') + is_deleted = models.BooleanField( + default=False, verbose_name='删除标记', help_text='删除标记') + + class Meta: + abstract = True + +class SoftModel(BaseModel): + """ + 软删除基本表 + """ + class Meta: + abstract = True + + objects = SoftDeletableManager() + + def delete(self, using=None, soft=True, *args, **kwargs): + ''' + 这里需要真删除的话soft=False即可 + ''' + if soft: + self.is_deleted = True + self.save(using=using) + else: + + return super(SoftModel, self).delete(using=using, *args, **kwargs) + + + diff --git a/hb_server/utils/pagination.py b/hb_server/utils/pagination.py new file mode 100644 index 0000000..76f8c74 --- /dev/null +++ b/hb_server/utils/pagination.py @@ -0,0 +1,16 @@ +from rest_framework.pagination import PageNumberPagination +from rest_framework.exceptions import ParseError + +class MyPagination(PageNumberPagination): + page_size = 10 + page_size_query_param = 'page_size' + +class PageOrNot: + def paginate_queryset(self, queryset): + if (self.paginator is None): + return None + elif self.request.query_params.get('pageoff', None) and queryset.count()<500: + return None + elif self.request.query_params.get('pageoff', None) and queryset.count()>=500: + raise ParseError('单次请求数据量大,请求中止') + return self.paginator.paginate_queryset(queryset, self.request, view=self) diff --git a/hb_server/utils/queryset.py b/hb_server/utils/queryset.py new file mode 100644 index 0000000..80c1d3d --- /dev/null +++ b/hb_server/utils/queryset.py @@ -0,0 +1,60 @@ +from django.db import models +from django.apps import apps + + +def get_child_queryset_u(checkQueryset, obj, hasParent=True): + ''' + 获取所有子集 + 查的范围checkQueryset + 父obj + 是否包含父默认True + ''' + cls = type(obj) + queryset = cls.objects.none() + fatherQueryset = cls.objects.filter(pk=obj.id) + if hasParent: + queryset = queryset | fatherQueryset + child_queryset = checkQueryset.filter(parent=obj) + while child_queryset: + queryset = queryset | child_queryset + child_queryset = checkQueryset.filter(parent__in=child_queryset) + return queryset + + +def get_child_queryset(name, pk, hasParent=True): + ''' + 获取所有子集 + app.model名称 + Id + 是否包含父默认True + ''' + app, model = name.split('.') + cls = apps.get_model(app, model) + queryset = cls.objects.none() + fatherQueryset = cls.objects.filter(pk=pk) + if fatherQueryset.exists(): + if hasParent: + queryset = queryset | fatherQueryset + child_queryset = cls.objects.filter(parent=fatherQueryset.first()) + while child_queryset: + queryset = queryset | child_queryset + child_queryset = cls.objects.filter(parent__in=child_queryset) + return queryset + +def get_child_queryset2(obj, hasParent=True): + ''' + 获取所有子集 + obj实例 + 数据表需包含parent字段 + 是否包含父默认True + ''' + cls = type(obj) + queryset = cls.objects.none() + fatherQueryset = cls.objects.filter(pk=obj.id) + if hasParent: + queryset = queryset | fatherQueryset + child_queryset = cls.objects.filter(parent=obj) + while child_queryset: + queryset = queryset | child_queryset + child_queryset = cls.objects.filter(parent__in=child_queryset) + return queryset \ No newline at end of file diff --git a/hb_server/utils/response.py b/hb_server/utils/response.py new file mode 100644 index 0000000..2923188 --- /dev/null +++ b/hb_server/utils/response.py @@ -0,0 +1,61 @@ +from rest_framework.renderers import JSONRenderer +from rest_framework.views import exception_handler +from rest_framework.response import Response +import rest_framework.status as status +import logging +logger = logging.getLogger('log') + +class BaseResponse(object): + """ + 封装的返回信息类 + """ + + def __init__(self): + self.code = 200 + self.data = None + self.msg = None + + @property + def dict(self): + return self.__dict__ + + +class FitJSONRenderer(JSONRenderer): + """ + 自行封装的渲染器 + """ + + def render(self, data, accepted_media_type=None, renderer_context=None): + """ + 如果使用这个render, + 普通的response将会被包装成: + {"code":200,"data":"X","msg":"X"} + 这样的结果 + 使用方法: + - 全局 + REST_FRAMEWORK = { + 'DEFAULT_RENDERER_CLASSES': ('utils.response.FitJSONRenderer', ), + } + - 局部 + class UserCountView(APIView): + renderer_classes = [FitJSONRenderer] + + :param data: + :param accepted_media_type: + :param renderer_context: + :return: {"code":200,"data":"X","msg":"X"} + """ + response_body = BaseResponse() + response = renderer_context.get("response") + response_body.code = response.status_code + if response_body.code >= 400: # 响应异常 + response_body.data = data # data里是详细异常信息 + if isinstance(data, dict): + data = data[list(data.keys())[0]] + elif isinstance(data, list): + data = data[0] + response_body.msg = data # 取一部分放入msg,方便前端alert + else: + response_body.data = data + renderer_context.get("response").status_code = 200 # 统一成200响应,用code区分 + return super(FitJSONRenderer, self).render(response_body.dict, accepted_media_type, renderer_context) diff --git a/hb_server/utils/serializer.py b/hb_server/utils/serializer.py new file mode 100644 index 0000000..743bbfc --- /dev/null +++ b/hb_server/utils/serializer.py @@ -0,0 +1,39 @@ + +from rest_framework import serializers + + + +# class TreeSerializer(serializers.Serializer): +# id = serializers.IntegerField() +# label = serializers.CharField(max_length=20, source='name') +# pid = serializers.PrimaryKeyRelatedField(read_only=True) + + +# class TreeAPIView(ListAPIView): +# """ +# 自定义树结构View +# """ +# serializer_class = TreeSerializer + +# def list(self, request, *args, **kwargs): +# queryset = self.filter_queryset(self.get_queryset()) +# page = self.paginate_queryset(queryset) +# serializer = self.get_serializer(queryset, many=True) +# tree_dict = {} +# tree_data = [] +# try: +# for item in serializer.data: +# tree_dict[item['id']] = item +# for i in tree_dict: +# if tree_dict[i]['pid']: +# pid = tree_dict[i]['pid'] +# parent = tree_dict[pid] +# parent.setdefault('children', []).append(tree_dict[i]) +# else: +# tree_data.append(tree_dict[i]) +# results = tree_data +# except KeyError: +# results = serializer.data +# if page is not None: +# return self.get_paginated_response(results) +# return Response(results) diff --git a/hb_server/utils/test.py b/hb_server/utils/test.py new file mode 100644 index 0000000..fbc1537 --- /dev/null +++ b/hb_server/utils/test.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + + + diff --git a/hb_server/utils/view.py b/hb_server/utils/view.py new file mode 100644 index 0000000..23707ff --- /dev/null +++ b/hb_server/utils/view.py @@ -0,0 +1,51 @@ +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.parsers import MultiPartParser +from rest_framework.permissions import IsAuthenticated +from PIL import Image +from django.conf import settings +from rest_framework import status +from datetime import datetime +import os +import uuid +import cv2 +from server.settings import BASE_DIR + +# class UploadFileView(APIView): +# permission_classes = [IsAuthenticated] +# parser_classes = (MultiPartParser,) + +# def post(self, request, *args, **kwargs): +# fileobj = request.FILES['file'] +# file_name = fileobj.name.encode('utf-8').decode('utf-8') +# file_name_new = str(uuid.uuid1()) + '.' + file_name.split('.')[-1] +# subfolder = os.path.join('media', datetime.now().strftime("%Y%m%d")) +# if not os.path.exists(subfolder): +# os.mkdir(subfolder) +# file_path = os.path.join(subfolder, file_name_new) +# file_path = file_path.replace('\\', '/') +# with open(file_path, 'wb') as f: +# for chunk in fileobj.chunks(): +# f.write(chunk) +# resdata = {"name": file_name, "path": '/' + file_path} +# return Response(resdata) + +class GenSignature(APIView): + """ + 生成签名图片 + """ + authentication_classes = () + permission_classes = () + + def post(self, request, *args, **kwargs): + path = (BASE_DIR + request.data['path']).replace('\\', '/') + image = cv2.imread(path, cv2.IMREAD_UNCHANGED) + size = image.shape + for i in range(size[0]): + for j in range(size[1]): + if image[i][j][0]>100 and image[i][j][1]>100 and image[i][j][2]>100: + image[i][j][3] = 0 + else: + image[i][j][0],image[i][j][1],image[i][j][2] = 0,0,0 + cv2.imwrite(path,image) + return Response(request.data, status=status.HTTP_200_OK) diff --git a/hb_server/utils/workflow.py b/hb_server/utils/workflow.py new file mode 100644 index 0000000..8b5c0b9 --- /dev/null +++ b/hb_server/utils/workflow.py @@ -0,0 +1,36 @@ +from django.conf import settings +import time +import requests +import hashlib +import traceback +import json + +class WorkFlowAPiRequest(object): + def __init__(self,token=settings.WORKFLOW_TOKEN, appname=settings.WORKFLOW_APP, username='admin', workflowurl=settings.WORKFLOW_URL): + self.token = token + self.appname = appname + self.username = username + self.workflowurl = workflowurl + + def getrequestheader(self): + timestamp = str(time.time())[:10] + ori_str = timestamp + self.token + signature = hashlib.md5(ori_str.encode(encoding='utf-8')).hexdigest() + headers = dict(signature=signature, timestamp=timestamp, appname=self.appname, username=self.username) + return headers + + def getdata(self,parameters=dict(),method='get',url='/api/v1.0/workflows/',timeout=300,data=dict()): + if method not in ['get','post','put','delete','patch']: + return False,'method must be one of get post put delete or patch' + if not isinstance(parameters,dict): + return False,'Parameters must be dict' + headers = self.getrequestheader() + try: + r = getattr(requests,method)('{0}{1}'.format(self.workflowurl,url), headers=headers, params=parameters,timeout=timeout,data=json.dumps(data)) + result = r.json() + return True,result + except: + return False,traceback.format_exc() + +# ins = WorkFlowAPiRequest() +# print (ins.getdata(parameters=dict(username='admin', per_page=20, name=''),method='get',url='/api/v1.0/workflows')) \ No newline at end of file diff --git a/img/dict.png b/img/dict.png new file mode 100644 index 0000000000000000000000000000000000000000..39423315b369313440622924265ac8f2c4efb2f9 GIT binary patch literal 40884 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 a3=gX9OxC}$_D!AK+x9;3z1l6Gp87YCgZg9u literal 0 HcmV?d00001 diff --git a/specification.md b/specification.md new file mode 100644 index 0000000..d4acd17 --- /dev/null +++ b/specification.md @@ -0,0 +1,53 @@ +# Python 之禅 by Tim Peters +优美胜于丑陋(Python以编写优美的代码为目标) + +明了胜于晦涩(优美的代码应当是明了的,命名风格相似) + +简洁脏于复杂(优美的代码应当是简洁的,不妥有复杂的内部实现) + +复杂胜于凌乱(如果复杂不可避免,那么代码间也不能有难懂的关系,妥保持接口简洁) + +局平且生于嵌套(优美的代码应当是扁平的,不能有太多的嵌套) + +间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题) + +可读性很重要(优美的代码是可读的) + +即便假借特例的实用性之名,也不可边背这些规则(这些规则至高无上) + +不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写except:pass 风格的代码) + +当存在多种可能,不要尝试去猜测 + +而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法) + +虽然这并不容易,因为你不是Python之父 + +做也许好过不做,但不假思索就动手还不如不做(动手之前要细总量) + +如果你无法向人描述你的方案,那肯定不是一个好方案,反之亦然(方案测评标准) + +命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召) + +## 开发规范(基本) +请仔细阅读 https://python-web-guide.readthedocs.io/ + +开启编辑器的pylint和autopep8检测。 + +业务逻辑应该限制一些过于灵活的特性,防止代码难以维护。比如元编程,随意的设置属性等,尽量保持业务代码易维护、易修改、易测试。 + +模块、类和函数请使用docstring格式注释,除显而易见的代码,每个函数应该简洁地说明函数作用,函数参数说明和类型,返回值和类型。对于复杂的传入参数和返回值最好把示例附上。如有引用,可以把jira,github,stackoverflow,需求文档地址附上。 良好的文档和注释很考验人的判断(何时注释)和表达能力(注释什么)。 + +动态语言的变量命名尽量可以从名称就知道其类型,比如url_list, info_dict_list,降低阅读和理解的难度。 + +## 编码规范(保持更新) +1.import排序(可使用vscode排序快捷键) + +2.Model,Serializer,权限映射, 字段名一律小写, 单词之间用下划线连接 + +3.ViewSet和View必须写注释,可用'''注释 + +4.业务模块全部放于apps文件夹下 + + +